Skip to content

Commit 8e7f146

Browse files
committed
chore: editor improvements
1 parent e297c76 commit 8e7f146

15 files changed

Lines changed: 437 additions & 202 deletions

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@sveltejs/kit": "^2.22.0",
2121
"@sveltejs/vite-plugin-svelte": "^6.0.0",
2222
"@tailwindcss/vite": "^4.0.0",
23+
"@types/bun": "^1.2.21",
2324
"eslint": "^9.18.0",
2425
"eslint-config-prettier": "^10.0.1",
2526
"eslint-plugin-svelte": "^3.0.0",
@@ -52,6 +53,7 @@
5253
"codemirror": "^6.0.2",
5354
"deepslate": "^0.24.0",
5455
"es-toolkit": "^1.39.10",
56+
"svelte-splitpanes": "^8.0.9",
5557
"valibot": "^1.1.0"
5658
}
5759
}

src/lib/deepslate/offload/DeepslateRendererOffloaded.svelte

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
type Point
1313
} from './proto';
1414
import { parse } from 'valibot';
15-
import { debounce } from 'es-toolkit';
15+
import { debounce, throttle } from 'es-toolkit';
1616
import { useEditorContext } from '../../editor/MMSEditor.svelte';
1717
import { Icon } from '@steeze-ui/svelte-icon';
1818
import {
@@ -29,11 +29,16 @@
2929
3030
const editor = useEditorContext();
3131
32-
const ds: Action<HTMLCanvasElement> = (canvas) => {
32+
const ds: Action<HTMLCanvasElement> = (canvas: HTMLCanvasElement) => {
3333
const offscreen = canvas.transferControlToOffscreen();
3434
const worker = new DeepslateRenderWorker({ name: 'Deepslate' });
3535
const send = (m: DeepslateRenderWorkerMessage, opts?: Parameters<Worker['postMessage']>[1]) =>
3636
worker.postMessage(parse(DeepslateRenderWorkerMessageSchema, m), opts);
37+
let parent: HTMLElement = canvas;
38+
while (parent !== document.body && !('previewRoot' in parent.dataset)) {
39+
console.log(parent.dataset);
40+
parent = parent.parentElement!;
41+
}
3742
3843
offscreen.height = canvas.clientHeight;
3944
offscreen.width = canvas.clientWidth;
@@ -47,15 +52,16 @@
4752
4853
const resizeObserver = new ResizeObserver(
4954
debounce(() => {
50-
console.log('Resize detected');
55+
const box = parent.getBoundingClientRect();
56+
5157
send({
5258
kind: 'update::canvas_dimensions',
53-
x: canvas.clientWidth,
54-
y: canvas.clientHeight
59+
x: Math.floor(box.width),
60+
y: Math.floor(box.height)
5561
});
56-
}, 250)
62+
}, 200)
5763
);
58-
resizeObserver.observe(canvas);
64+
resizeObserver.observe(parent, { box: 'device-pixel-content-box' });
5965
6066
$effect(() => {
6167
if (editor.previewSymbol) {
@@ -96,14 +102,24 @@
96102
});
97103
});
98104
99-
const fetchValue = debounce(() => {
105+
$effect(() => {
106+
send({
107+
kind: 'update::marker_pos',
108+
pos: mouseLocked ? mousePos : null
109+
});
110+
});
111+
112+
const fetchValue = () => {
100113
send({
101114
kind: 'request::value_at_point',
102115
point: mousePos!
103116
});
104-
}, 25);
117+
};
105118
$effect(() => {
106-
if (mousePos) fetchValue();
119+
// We keep the editor.previewSymbol dependency
120+
// so that the value will refresh when the symbol is modified,
121+
// without needing to unlock the mouse
122+
if (mousePos && editor.previewSymbol) fetchValue();
107123
});
108124
109125
worker.addEventListener('message', (e) => {
@@ -119,12 +135,12 @@
119135
120136
return {
121137
destroy() {
122-
resizeObserver.unobserve(canvas);
138+
resizeObserver.unobserve(parent);
123139
}
124140
};
125141
};
126142
127-
let scale = $state(8);
143+
let scale = $state(64);
128144
let originX = $state(0);
129145
let originY = $state(0);
130146
let worldHeight = $state(256);
@@ -157,10 +173,32 @@
157173
scale
158174
);
159175
};
176+
177+
let prevMouse = $state<{ x: number; y: number } | null>(null);
178+
const drag = throttle((e: MouseEvent | PointerEvent) => {
179+
if (prevMouse) {
180+
const xDiff = (prevMouse?.x ?? 0) - e.pageX;
181+
const yDiff = e.pageY - (prevMouse?.y ?? 0);
182+
originX += Math.round(xDiff / scale);
183+
originY += Math.round(yDiff / scale);
184+
}
185+
prevMouse = { x: e.pageX, y: e.pageY };
186+
}, 20);
187+
188+
const dragStart = () => {
189+
prevMouse = null;
190+
window.addEventListener('mousemove', drag);
191+
window.addEventListener('mouseup', dragEnd);
192+
};
193+
const dragEnd = () => {
194+
window.removeEventListener('mousemove', drag);
195+
window.removeEventListener('mouseup', dragEnd);
196+
prevMouse = null;
197+
};
160198
</script>
161199

162200
{#if editor.previewSymbol}
163-
<div class="relative h-full w-full">
201+
<div class="relative h-full max-h-full w-full overflow-y-hidden" data-preview-root>
164202
{#if mousePos}
165203
<div
166204
class="absolute top-2 right-2 flex items-center gap-2 bg-slate-600/50 px-2 py-1 font-mono text-xs font-bold text-slate-50"
@@ -204,6 +242,9 @@
204242
<Icon src={Minus} class="w-6" />
205243
</button>
206244
</div>
245+
<div class="absolute right-2 bottom-2 bg-slate-600/50 px-1 py-0.5 text-[0.5rem] text-slate-50">
246+
Preview powered by <a href="https://github.com/misode/deepslate" target="_blank">Deepslate</a>
247+
</div>
207248

208249
<div class="absolute bottom-2 left-2 flex flex-col items-start gap-2">
209250
<span class="bg-slate-600/50 px-1 py-0.5 text-sm font-bold text-slate-50">
@@ -248,10 +289,13 @@
248289
</div>
249290

250291
<canvas
251-
class="h-full w-full"
252292
use:ds
253293
onmousemove={updateMousePos}
254294
ondblclick={toggleMouseLock}
255-
></canvas>
295+
onmousedown={dragStart}
296+
class="cursor-grab"
297+
class:cursor-grabbing={prevMouse}
298+
>
299+
</canvas>
256300
</div>
257301
{/if}

src/lib/deepslate/offload/deepslate_render_worker.ts

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import * as deepslate from 'deepslate';
22
import { parse } from 'valibot';
33
import { DeepslateRenderWorkerMessageSchema, type Point } from './proto';
44
import { viridis } from './viridis';
5-
import * as mcdoc from '@spyglassmc/mcdoc';
6-
import { pixelToCoordinate } from './lib';
5+
import { coordinateToPixel, pixelToCoordinate } from './lib';
76

87
declare var self: DedicatedWorkerGlobalScope;
98
export {}; // Make it a module if not already one
@@ -13,16 +12,28 @@ let ctx: OffscreenCanvasRenderingContext2D;
1312
let seed: [bigint, bigint];
1413
let random: deepslate.Random;
1514
let updateFn: (value: object) => void;
16-
let pointValFn: (point: Point) => number;
17-
let pointValLabel: string = '';
15+
16+
let pointValFn: ((point: Point) => number) | undefined;
17+
let memo: Map<[number, number], number> = new Map();
18+
const getVal = (p: Point): number | null => {
19+
if (!pointValFn) return null;
20+
if (memo.has([p.x, p.y])) {
21+
return memo.get([p.x, p.y])!;
22+
}
23+
const res = pointValFn(p);
24+
memo.set([p.x, p.y], res);
25+
return res;
26+
};
1827

1928
let value: object;
29+
let pointValLabel: string = '';
2030

2131
// View Configuration
2232
let scale = 32; // 32 pixels -> 1 block
2333
let origin = { x: 0, y: 0 };
2434
let worldHeight: number;
2535
let minY: number;
36+
let markerPosition: Point | null = null;
2637

2738
const reseed = () => {
2839
seed = [BigInt(Math.floor(performance.now())), BigInt(Math.floor(performance.timeOrigin))] as [
@@ -36,7 +47,7 @@ const reset = () => {
3647
random = new deepslate.XoroshiroRandom(seed);
3748
};
3849

39-
const drawAtCoords = (fn: (p: Point) => number, worldBounded = false) => {
50+
const drawAtCoords = (worldBounded = false) => {
4051
// TODO: We are doing a bit of overdraw here to make sure that we cover the whole canvas -- might want to fix that
4152
const maxY = minY + worldHeight;
4253
const yInBounds = (y: number) => {
@@ -57,7 +68,8 @@ const drawAtCoords = (fn: (p: Point) => number, worldBounded = false) => {
5768
);
5869

5970
if (yInBounds(coord.y)) {
60-
const value = fn(coord);
71+
const value = getVal(coord);
72+
if (value === null) continue;
6173
ctx.fillStyle = viridis(value);
6274
ctx.fillRect(xPixel, yPixel, scale, scale);
6375
continue;
@@ -83,21 +95,6 @@ const drawAtCoords = (fn: (p: Point) => number, worldBounded = false) => {
8395
});
8496
};
8597

86-
const d = {
87-
get width() {
88-
return Math.floor(canvas.width / scale);
89-
},
90-
get height() {
91-
return Math.floor(canvas.height / scale);
92-
},
93-
coordAtPixel(pixel: Point): Point {
94-
return {
95-
x: Math.floor(pixel.x / scale),
96-
y: Math.floor(pixel.y / scale)
97-
};
98-
}
99-
};
100-
10198
const updateContext = (): boolean => {
10299
const c = canvas.getContext('2d');
103100
if (!c) return false;
@@ -120,9 +117,6 @@ const main = (e: MessageEvent) => {
120117
case 'init': {
121118
canvas = message.canvas;
122119
updateContext();
123-
// fetchData().catch(() => {
124-
// console.error('Failed to load mcdoc data');
125-
// });
126120
break;
127121
}
128122
case 'injest::noise': {
@@ -133,6 +127,7 @@ const main = (e: MessageEvent) => {
133127
break;
134128
}
135129
case 'update::preview': {
130+
memo = new Map();
136131
const { type } = message;
137132
if (type in previews) {
138133
updateFn = previews[type as keyof typeof previews];
@@ -159,17 +154,39 @@ const main = (e: MessageEvent) => {
159154
? self.postMessage({
160155
kind: 'response::value_at_point',
161156
point: message.point,
162-
value: pointValFn(message.point) ?? null,
157+
value: getVal(message.point) ?? null,
163158
label: pointValLabel
164159
})
165160
: null;
166161
return;
167162
}
163+
164+
case 'update::marker_pos': {
165+
markerPosition = message.pos;
166+
break;
167+
}
168168
}
169169

170170
if (ctx) {
171171
reset(); // Ensure we end up with a steady random structure unless explicitly reset
172172
updateFn?.(value);
173+
174+
if (markerPosition) {
175+
requestAnimationFrame(() => {
176+
requestAnimationFrame(() => {
177+
if (!markerPosition) return;
178+
ctx.fillStyle = 'rgb(255,0,0)';
179+
const pos = coordinateToPixel(
180+
markerPosition,
181+
origin,
182+
{ w: canvas.width, h: canvas.height },
183+
scale
184+
);
185+
console.log(pos.x % scale, pos.y % scale);
186+
ctx.fillRect(pos.x, pos.y, scale, scale);
187+
});
188+
});
189+
}
173190
}
174191
};
175192

@@ -184,7 +201,7 @@ const previews = {
184201
return res;
185202
};
186203
pointValLabel = 'Value';
187-
drawAtCoords(pointValFn);
204+
drawAtCoords();
188205
},
189206
DensityFunction: (value: object) => {
190207
let fn = deepslate.DensityFunction.fromJson(value);
@@ -197,6 +214,6 @@ const previews = {
197214
return res;
198215
};
199216
pointValLabel = 'Density';
200-
drawAtCoords(pointValFn, true);
217+
drawAtCoords(true);
201218
}
202219
};

src/lib/deepslate/offload/lib.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ export const pixelToCoordinate = (
77
scale: number
88
): Point => {
99
const out = { ...pixel };
10-
debugger
1110
// Translate to coordinate grid from pixel grid
1211
const pX = Math.floor(out.x / scale);
1312
const pY = Math.floor(out.y / scale);
@@ -28,3 +27,27 @@ export const pixelToCoordinate = (
2827

2928
return out;
3029
};
30+
31+
export const coordinateToPixel = (
32+
cood: Point,
33+
offset: Point,
34+
canvasSize: { w: number; h: number },
35+
scale: number
36+
): Point => {
37+
const out = { ...cood };
38+
// Apply offset
39+
out.x += offset.x;
40+
out.y += offset.y;
41+
42+
// Translate to pixel grid from coordinate grid
43+
out.x = out.x * scale;
44+
out.y = canvasSize.h - out.y * scale;
45+
46+
out.x += Math.floor(canvasSize.w / 2);
47+
out.y -= Math.floor(canvasSize.h / 2);
48+
49+
out.x -= out.x % scale;
50+
out.y -= out.y % scale;
51+
52+
return out;
53+
};

src/lib/deepslate/offload/proto.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ export const ResponseValueAtPoint = v.object({
4444
label: v.string()
4545
});
4646

47+
export const UpdateMarkerPosSchema = v.object({
48+
kind: v.literal('update::marker_pos'),
49+
pos: v.nullable(PointSchema)
50+
});
51+
4752
export const InjestNoiseMessageSchema = v.object({
4853
kind: v.literal('injest::noise'),
4954
ref: v.object({ namespace: v.string(), name: v.string() }),
@@ -57,6 +62,7 @@ export const DeepslateRenderWorkerMessageSchema = v.union([
5762
UpdateViewMessageSchema,
5863
InjestNoiseMessageSchema,
5964
RequestValueAtPoint,
60-
ResponseValueAtPoint
65+
ResponseValueAtPoint,
66+
UpdateMarkerPosSchema
6167
]);
6268
export type DeepslateRenderWorkerMessage = v.InferOutput<typeof DeepslateRenderWorkerMessageSchema>;

0 commit comments

Comments
 (0)