diff --git a/client/client_recv.js b/client/client_recv.js index 8a19afc..4789658 100644 --- a/client/client_recv.js +++ b/client/client_recv.js @@ -116,13 +116,16 @@ function bitmap_bbox(event) { return bbox; } -async function handle_event(state, context, event, relax = false) { +function handle_event(state, context, event, relax = false) { if (config.debug_print) console.debug(`event type ${event.type} from user ${event.user_id}`); + let need_draw = false; + switch (event.type) { case EVENT.STROKE: { if (event.user_id != state.me) { clear_dynamic_stroke(state, context, event.user_id); + need_draw = true; } add_static_stroke(state, context, event, relax); @@ -131,6 +134,7 @@ async function handle_event(state, context, event, relax = false) { } case EVENT.UNDO: { + need_draw = true; console.error('todo'); // for (let i = state.events.length - 1; i >=0; --i) { // const other_event = state.events[i]; @@ -173,6 +177,7 @@ async function handle_event(state, context, event, relax = false) { } case EVENT.IMAGE: { + need_draw = true; console.error('todo'); // const url = config.image_url + event.image_id; // const item = document.createElement('img'); @@ -198,6 +203,7 @@ async function handle_event(state, context, event, relax = false) { } case EVENT.IMAGE_MOVE: { + need_draw = true; console.error('todo'); // // Already moved due to local prediction // if (event.user_id !== state.me.id) { @@ -216,6 +222,7 @@ async function handle_event(state, context, event, relax = false) { } case EVENT.ERASER: { + need_draw = true; console.error('todo'); // if (event.deleted) { // break; @@ -239,6 +246,8 @@ async function handle_event(state, context, event, relax = false) { console.error('fuck'); } } + + return need_draw; } async function handle_message(state, context, d) { @@ -273,14 +282,14 @@ async function handle_message(state, context, d) { for (let i = 0; i < event_count; ++i) { const event = des_event(d); - await handle_event(state, context, event, true); + handle_event(state, context, event, true); state.events.push(event); } - recompute_static_data(context); - do_draw = true; + recompute_static_data(context); + send_ack(event_count); sync_queue(state); @@ -324,13 +333,12 @@ async function handle_message(state, context, d) { for (let i = 0; i < count; ++i) { const event = des_event(d); if (i >= first) { - handle_event(state, context, event); + const need_draw = handle_event(state, context, event); + do_draw = do_draw || need_draw; state.events.push(event); } } - do_draw = true; - state.sn = sn; send_ack(sn); // await? diff --git a/client/client_send.js b/client/client_send.js index 3d793bb..91a4ea5 100644 --- a/client/client_send.js +++ b/client/client_send.js @@ -220,7 +220,7 @@ function predraw_event(x, y) { function stroke_event(state) { return { 'type': EVENT.STROKE, - 'points': state.current_strokes[state.me].points, + 'points': process_stroke(state.current_strokes[state.me].points), 'width': state.current_strokes[state.me].width, 'color': state.current_strokes[state.me].color, }; diff --git a/client/default.css b/client/default.css index 896568a..d4ffad9 100644 --- a/client/default.css +++ b/client/default.css @@ -1,60 +1,38 @@ +:root { + --dark-blue: #2f343d; + --dark-hover: #888; + --radius: 5px; + --hgap: 5px; + --gap: 10px; +} + html, body { margin: 0; padding: 0; + width: 100%; + height: 100%; overflow: hidden; - touch-action: none; -} - -.dhide { - display: none !important; -} - -.canvas { - position: absolute; - top: 0; - left: 0; - opacity: 1; - transition: opacity .2s; - transform-origin: top left; - pointer-events: none; } -#toucher { - position: fixed; +canvas { width: 100%; height: 100%; - top: 0; - left: 0; - z-index: 5; /* above all canvases, but below tools */ + display: block; cursor: crosshair; } -.canvas.white { - opacity: 0; -} - -#canvas-images { - z-index: 0; -} - -#canvas0 { - z-index: 1; - background: #eee; - background-position: 0px 0px; - background-size: 32px 32px; - background-image: radial-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 10%); +canvas.movemode { + cursor: grab; } -#canvas1 { - z-index: 2; - opacity: 0.3; +canvas.movemode.moving { + cursor: grabbing; } .tools-wrapper { position: fixed; bottom: 0; width: 100%; - height: 32px; display: flex; justify-content: center; align-items: end; @@ -62,61 +40,97 @@ html, body { pointer-events: none; } +.pallete-wrapper { + position: fixed; + top: 0; + left: 0; + height: 100%; + pointer-events: none; + display: flex; + flex-direction: column; + justify-content: center; +} + +.pallete { + pointer-events: all; + display: grid; + flex-direction: column; + align-items: center; + background: var(--dark-blue); + border-top-right-radius: var(--radius); + border-bottom-right-radius: var(--radius); +/* border-bottom-left-radius: var(--radius);*/ + padding-top: var(--gap); + padding-bottom: var(--gap); +} + +.pallete .color { + padding: var(--gap); + cursor: pointer; + background: var(--dark-blue); + transition: transform .1s ease-in-out; +} + +.pallete .color:hover { + background: var(--dark-hover); +} + +.pallete .color.active { + transform: translateX(10px); + border-top-right-radius: var(--radius); + border-bottom-right-radius: var(--radius); +} + +.pallete .color.active:hover { + background: var(--dark-blue); +} + +.pallete .color-pane { + width: 24px; + height: 24px; + box-sizing: border-box; + border-radius: var(--radius); +} + .tools { pointer-events: all; display: flex; align-items: center; justify-content: center; - background: #333; - border-radius: 5px; + background: var(--dark-blue); + border-radius: var(--radius); border-bottom-right-radius: 0; border-bottom-left-radius: 0; height: 42px; - padding-left: 10px; - padding-right: 10px; + padding-left: var(--gap); + padding-right: var(--gap); } .tool { cursor: pointer; - padding-left: 10px; - padding-right: 10px; + padding-left: var(--gap); + padding-right: var(--gap); height: 100%; display: flex; align-items: center; - background: #333; + background: var(--dark-blue); transition: transform .1s ease-in-out; user-select: none; } .tool:hover { - background: #888; + background: var(--dark-hover); } .tool.active { transform: translateY(-10px); - border-top-right-radius: 5px; - border-top-left-radius: 5px; - background: #333; + border-top-right-radius: var(--radius); + border-top-left-radius: var(--radius); + background: var(--dark-blue); } .tool img { height: 24px; width: 24px; filter: invert(100%); -} - -.toolbar { - visibility: hidden; -} - -.floating-image { - position: absolute; - user-drag: none; - user-select: none; -} - -.floating-image.activated { - outline: 5px solid #5286ff; - z-index: 999999 !important; - cursor: grab; } \ No newline at end of file diff --git a/client/index.html b/client/index.html index 283ee99..9dcaac6 100644 --- a/client/index.html +++ b/client/index.html @@ -4,106 +4,41 @@ Desk - - - - - - - - - - - + + + + + + + + + + + + + - - +
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/client/math.js b/client/math.js index 2d38e62..b11ffd9 100644 --- a/client/math.js +++ b/client/math.js @@ -166,9 +166,9 @@ function stroke_intersects_region(points, bbox) { } function color_to_u32(color_str) { - const r = parseInt(color_str.substring(1, 3), 16); - const g = parseInt(color_str.substring(3, 5), 16); - const b = parseInt(color_str.substring(5, 7), 16); + const r = parseInt(color_str.substring(0, 2), 16); + const g = parseInt(color_str.substring(2, 4), 16); + const b = parseInt(color_str.substring(4, 6), 16); return (r << 16) | (g << 8) | b; } diff --git a/client/tools.js b/client/tools.js index 55b7eb7..adedbac 100644 --- a/client/tools.js +++ b/client/tools.js @@ -1,26 +1,35 @@ -function tools_switch(state, tool) { +function switch_tool(state, item) { + const tool = item.getAttribute('data-tool'); + if (state.tools.active_element) { state.tools.active_element.classList.remove('active'); } state.tools.active = tool; - state.tools.active_element = document.querySelector(`.tool[data-tool="${tool}"]`); + state.tools.active_element = item; state.tools.active_element.classList.add('active'); } -function init_tools(state, context) { - const pencil = document.querySelector('.tool[data-tool="pencil"]'); - const ruler = document.querySelector('.tool[data-tool="ruler"]'); - const eraser = document.querySelector('.tool[data-tool="eraser"]'); - const undo = document.querySelector('.tool[data-tool="undo"]'); - - pencil.addEventListener('click', () => tools_switch(state, 'pencil')); - ruler.addEventListener('click', () => tools_switch(state, 'ruler')); - eraser.addEventListener('click', () => tools_switch(state, 'eraser')); - undo.addEventListener('click', () => { - pop_stroke(state, context); - window.requestAnimationFrame(() => draw(state, context)); - }); - - tools_switch(state, 'pencil'); +function switch_color(state, item) { + const color = item.getAttribute('data-color'); + + if (state.colors.active_element) { + state.colors.active_element.classList.remove('active'); + } + + state.colors.active = color_to_u32(color); + state.colors.active_element = item; + state.colors.active_element.classList.add('active'); +} + +function init_tools(state) { + const tools = document.querySelectorAll('.tools .tool'); + const colors = document.querySelectorAll('.pallete .color'); + + tools.forEach((item) => { item.addEventListener('click', () => switch_tool(state, item)); }); + colors.forEach((item) => { item.addEventListener('click', () => switch_color(state, item)); }); + + // TODO: from localstorage + switch_tool(state, document.querySelector('.tool[data-tool="pencil"]')); + switch_color(state, document.querySelector('.color[data-color="000000"]')); } \ No newline at end of file diff --git a/client/webgl.js b/client/webgl.js index 6a108ee..5524884 100644 --- a/client/webgl.js +++ b/client/webgl.js @@ -163,7 +163,6 @@ function main() { 'lsn': 0, 'server_lsn': 0, - 'color': 0, 'stroke_width': 8, 'touch': { @@ -193,6 +192,11 @@ function main() { 'active_element': null, }, + 'colors': { + 'active': null, + 'active_element': null, + }, + 'timers': { 'ws_reconnect': null, }, @@ -231,7 +235,7 @@ function main() { init_webgl(state, context); init_listeners(state, context); - init_tools(state, context); + init_tools(state); ws_connect(state, context, true); diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js index e8cb430..0b573e7 100644 --- a/client/webgl_geometry.js +++ b/client/webgl_geometry.js @@ -141,15 +141,28 @@ function recompute_dynamic_data(state, context) { context.dynamic_positions_f32 = new Float32Array(total_dynamic_length); context.dynamic_colors_u8 = new Uint8Array(total_dynamic_length / 2 * 3); + // TODO: preview stroke colors for other users + context.dynamic_colors_u8.fill(0); + let at = 0; for (const player_id in context.dynamic_positions) { context.dynamic_positions_f32.set(context.dynamic_positions[player_id], at); + + if (parseInt(player_id) === state.me) { + const color_u32 = state.colors.active; + const r = (color_u32 >> 16) & 0xFF; + const g = (color_u32 >> 8) & 0xFF; + const b = color_u32 & 0xFF; + for (let i = 0; i < context.dynamic_positions[player_id].length; ++i) { + context.dynamic_colors_u8[at / 2 * 3 + i * 3 + 0] = r; + context.dynamic_colors_u8[at / 2 * 3 + i * 3 + 1] = g; + context.dynamic_colors_u8[at / 2 * 3 + i * 3 + 2] = b; + } + } + at += context.dynamic_positions[player_id].length; } - - // TODO: preview stroke colors - context.dynamic_colors_u8.fill(0); } function update_dynamic_stroke(state, context, player_id, point) { @@ -157,7 +170,7 @@ function update_dynamic_stroke(state, context, player_id, point) { state.current_strokes[player_id] = { 'points': [], 'width': 8, // TODO - 'color': 0, // TODO + 'color': state.colors.active, }; context.dynamic_positions[player_id] = []; @@ -177,6 +190,7 @@ function update_dynamic_stroke(state, context, player_id, point) { function clear_dynamic_stroke(state, context, player_id) { if (player_id in state.current_strokes) { state.current_strokes[player_id].points.length = 0; + state.current_strokes[player_id].color = state.colors.active; context.dynamic_positions[player_id].length = 0; recompute_dynamic_data(state, context); } diff --git a/client/webgl_listeners.js b/client/webgl_listeners.js index b975b9e..cbfac86 100644 --- a/client/webgl_listeners.js +++ b/client/webgl_listeners.js @@ -102,7 +102,7 @@ function mouseup(e, state, context) { if (state.drawing) { const stroke = { - 'color': state.color, + 'color': state.colors.active, 'width': state.stroke_width, 'points': process_stroke(state.current_strokes[state.me].points), 'user_id': state.me, @@ -229,6 +229,7 @@ function touchmove(e, state, context) { if (state.touch.buffered.length > 0) { clear_dynamic_stroke(state, context, state.me); + // BUG: can't see these on other clients!! for (const p of state.touch.buffered) { update_dynamic_stroke(state, context, state.me, p); fire_event(predraw_event(canvasp.x, canvasp.y)); @@ -314,7 +315,7 @@ function touchend(e, state, context) { // await queue_event(event); const stroke = { - 'color': state.color, + 'color': state.colors.active, 'width': state.stroke_width, 'points': process_stroke(state.current_strokes[state.me].points), 'user_id': state.me,