diff --git a/README.txt b/README.txt index ab43e50..b2d3331 100644 --- a/README.txt +++ b/README.txt @@ -30,6 +30,9 @@ Release: - Do NOT use session id as player id LUL - Save events to indexeddb (as some kind of a blob), restore on reconnect and page reload - Local prediction for tools! + - Immediately commit a stroke to the canvas, change order if earlier strokes arrive + - Show my own image immediately, show placeholders while images are loading (add bitmap size to event) + - undo immediately, this one can not arrive out of order, because noone else is going to undo MY actions * Missing features I do not consider bonus + Player pointers + Pretty player pointers diff --git a/client/default.css b/client/default.css index c23c499..8960f8e 100644 --- a/client/default.css +++ b/client/default.css @@ -43,6 +43,10 @@ canvas { cursor: url('icons/crosshair.svg') 16 16, crosshair; } +canvas.tool-pointer { + cursor: default; +} + canvas.picker { cursor: url('icons/picker.svg') 0 19, crosshair; } diff --git a/client/icons/pointer.svg b/client/icons/pointer.svg new file mode 100644 index 0000000..e1ecc66 --- /dev/null +++ b/client/icons/pointer.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + diff --git a/client/index.html b/client/index.html index e966fd0..917e995 100644 --- a/client/index.html +++ b/client/index.html @@ -234,6 +234,7 @@
+
diff --git a/client/index.js b/client/index.js index 84fcea9..9d31042 100644 --- a/client/index.js +++ b/client/index.js @@ -233,7 +233,7 @@ async function main() { 'wasm': {}, - 'background_pattern': 'grid', + 'background_pattern': 'dots', }; const context = { diff --git a/client/tools.js b/client/tools.js index 5b28778..cd5f5f1 100644 --- a/client/tools.js +++ b/client/tools.js @@ -10,9 +10,22 @@ function switch_tool(state, item) { state.tools.active_element.classList.remove('active'); } + const old_class = 'tool-' + state.tools.active; + const new_class = 'tool-' + tool; + + document.querySelector('canvas').classList.remove(old_class); + state.tools.active = tool; state.tools.active_element = item; state.tools.active_element.classList.add('active'); + + document.querySelector('canvas').classList.add(new_class); + + if (tool === 'pointer' || tool === 'eraser') { + document.querySelector('.brush-dom').classList.add('dhide'); + } else { + document.querySelector('.brush-dom').classList.remove('dhide'); + } } function select_color(state, item, color_u32) { diff --git a/client/webgl_draw.js b/client/webgl_draw.js index f3610cd..472fd20 100644 --- a/client/webgl_draw.js +++ b/client/webgl_draw.js @@ -226,20 +226,22 @@ async function draw(state, context, animate, ts) { gl.vertexAttribPointer(locations['a_pos'], 2, gl.FLOAT, false, 2 * 4, 0); for (const entry of context.images) { - if (state.active_image === entry.key) { - //gl.uniform1i(locations['u_active'], 1); - } else { - //gl.uniform1i(locations['u_active'], 0); - } - gl.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height); gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom); gl.uniform2f(locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y); gl.uniform1i(locations['u_texture'], 0); // Only 1 active texture for each drawcall + gl.uniform1i(locations['u_solid'], 0); gl.bindTexture(gl.TEXTURE_2D, entry.texture); gl.drawArrays(gl.TRIANGLES, offset, 6); + // Highlight active image + if (entry.key === context.active_image) { + gl.uniform1i(locations['u_solid'], 1); + gl.uniform4f(locations['u_color'], 0.133 * 0.5, 0.545 * 0.5, 0.902 * 0.5, 0.5); + gl.drawArrays(gl.TRIANGLES, offset, 6); + } + offset += 6; } } diff --git a/client/webgl_listeners.js b/client/webgl_listeners.js index 60703db..de37e77 100644 --- a/client/webgl_listeners.js +++ b/client/webgl_listeners.js @@ -107,21 +107,25 @@ function zenmode() { } function enter_picker_mode(state, context) { - document.querySelector('canvas').classList.add('picker'); - document.querySelector('.picker-preview-outer').classList.remove('dhide'); - document.querySelector('.brush-dom').classList.add('dhide'); - - state.colorpicking = true; + if (state.tools.active === 'pencil') { // or other drawing tools + document.querySelector('canvas').classList.add('picker'); + document.querySelector('.picker-preview-outer').classList.remove('dhide'); + document.querySelector('.brush-dom').classList.add('dhide'); + + state.colorpicking = true; - const canvasp = screen_to_canvas(state, state.cursor); - update_color_picker_color(state, context, canvasp); + const canvasp = screen_to_canvas(state, state.cursor); + update_color_picker_color(state, context, canvasp); + } } function exit_picker_mode(state) { - document.querySelector('canvas').classList.remove('picker'); - document.querySelector('.picker-preview-outer').classList.add('dhide'); - document.querySelector('.brush-dom').classList.remove('dhide'); - state.colorpicking = false; + if (state.colorpicking) { + document.querySelector('canvas').classList.remove('picker'); + document.querySelector('.picker-preview-outer').classList.add('dhide'); + document.querySelector('.brush-dom').classList.remove('dhide'); + state.colorpicking = false; + } } async function paste(e, state, context) { @@ -160,7 +164,7 @@ function keyup(e, state, context) { state.moving = false; context.canvas.classList.remove('movemode'); } else if (e.code === 'ControlLeft' || e.code === 'ControlRight') { - exit_picker_mode(state); + exit_picker_mode(state);exit_picker_mode } else if (e.code === 'KeyZ') { state.zoomdown = false; } @@ -170,21 +174,6 @@ function mousedown(e, state, context) { const screenp = {'x': window.devicePixelRatio * e.clientX, 'y': window.devicePixelRatio * e.clientY}; const canvasp = screen_to_canvas(state, screenp); - if (e.button === 2) { - // Right click on image to select it - const image_event = image_at(state, canvasp.x, canvasp.y); - - if (image_event) { - context.active_image = image_event.image_id; - } else { - context.active_image = null; - } - - schedule_draw(state, context); - - return; - } - if (e.button !== 0 && e.button !== 1) { return; } @@ -203,15 +192,6 @@ function mousedown(e, state, context) { return; } - if (context.active_image) { - // Move selected image with left click - const image_event = image_at(state, canvasp.x, canvasp.y); - if (image_event && image_event.image_id === context.active_image) { - state.moving_image = image_event; - return; - } - } - if (state.spacedown || e.button === 1) { state.moving = true; context.canvas.classList.add('moving'); @@ -236,6 +216,16 @@ function mousedown(e, state, context) { } else if (state.tools.active === 'eraser') { state.erasing = true; + } else if (state.tools.active === 'pointer') { + const image_event = image_at(state, canvasp.x, canvasp.y); + + if (image_event) { + context.active_image = image_event.image_id; + } else { + context.active_image = null; + } + + schedule_draw(state, context); } } @@ -412,7 +402,7 @@ function mouseleave(e, state, context) { context.canvas.classList.remove('movemode'); } - exit_picker_mode(state); + //exit_picker_mode(state); // something else? } diff --git a/client/webgl_shaders.js b/client/webgl_shaders.js index 0c78cd0..c8db327 100644 --- a/client/webgl_shaders.js +++ b/client/webgl_shaders.js @@ -207,8 +207,6 @@ const tquad_vs_src = `#version 300 es void main() { vec2 screen01 = (a_pos * u_scale + u_translation) / u_res; vec2 screen02 = screen01 * 2.0; - screen02.y = 2.0 - screen02.y; - vec2 screen11 = screen02 - 1.0; int vertex_index = gl_VertexID % 6; @@ -222,6 +220,9 @@ const tquad_vs_src = `#version 300 es v_texcoord = vec2(1.0, 1.0); } + screen02.y = 2.0 - screen02.y; + vec2 screen11 = screen02 - 1.0; + gl_Position = vec4(screen11, 0, 1); } `; @@ -232,11 +233,17 @@ const tquad_fs_src = `#version 300 es in vec2 v_texcoord; uniform sampler2D u_texture; + uniform int u_solid; + uniform vec4 u_color; layout(location = 0) out vec4 FragColor; void main() { - FragColor = texture(u_texture, v_texcoord); + if (u_solid == 0) { + FragColor = texture(u_texture, v_texcoord); + } else { + FragColor = u_color; + } } `; @@ -405,6 +412,8 @@ function init_webgl(state, context) { 'u_scale': gl.getUniformLocation(context.programs['image'], 'u_scale'), 'u_translation': gl.getUniformLocation(context.programs['image'], 'u_translation'), 'u_texture': gl.getUniformLocation(context.programs['image'], 'u_texture'), + 'u_solid': gl.getUniformLocation(context.programs['image'], 'u_solid'), + 'u_color': gl.getUniformLocation(context.programs['image'], 'u_color'), }; context.locations['debug'] = {