Browse Source

Images moving around, paste image from clipboard

infinite
A.Olokhtonov 2 years ago
parent
commit
31b18e69a0
  1. 33
      client/aux.js
  2. 32
      client/client_recv.js
  3. 9
      client/client_send.js
  4. 24
      client/index.html
  5. 6
      client/index.js
  6. 25
      client/webgl_draw.js
  7. 56
      client/webgl_geometry.js
  8. 82
      client/webgl_listeners.js
  9. 8
      client/webgl_shaders.js

33
client/aux.js

@ -8,6 +8,30 @@ function ui_online() { @@ -8,6 +8,30 @@ function ui_online() {
document.querySelector('.offline-toast').classList.add('hidden');
}
async function insert_image(state, context, file) {
const bitmap = await createImageBitmap(file);
const p = { 'x': state.cursor.x, 'y': state.cursor.y };
const canvasp = screen_to_canvas(state, p);
canvasp.x -= bitmap.width / 2;
canvasp.y -= bitmap.height / 2;
const form_data = new FormData();
form_data.append('file', file);
const resp = await fetch(`/api/image?deskId=${state.desk_id}`, {
method: 'post',
body: form_data,
})
if (resp.ok) {
const image_id = await resp.text();
const event = image_event(image_id, canvasp.x, canvasp.y);
await queue_event(state, event);
}
}
function event_size(event) {
let size = 1 + 3; // type + padding
@ -64,4 +88,13 @@ function find_touch(touchlist, id) { @@ -64,4 +88,13 @@ function find_touch(touchlist, id) {
}
return null;
}
function find_image(state, image_id) {
for (let i = state.events.length - 1; i >= 0; --i) {
const event = state.events[i];
if (event.type === EVENT.IMAGE && !event.deleted && event.image_id === image_id) {
return event;
}
}
}

32
client/client_recv.js

@ -222,7 +222,10 @@ function handle_event(state, context, event, relax = false) { @@ -222,7 +222,10 @@ function handle_event(state, context, event, relax = false) {
const bitmap = await createImageBitmap(blob);
const p = {'x': event.x, 'y': event.y};
add_image(context, bitmap, p);
event.width = bitmap.width;
event.height = bitmap.height;
add_image(context, event.image_id, bitmap, p);
// God knows when this will actually complete (it loads the image from the server)
// so do not set need_draw. Instead just schedule the draw ourselves when done
@ -236,20 +239,19 @@ function handle_event(state, context, event, relax = false) { @@ -236,20 +239,19 @@ 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) {
// const image_id = event.image_id;
// const item = document.querySelector(`.floating-image[data-image-id="${image_id}"]`);
// const ix = state.images[event.image_id].x += event.x;
// const iy = state.images[event.image_id].y += event.y;
// if (item) {
// item.style.transform = `translate(${ix}px, ${iy}px)`;
// }
// }
// Already moved due to local prediction
if (event.user_id !== state.me.id) {
const image_id = event.image_id;
const image_event = find_image(state, image_id);
if (image_event) {
// if (config.debug_print) console.debug('move image', image_id, 'to', image_event.x, image_event.y);
image_event.x = event.x;
image_event.y = event.y;
move_image(context, image_event);
need_draw = true;
}
}
break;
}

9
client/client_send.js

@ -254,6 +254,15 @@ function image_event(image_id, x, y) { @@ -254,6 +254,15 @@ function image_event(image_id, x, y) {
};
}
function image_move_event(image_id, x, y) {
return {
'type': EVENT.IMAGE_MOVE,
'image_id': image_id,
'x': x,
'y': y,
};
}
function stroke_event(state) {
return {
'type': EVENT.STROKE,

24
client/index.html

@ -7,20 +7,20 @@ @@ -7,20 +7,20 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="shortcut icon" href="icons/favicon.svg" id="favicon">
<link rel="stylesheet" type="text/css" href="default.css?v=24">
<link rel="stylesheet" type="text/css" href="default.css?v=26">
<script type="text/javascript" src="aux.js?v=24"></script>
<script type="text/javascript" src="math.js?v=24"></script>
<script type="text/javascript" src="tools.js?v=24"></script>
<script type="text/javascript" src="webgl_geometry.js?v=24"></script>
<script type="text/javascript" src="webgl_shaders.js?v=24"></script>
<script type="text/javascript" src="webgl_listeners.js?v=24"></script>
<script type="text/javascript" src="webgl_draw.js?v=24"></script>
<script type="text/javascript" src="index.js?v=24"></script>
<script type="text/javascript" src="aux.js?v=26"></script>
<script type="text/javascript" src="math.js?v=26"></script>
<script type="text/javascript" src="tools.js?v=26"></script>
<script type="text/javascript" src="webgl_geometry.js?v=26"></script>
<script type="text/javascript" src="webgl_shaders.js?v=26"></script>
<script type="text/javascript" src="webgl_listeners.js?v=26"></script>
<script type="text/javascript" src="webgl_draw.js?v=26"></script>
<script type="text/javascript" src="index.js?v=26"></script>
<script type="text/javascript" src="client_send.js?v=24"></script>
<script type="text/javascript" src="client_recv.js?v=24"></script>
<script type="text/javascript" src="websocket.js?v=24"></script>
<script type="text/javascript" src="client_send.js?v=26"></script>
<script type="text/javascript" src="client_recv.js?v=26"></script>
<script type="text/javascript" src="websocket.js?v=26"></script>
</head>
<body>
<div class="main">

6
client/index.js

@ -78,7 +78,9 @@ function main() { @@ -78,7 +78,9 @@ function main() {
'moving': false,
'drawing': false,
'spacedown': false,
'moving_image': null,
'current_strokes': {},
'queue': [],
@ -124,6 +126,8 @@ function main() { @@ -124,6 +126,8 @@ function main() {
'quad_positions_f32': new Float32Array(0),
'quad_texcoords_f32': new Float32Array(0),
'bgcolor': {'r': 1.0, 'g': 1.0, 'b': 1.0},
'active_image': null,
};
const url = new URL(window.location.href);

25
client/webgl_draw.js

@ -41,12 +41,27 @@ function draw(state, context) { @@ -41,12 +41,27 @@ function draw(state, context) {
gl.vertexAttribPointer(locations['a_texcoord'], 2, gl.FLOAT, false, 0, 0);
gl.bufferData(gl.ARRAY_BUFFER, context.quad_texcoords_f32, gl.STATIC_DRAW);
let tex_index = 0;
const count = Object.keys(context.textures).length;
let active_image_index = -1;
for (const key in context.textures) {
gl.bindTexture(gl.TEXTURE_2D, context.textures[key]);
gl.drawArrays(gl.TRIANGLES, tex_index * 6, 6);
++tex_index;
gl.uniform1i(locations['u_layer'], 0);
gl.uniform1i(locations['u_outline'], 0);
for (let key = 0; key < count; ++key) {
if (context.textures[key].image_id === context.active_image) {
active_image_index = key;
continue;
}
gl.bindTexture(gl.TEXTURE_2D, context.textures[key].texture);
gl.drawArrays(gl.TRIANGLES, key * 6, 6);
}
if (active_image_index !== -1) {
gl.uniform1i(locations['u_layer'], 1);
gl.uniform1i(locations['u_outline'], 1);
gl.bindTexture(gl.TEXTURE_2D, context.textures[active_image_index].texture);
gl.drawArrays(gl.TRIANGLES, active_image_index * 6, 6);
}
// Draw strokes

56
client/webgl_geometry.js

@ -126,7 +126,7 @@ function get_static_stroke(state) { @@ -126,7 +126,7 @@ function get_static_stroke(state) {
function add_static_stroke(state, context, stroke, relax = false) {
if (!state.online || !stroke) return;
push_stroke(state, stroke, context.static_positions, context.static_colors);
if (!relax) {
@ -216,15 +216,18 @@ function clear_dynamic_stroke(state, context, player_id) { @@ -216,15 +216,18 @@ function clear_dynamic_stroke(state, context, player_id) {
}
}
function add_image(context, bitmap, p) {
function add_image(context, image_id, bitmap, p) {
const x = p.x;
const y = p.y;
const gl = context.gl;
const id = Object.keys(context.textures).length;
context.textures[id] = gl.createTexture();
context.textures[id] = {
'texture': gl.createTexture(),
'image_id': image_id
};
gl.bindTexture(gl.TEXTURE_2D, context.textures[id]);
gl.bindTexture(gl.TEXTURE_2D, context.textures[id].texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,gl.UNSIGNED_BYTE, bitmap);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
@ -251,4 +254,49 @@ function add_image(context, bitmap, p) { @@ -251,4 +254,49 @@ function add_image(context, bitmap, p) {
context.quad_positions_f32 = new Float32Array(context.quad_positions);
context.quad_texcoords_f32 = new Float32Array(context.quad_texcoords);
}
function move_image(context, image_event) {
const x = image_event.x;
const y = image_event.y;
const count = Object.keys(context.textures).length;
for (let id = 0; id < count; ++id) {
const image = context.textures[id];
if (image.image_id === image_event.image_id) {
context.quad_positions[id * 12 + 0] = x;
context.quad_positions[id * 12 + 1] = y;
context.quad_positions[id * 12 + 2] = x;
context.quad_positions[id * 12 + 3] = y + image_event.height;
context.quad_positions[id * 12 + 4] = x + image_event.width;
context.quad_positions[id * 12 + 5] = y + image_event.height;
context.quad_positions[id * 12 + 6] = x + image_event.width;
context.quad_positions[id * 12 + 7] = y;
context.quad_positions[id * 12 + 8] = x;
context.quad_positions[id * 12 + 9] = y;
context.quad_positions[id * 12 + 10] = x + image_event.width;
context.quad_positions[id * 12 + 11] = y + image_event.height;
context.quad_positions_f32 = new Float32Array(context.quad_positions);
break;
}
}
}
function image_at(state, x, y) {
for (let i = state.events.length - 1; i >= 0; --i) {
const event = state.events[i];
if (event.type === EVENT.IMAGE && !event.deleted) {
if ('height' in event && 'width' in event) {
if (event.x <= x && x <= event.x + event.width && event.y <= y && y <= event.y + event.height) {
return event;
}
}
}
}
return null;
}

82
client/webgl_listeners.js

@ -1,11 +1,13 @@ @@ -1,11 +1,13 @@
function init_listeners(state, context) {
window.addEventListener('keydown', (e) => keydown(e, state, context));
window.addEventListener('keyup', (e) => keyup(e, state, context));
window.addEventListener('paste', (e) => paste(e, state, context));
context.canvas.addEventListener('mousedown', (e) => mousedown(e, state, context));
context.canvas.addEventListener('mousemove', (e) => mousemove(e, state, context));
context.canvas.addEventListener('mouseup', (e) => mouseup(e, state, context));
context.canvas.addEventListener('mouseleave', (e) => mouseup(e, state, context));
context.canvas.addEventListener('contextmenu', cancel);
context.canvas.addEventListener('wheel', (e) => wheel(e, state, context));
context.canvas.addEventListener('touchstart', (e) => touchstart(e, state, context));
@ -27,6 +29,16 @@ function zenmode() { @@ -27,6 +29,16 @@ function zenmode() {
document.querySelector('.sizer-wrapper').classList.toggle('hidden');
}
async function paste(e, state, context) {
const items = (e.clipboardData || e.originalEvent.clipboardData).items;
for (const item of items) {
if (item.kind === 'file') {
const file = item.getAsFile();
await insert_image(state, context, file);
}
}
}
function keydown(e, state, context) {
if (e.code === 'Space' && !state.drawing) {
state.spacedown = true;
@ -46,22 +58,48 @@ function keyup(e, state, context) { @@ -46,22 +58,48 @@ function keyup(e, state, context) {
}
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) {
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) {
state.moving = true;
context.canvas.classList.add('moving');
return;
}
const screenp = {'x': window.devicePixelRatio * e.clientX, 'y': window.devicePixelRatio * e.clientY};
const canvasp = screen_to_canvas(state, screenp);
clear_dynamic_stroke(state, context, state.me);
update_dynamic_stroke(state, context, state.me, canvasp);
state.drawing = true;
context.active_image = null;
schedule_draw(state, context);
}
@ -77,6 +115,13 @@ function mousemove(e, state, context) { @@ -77,6 +115,13 @@ function mousemove(e, state, context) {
do_draw = true;
}
if (state.moving_image) {
state.moving_image.x += e.movementX / state.canvas.zoom;
state.moving_image.y += e.movementY / state.canvas.zoom;
move_image(context, state.moving_image);
do_draw = true;
}
const screenp = {'x': window.devicePixelRatio * e.clientX, 'y': window.devicePixelRatio * e.clientY};
const canvasp = screen_to_canvas(state, screenp);
@ -100,6 +145,13 @@ function mouseup(e, state, context) { @@ -100,6 +145,13 @@ function mouseup(e, state, context) {
return;
}
if (state.moving_image) {
schedule_draw(state, context);
queue_event(state, image_move_event(context.active_image, state.moving_image.x, state.moving_image.y));
state.moving_image = null;
return;
}
if (state.moving) {
state.moving = false;
context.canvas.classList.remove('moving');
@ -354,29 +406,7 @@ async function on_drop(e, state, context) { @@ -354,29 +406,7 @@ async function on_drop(e, state, context) {
}
const file = e.dataTransfer.files[0];
const bitmap = await createImageBitmap(file);
const p = { 'x': state.cursor.x, 'y': state.cursor.y };
const canvasp = screen_to_canvas(state, p);
canvasp.x -= bitmap.width / 2;
canvasp.y -= bitmap.height / 2;
add_image(context, bitmap, canvasp);
const form_data = new FormData();
form_data.append('file', file);
const resp = await fetch(`/api/image?deskId=${state.desk_id}`, {
method: 'post',
body: form_data,
})
if (resp.ok) {
const image_id = await resp.text();
const event = image_event(image_id, canvasp.x, canvasp.y);
await queue_event(state, event);
}
await insert_image(state, context, file);
schedule_draw(state, context);

8
client/webgl_shaders.js

@ -56,9 +56,14 @@ const tquad_fs_src = ` @@ -56,9 +56,14 @@ const tquad_fs_src = `
varying vec2 v_texcoord;
uniform sampler2D u_texture;
uniform bool u_outline;
void main() {
gl_FragColor = texture2D(u_texture, v_texcoord);
if (!u_outline) {
gl_FragColor = texture2D(u_texture, v_texcoord);
} else {
gl_FragColor = mix(texture2D(u_texture, v_texcoord), vec4(0.7, 0.7, 0.95, 1), 0.5);
}
}
`;
@ -100,6 +105,7 @@ function init_webgl(state, context) { @@ -100,6 +105,7 @@ function init_webgl(state, context) {
'u_scale': gl.getUniformLocation(context.programs['quad'], 'u_scale'),
'u_translation': gl.getUniformLocation(context.programs['quad'], 'u_translation'),
'u_layer': gl.getUniformLocation(context.programs['quad'], 'u_layer'),
'u_outline': gl.getUniformLocation(context.programs['quad'], 'u_outline'),
};
context.buffers['stroke'] = {

Loading…
Cancel
Save