Skip to content

Commit dc53c07

Browse files
authored
feat: display project boundary, add panning and a bit of zoom (#36)
1 parent eea3ea2 commit dc53c07

File tree

8 files changed

+220
-42
lines changed

8 files changed

+220
-42
lines changed

integration-tests/template.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
body {
2121
display: grid;
2222
grid-template-columns: 100px minmax(0, 1fr);
23+
overscroll-behavior: none;
2324
}
2425

2526
canvas {

src/WebGPU/pick.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,11 @@ export default class PickManager {
8585
}
8686

8787
createMatrix(canvas: HTMLCanvasElement, canvasMatrix: Float32Array) {
88-
const { clientWidth, clientHeight } = canvas
89-
90-
const tx = -(2 * (pointer.x / clientWidth) - 1)
91-
const ty = 2 * (pointer.y / clientHeight) - 1
88+
const tx = -(2 * (pointer.x / canvas.width) - 1)
89+
const ty = 2 * (pointer.y / canvas.height) - 1
9290

9391
const pickMatrix = [
94-
mat4.scaling([clientWidth, clientHeight, 0]), // scale to 1px convers whole shader output
92+
mat4.scaling([canvas.width, canvas.height, 0]), // scale to 1px convers whole shader output
9593
mat4.translation([tx, ty, 0]),
9694
canvasMatrix,
9795
].reduce(

src/WebGPU/pointer.ts

Lines changed: 113 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,25 @@ import {
44
on_pointer_down,
55
on_pointer_up,
66
} from '../logic/index.zig'
7+
import clamp from '../utils/clamp'
78

89
const OUTSIDE_CANVAS = -1
910

11+
enum CameraMode {
12+
Pan,
13+
Zoom,
14+
None,
15+
}
16+
17+
let cameraMode = CameraMode.None
18+
let panCameraStart: Point | null = null
19+
20+
export const camera = {
21+
x: 0,
22+
y: 0,
23+
zoom: 1,
24+
}
25+
1026
export const pointer = {
1127
x: 0,
1228
y: 0,
@@ -20,19 +36,29 @@ export const pointer = {
2036

2137
export default function initMouseController(
2238
canvas: HTMLCanvasElement,
39+
onZoom: VoidFunction,
2340
onStartProcessing: VoidFunction
2441
) {
2542
pointer.x = OUTSIDE_CANVAS
2643
pointer.y = OUTSIDE_CANVAS
2744

45+
function getZigAbsolutePointer(): [number, number] {
46+
return [
47+
(pointer.x - camera.x) / camera.zoom,
48+
(canvas.height - pointer.y - camera.y) / camera.zoom,
49+
]
50+
}
51+
2852
function updatePointer(e: MouseEvent) {
2953
const rect = canvas.getBoundingClientRect()
30-
pointer.x = e.clientX - rect.left
31-
pointer.y = e.clientY - rect.top
54+
const scale = canvas.width / rect.width
55+
pointer.x = (e.clientX - rect.left) * scale
56+
pointer.y = (e.clientY - rect.top) * scale
3257
}
3358

3459
canvas.addEventListener('mouseleave', () => {
3560
onStartProcessing()
61+
canvas.style.cursor = 'default'
3662

3763
const update = () => {
3864
pointer.x = OUTSIDE_CANVAS
@@ -50,11 +76,19 @@ export default function initMouseController(
5076
})
5177

5278
canvas.addEventListener('mousemove', (e) => {
79+
if (panCameraStart) {
80+
updatePointer(e)
81+
82+
camera.x = pointer.x - panCameraStart.x
83+
camera.y = -(pointer.y - panCameraStart.y)
84+
return
85+
}
86+
5387
onStartProcessing()
5488

5589
const move = () => {
5690
updatePointer(e)
57-
on_pointer_move(pointer.x, canvas.clientHeight - pointer.y)
91+
on_pointer_move(...getZigAbsolutePointer())
5892
}
5993
if (pointer.afterPickEventsQueue.length > 0) {
6094
pointer.afterPickEventsQueue.push({
@@ -67,16 +101,31 @@ export default function initMouseController(
67101
})
68102

69103
canvas.addEventListener('mousedown', (e) => {
104+
if (cameraMode === CameraMode.Pan) {
105+
updatePointer(e)
106+
panCameraStart = {
107+
x: pointer.x - camera.x,
108+
y: pointer.y + camera.y,
109+
}
110+
canvas.style.cursor = 'grabbing'
111+
return
112+
}
113+
panCameraStart = null
114+
70115
onStartProcessing()
71116

72117
updatePointer(e)
73118
pointer.afterPickEventsQueue.push({
74119
requireNewPick: true,
75-
cb: on_pointer_down.bind(null, pointer.x, canvas.clientHeight - pointer.y),
120+
cb: on_pointer_down.bind(null, ...getZigAbsolutePointer()),
76121
})
77122
})
78123

79124
canvas.addEventListener('mouseup', () => {
125+
cameraMode = CameraMode.None
126+
panCameraStart = null
127+
canvas.style.cursor = 'default'
128+
80129
onStartProcessing()
81130

82131
if (pointer.afterPickEventsQueue.length > 0) {
@@ -89,7 +138,66 @@ export default function initMouseController(
89138
}
90139
})
91140

141+
/* panning , supports both scroll and touch, expect Safari */
92142
canvas.addEventListener('wheel', (event) => {
93-
console.log(event.deltaY)
143+
event.preventDefault()
144+
if (cameraMode === CameraMode.Zoom) {
145+
const oldZoom = camera.zoom
146+
camera.zoom = clamp(camera.zoom - event.deltaY * 0.005, 0.1, 20)
147+
onZoom()
148+
149+
const zoomFactor = camera.zoom / oldZoom
150+
151+
camera.x = pointer.x - (pointer.x - camera.x) * zoomFactor
152+
const realY = canvas.height - pointer.y
153+
camera.y = realY - (realY - camera.y) * zoomFactor
154+
} else {
155+
camera.x -= event.deltaX
156+
camera.y += event.deltaY
157+
}
158+
})
159+
// pointer.zoom = clamp(pointer.zoom + event.deltaY * 0.01, 0.1, 100)
160+
161+
document.body.addEventListener('keydown', (event) => {
162+
if (event.code === 'Space') {
163+
event.preventDefault()
164+
if (cameraMode !== CameraMode.Pan) {
165+
canvas.style.cursor = 'grab'
166+
cameraMode = CameraMode.Pan
167+
}
168+
} else if (event.key === 'Alt') {
169+
event.preventDefault()
170+
cameraMode = CameraMode.Zoom
171+
}
172+
})
173+
document.body.addEventListener('keyup', (event) => {
174+
if (event.code === 'Space' || event.key === 'Alt') {
175+
cameraMode = CameraMode.None
176+
}
177+
if (event.code === 'Space' && panCameraStart === null) {
178+
canvas.style.cursor = 'default'
179+
}
180+
})
181+
182+
let lastTouchY: number
183+
184+
canvas.addEventListener('touchstart', (event) => {
185+
if (event.touches.length === 2) {
186+
event.preventDefault()
187+
188+
lastTouchY = event.touches[0].clientY
189+
}
190+
})
191+
192+
canvas.addEventListener('touchmove', (event) => {
193+
if (event.touches.length === 2) {
194+
event.preventDefault()
195+
196+
const delta = lastTouchY - event.touches[0].clientY
197+
lastTouchY = event.touches[0].clientY
198+
199+
camera.zoom = clamp(camera.zoom - delta * 0.01, 0.1, 20)
200+
onZoom()
201+
}
94202
})
95203
}

src/getCanvasMatrix.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import mat4 from 'utils/mat4'
2+
import { camera } from 'WebGPU/pointer'
23

34
export default function getCanvasMatrix(canvas: HTMLCanvasElement) {
4-
const matrix = mat4.ortho(
5+
const ortho = mat4.ortho(
56
0, // left
67
canvas.width, // right
78
0, // bottom
89
canvas.height, // top
910
1, // near
1011
-1 // far
1112
)
13+
// when we implement zoom, it might be actually easier to scale our controls/icons down and this matrix up
14+
// instead of implement zoom for every signle effect I guess
15+
const translated = mat4.translate(ortho, [camera.x, camera.y, 0])
16+
const matrix = mat4.scale(translated, [camera.zoom, camera.zoom, 1])
1217

1318
return matrix
1419
}

src/index.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import {
1111
connect_on_asset_selection_callback,
1212
destroy_state,
1313
import_icons,
14+
update_render_scale,
1415
} from './logic/index.zig'
15-
import initMouseController from 'WebGPU/pointer'
16+
import initMouseController, { camera } from 'WebGPU/pointer'
1617
import IconsPng from '../msdf/output/icons.png'
1718
import IconsJson from '../msdf/output/icons.json'
1819
import getDefaultPoints from 'utils/getDefaultPoints'
@@ -58,7 +59,11 @@ export default async function initCreator(
5859
updateProcessing()
5960
})
6061

61-
init_state(canvas.clientWidth, canvas.clientHeight)
62+
const projectWidth = canvas.clientWidth / 2
63+
const projectHeight = canvas.clientHeight / 2
64+
65+
init_state(projectWidth, projectHeight)
66+
// rotation doesnt work
6267
const context = canvas.getContext('webgpu')
6368
if (!context) throw Error('WebGPU from canvas needs to be always provided')
6469

@@ -70,13 +75,23 @@ export default async function initCreator(
7075
// will copy out of the swapchain texture.
7176
})
7277

78+
function updateRenderScale() {
79+
update_render_scale(canvas.width / (canvas.clientWidth * camera.zoom))
80+
}
81+
82+
let wasInitialOffsetSet = false
7383
canvasSizeObserver(canvas, device, () => {
74-
// state.needsRefresh = true
84+
if (wasInitialOffsetSet === false) {
85+
camera.x = (canvas.width - projectWidth) / 2
86+
camera.y = (canvas.height - projectHeight) / 2
87+
wasInitialOffsetSet = true
88+
}
89+
updateRenderScale()
7590
})
7691

7792
initPrograms(device, presentationFormat)
7893

79-
initMouseController(canvas, () => {
94+
initMouseController(canvas, updateRenderScale, () => {
8095
isMouseEventProcessing = true
8196
updateProcessing()
8297
})
@@ -100,7 +115,7 @@ export default async function initCreator(
100115

101116
const addImage: CreatorAPI['addImage'] = (url) => {
102117
const textureId = Textures.add(url, (width, height) => {
103-
const points = getDefaultPoints(width, height, canvas.clientWidth, canvas.clientHeight)
118+
const points = getDefaultPoints(width, height, projectWidth, projectHeight)
104119
add_asset(0 /* no id yet, needs to be generated */, points, textureId)
105120
})
106121
}

src/logic/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ declare module '*.zig' {
3333
export const on_pointer_up: () => void
3434
export const on_pointer_move: (x: number, y: number) => void
3535
export const on_pointer_leave: VoidFunction
36+
export const update_render_scale: (render_scale: number) => void
3637

3738
export const connect_web_gpu_programs: (programs: {
3839
draw_texture: (vertexData: ZigF32Array, texture_id: number) => void

0 commit comments

Comments
 (0)