Browse Source

Color picker: picks colors! They don't get used though

ssao
A.Olokhtonov 7 months ago
parent
commit
68c892ba5f
  1. 42
      client/bvh.js
  2. 17
      client/default.css
  3. 80
      client/icons/picker.svg
  4. 8
      client/index.html
  5. 1
      client/index.js
  6. 72
      client/math.js
  7. 64
      client/webgl_listeners.js

42
client/bvh.js

@ -216,6 +216,48 @@ function bvh_clip(state, context) { @@ -216,6 +216,48 @@ function bvh_clip(state, context) {
tv_data(context.clipped_indices).sort(); // we need to draw back to front still!
}
function bvh_point(state, p) {
const bvh = state.bvh;
const stack = [];
const indices = [];
if (bvh.root === null) {
return null;
}
stack.push(bvh.root);
while (stack.length > 0) {
const node_index = stack.pop();
const node = bvh.nodes[node_index];
if (!point_in_bbox(p, node.bbox)) {
continue;
}
if (node.is_leaf) {
const stroke = state.events[node.stroke_index];
const xs = state.wasm.buffers['xs'].tv.data.subarray(stroke.coords_from, stroke.coords_to);
const ys = state.wasm.buffers['ys'].tv.data.subarray(stroke.coords_from, stroke.coords_to);
const pressures = state.wasm.buffers['pressures'].tv.data.subarray(stroke.coords_from, stroke.coords_to);
if (point_in_stroke(p, xs, ys, pressures, stroke.width)) {
indices.push(node.stroke_index);
}
} else {
stack.push(node.child1);
stack.push(node.child2);
}
}
if (indices.length > 0) {
indices.sort();
return indices[indices.length - 1];
}
return null;
}
function bvh_construct_rec(bvh, vertical, strokes, depth) {
if (strokes.length > 1) {
// internal

17
client/default.css

@ -43,6 +43,10 @@ canvas { @@ -43,6 +43,10 @@ canvas {
cursor: url('icons/cursor.svg') 7 7, crosshair;
}
canvas.picker {
cursor: url('icons/picker.svg') 0 19, crosshair;
}
canvas.movemode {
cursor: grab;
}
@ -409,3 +413,16 @@ body.offline * { @@ -409,3 +413,16 @@ body.offline * {
background: white;
border: 1px solid var(--dark-blue);
}
.picker-preview-outer {
position: absolute;
top: 16px;
right: 16px;
border: 1px solid black;
}
.picker-preview-inner {
width: 64px;
height: 64px;
border: 1px solid white;
}

80
client/icons/picker.svg

@ -0,0 +1,80 @@ @@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="20"
height="20"
viewBox="0 0 20 20"
version="1.1"
id="svg5"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="picker.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="true"
inkscape:zoom="90.509668"
inkscape:cx="7.1926018"
inkscape:cy="12.827359"
inkscape:window-width="2558"
inkscape:window-height="1396"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="layer1">
<inkscape:grid
type="xygrid"
id="grid686"
originx="-0.050020602"
originy="0.069140606" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-0.0500206,0.06914061)">
<path
id="path11870"
style="fill:none;stroke:#ffffff;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
d="m 16.128281,1.3170014 c -0.65397,0 -1.307679,0.2490845 -1.806641,0.7480468 L 11.534531,4.8541108 10.931015,4.2505951 c -0.333675,-0.3336745 -0.871404,-0.3336745 -1.2050785,0 L 9.4251552,4.5513764 c -0.3336746,0.3336746 -0.3336746,0.8714035 0,1.2050781 l 0.023437,0.6796875 -6,6 -1,2 -1,2 2,2 2,-1 2,-1 6.0000003,-6 0.794922,0.138672 c 0.464866,0.08109 0.871403,0.333675 1.205078,0 l 0.300781,-0.300781 c 0.333675,-0.333675 0.333675,-0.8714039 0,-1.2050785 L 15.147812,8.467392 17.934921,5.6783295 c 0.997925,-0.9979247 0.997925,-2.6153565 0,-3.6132813 C 17.435959,1.566086 16.78225,1.3170014 16.128281,1.3170014 Z"
sodipodi:nodetypes="sccssssccccccccssssccss" />
<path
id="path1222"
style="stroke-width:0;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke fill markers"
d="m 17.934916,5.6788948 c -0.997925,0.9979254 -2.615882,0.9979257 -3.613808,0 -0.997925,-0.9979254 -0.997925,-2.6158828 0,-3.6138082 0.997926,-0.9979257 2.615883,-0.9979254 3.613808,0 0.997926,0.9979257 0.997926,2.6158825 0,3.6138082 z"
sodipodi:nodetypes="sssss" />
<path
id="rect1224"
style="stroke-width:0;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke fill markers"
d="m 14.321108,2.0650866 3.613808,3.6138082 -3.011507,3.011507 -3.613808,-3.6138082 z"
sodipodi:nodetypes="ccccc" />
<path
id="rect3408"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.70357;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;paint-order:stroke fill markers"
d="M 10.966421,6.0523414 13.94766,9.0335811 6.7075065,16.273735 H 5.003941 L 3.7262668,17.551409 2.4485927,16.273735 3.7262668,14.99606 v -1.703565 z"
sodipodi:nodetypes="ccccccccc" />
<path
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.42656;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 5,14 0,1 1,0 3,-3 -1,-1 z"
id="path10295"
sodipodi:nodetypes="cccccc" />
<path
id="rect2678"
style="stroke-width:0;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke fill markers"
d="m 10.931333,4.2502561 4.818411,4.818411 c 0.333675,0.3336749 0.333675,0.8709278 0,1.2046029 l -0.301151,0.301151 c -0.333675,0.333675 -0.870928,0.333675 -1.204603,0 L 9.425579,5.7560096 c -0.3336749,-0.3336749 -0.3336749,-0.8709277 0,-1.2046027 L 9.7267297,4.2502562 c 0.3336753,-0.3336749 0.8709283,-0.333675 1.2046033,-1e-7 z"
sodipodi:nodetypes="sssssssss" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

8
client/index.html

@ -9,6 +9,9 @@ @@ -9,6 +9,9 @@
<link rel="shortcut icon" href="icons/favicon.svg" id="favicon">
<link rel="stylesheet" type="text/css" href="default.css">
<link rel="preload" href="icons/cursor.svg" as="image" type="image/svg+xml" />
<link rel="preload" href="icons/picker.svg" as="image" type="image/svg+xml" />
<script type="text/javascript" src="aux.js"></script>
<script type="text/javascript" src="heapify.js"></script>
<script type="text/javascript" src="bvh.js"></script>
@ -53,6 +56,11 @@ @@ -53,6 +56,11 @@
<button id="debug-begin-benchmark" title="Do not forget to enable recording in your browser!">Benchmark</button>
</div>
<div class="picker-preview-outer dhide">
<div class="picker-preview-inner">
</div>
</div>
<div class="top-wrapper">
<div class="topleft"></div>
<div class="sizer">

1
client/index.js

@ -166,6 +166,7 @@ async function main() { @@ -166,6 +166,7 @@ async function main() {
'moving': false,
'drawing': false,
'spacedown': false,
'colorpicking': false,
'moving_image': null,

72
client/math.js

@ -203,6 +203,22 @@ function color_from_u32(color_u32) { @@ -203,6 +203,22 @@ function color_from_u32(color_u32) {
return '#' + r_str + g_str + b_str;
}
function color_from_rgbdict(color_dict) {
const r = Math.floor(color_dict.r * 255);
const g = Math.floor(color_dict.g * 255);
const b = Math.floor(color_dict.b * 255);
let r_str = r.toString(16);
let g_str = g.toString(16);
let b_str = b.toString(16);
if (r <= 0xF) r_str = '0' + r_str;
if (g <= 0xF) g_str = '0' + g_str;
if (b <= 0xF) b_str = '0' + b_str;
return '#' + r_str + g_str + b_str;
}
function ccw(A, B, C) {
return (C.y - A.y) * (B.x - A.x) > (B.y - A.y) * (C.x - A.x);
}
@ -233,6 +249,62 @@ function point_in_quad(p, quad_topleft, quad_bottomright) { @@ -233,6 +249,62 @@ function point_in_quad(p, quad_topleft, quad_bottomright) {
return false;
}
function point_in_bbox(p, bbox) {
if (bbox.x1 <= p.x && p.x < bbox.x2 && bbox.y1 <= p.y && p.y < bbox.y2) {
return true;
}
return false;
}
function clamp(v, a, b) {
return (v < a ? a : (v > b ? b : v));
}
function dot(a, b) {
return a.x * b.x + a.y * b.y;
}
function mix(a, b, t) {
return a * t + b * (1 - t);
}
function point_in_stroke(p, xs, ys, pressures, width) {
for (let i = 0; i < xs.length - 1; ++i) {
const ax = xs[i + 0];
const bx = xs[i + 1];
const ay = ys[i + 0];
const by = ys[i + 1];
const at = pressures[i + 0] / 255 * width;
const bt = pressures[i + 1] / 255 * width;
const pa = {
'x': p.x - ax,
'y': p.y - ay,
};
const ba = {
'x': bx - ax,
'y': by - ay,
};
const h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
const thickness = mix(at, bt, h);
const v = {
'x': p.x - (ax + ba.x * h),
'y': p.y - (ay + ba.y * h),
};
const dist = Math.sqrt(dot(v, v)) - thickness;
if (dist <= 0) {
return true;
}
}
return false;
}
function segment_interesects_quad(a, b, quad_topleft, quad_bottomright, quad_topright, quad_bottomleft) {
if (point_in_quad(a, quad_topleft, quad_bottomright)) {
return true;

64
client/webgl_listeners.js

@ -7,6 +7,7 @@ function init_listeners(state, context) { @@ -7,6 +7,7 @@ function init_listeners(state, context) {
context.canvas.addEventListener('pointermove', (e) => mousemove(e, state, context));
context.canvas.addEventListener('pointerup', (e) => mouseup(e, state, context));
context.canvas.addEventListener('pointerleave', (e) => mouseup(e, state, context));
context.canvas.addEventListener('pointerleave', (e) => mouseleave(e, state, context));
context.canvas.addEventListener('contextmenu', cancel);
context.canvas.addEventListener('wheel', (e) => wheel(e, state, context));
@ -105,6 +106,22 @@ function zenmode() { @@ -105,6 +106,22 @@ function zenmode() {
document.querySelector('.top-wrapper').classList.toggle('hidden');
}
function enter_picker_mode(state, context) {
document.querySelector('canvas').classList.add('picker');
document.querySelector('.picker-preview-outer').classList.remove('dhide');
state.colorpicking = true;
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');
state.colorpicking = false;
}
async function paste(e, state, context) {
const items = (e.clipboardData || e.originalEvent.clipboardData).items;
for (const item of items) {
@ -122,28 +139,8 @@ function keydown(e, state, context) { @@ -122,28 +139,8 @@ function keydown(e, state, context) {
} else if (e.code === 'Tab') {
e.preventDefault();
zenmode();
} else if (e.code === 'KeyZ') {
const topleft = screen_to_canvas(state, {'x': 0, 'y': 0});
const bottomright = screen_to_canvas(state, {'x': context.canvas.width, 'y': context.canvas.height});
for (let i = 0; i < state.events.length; ++i) {
const event = state.events[i];
if (event.type === EVENT.STROKE) {
let on_screen = false;
for (const p of event.points) {
if (topleft.x <= p.x && p.x <= bottomright.x && topleft.y <= p.y && p.y <= bottomright.y) {
on_screen = true;
break;
}
}
if (on_screen) {
console.log(i);
}
}
}
} else if (e.code === 'ControlLeft' || e.code === 'ControlRight') {
enter_picker_mode(state, context);
} else if (e.code === 'KeyD') {
document.querySelector('.debug-window').classList.toggle('dhide');
}
@ -154,6 +151,8 @@ function keyup(e, state, context) { @@ -154,6 +151,8 @@ function keyup(e, state, context) {
state.spacedown = false;
state.moving = false;
context.canvas.classList.remove('movemode');
} else if (e.code === 'ControlLeft' || e.code === 'ControlRight') {
exit_picker_mode(state);
}
}
@ -216,6 +215,17 @@ function mousedown(e, state, context) { @@ -216,6 +215,17 @@ function mousedown(e, state, context) {
}
}
function update_color_picker_color(state, context, canvasp) {
const stroke_index = bvh_point(state, canvasp);
let color_under_cursor = color_from_rgbdict(context.bgcolor);
if (stroke_index != null) {
color_under_cursor = color_from_u32(state.events[stroke_index].color);
}
document.querySelector('.picker-preview-inner').style.background = color_under_cursor;
}
function mousemove(e, state, context) {
e.preventDefault();
@ -229,6 +239,11 @@ function mousemove(e, state, context) { @@ -229,6 +239,11 @@ function mousemove(e, state, context) {
fire_event(state, movecursor_event(canvasp.x, canvasp.y));
}
if (state.colorpicking) {
update_color_picker_color(state, context, canvasp);
}
if (state.moving) {
state.canvas.offset.x += e.movementX;
state.canvas.offset.y += e.movementY;
@ -327,6 +342,11 @@ function mouseup(e, state, context) { @@ -327,6 +342,11 @@ function mouseup(e, state, context) {
}
}
function mouseleave(e, state, context) {
exit_picker_mode(state);
// something else?
}
function wheel(e, state, context) {
const screenp = {'x': window.devicePixelRatio * e.clientX, 'y': window.devicePixelRatio * e.clientY};
const canvasp = screen_to_canvas(state, screenp);

Loading…
Cancel
Save