@gjsify/example-dom-three-geometry-teapot 0.36.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.js +453 -53
- package/package.json +7 -7
- package/src/browser/browser.ts +5 -18
- package/src/three-demo.ts +9 -27
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/example-dom-three-geometry-teapot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"description": "Three.js geometry teapot example ported from refs/three/examples/webgl_geometry_teapot.html with Adwaita controls",
|
|
5
5
|
"main": "dist/gjs.js",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"build:assets": "gjsify copy src/browser/index.html src/browser/webgl.css src/assets dist/"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@gjsify/webgl": "^0.
|
|
30
|
+
"@gjsify/webgl": "^0.37.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@girs/adw-1": "^4.1.0",
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"@girs/gjs": "^4.1.0",
|
|
36
36
|
"@girs/gobject-2.0": "^4.1.0",
|
|
37
37
|
"@girs/gtk-4.0": "^4.1.0",
|
|
38
|
-
"@gjsify/adwaita-icons": "^0.
|
|
39
|
-
"@gjsify/adwaita-web": "^0.
|
|
40
|
-
"@gjsify/cli": "^0.
|
|
41
|
-
"@gjsify/devtools": "^0.
|
|
38
|
+
"@gjsify/adwaita-icons": "^0.37.0",
|
|
39
|
+
"@gjsify/adwaita-web": "^0.37.0",
|
|
40
|
+
"@gjsify/cli": "^0.37.0",
|
|
41
|
+
"@gjsify/devtools": "^0.37.0",
|
|
42
42
|
"@gjsify/node-gi": "file:../../../packages/node-gi/node-gi",
|
|
43
|
-
"@gjsify/vite-plugin-blueprint": "^0.
|
|
43
|
+
"@gjsify/vite-plugin-blueprint": "^0.37.0",
|
|
44
44
|
"@types/node": "^25.9.2",
|
|
45
45
|
"@types/three": "^0.185.0",
|
|
46
46
|
"http-server": "^14.1.1",
|
package/src/browser/browser.ts
CHANGED
|
@@ -37,43 +37,35 @@ function setButtonIcon(btn: HTMLButtonElement, svgSource: string): void {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
* The container receives an adw-window with sidebar controls and a WebGL canvas.
|
|
40
|
+
* Mounts an adw-window with sidebar controls and a WebGL canvas into `container`.
|
|
42
41
|
*/
|
|
43
42
|
export function mount(container: HTMLElement, options?: MountOptions): ShowcaseHandle {
|
|
44
|
-
// Build UI — mirrors GJS Blueprint structure
|
|
45
43
|
const win = document.createElement('adw-window');
|
|
46
44
|
win.setAttribute('width', '1100');
|
|
47
45
|
win.setAttribute('height', '700');
|
|
48
46
|
|
|
49
|
-
// Header bar (toggle button added after DOM connection below)
|
|
50
47
|
const headerBar = document.createElement('adw-header-bar') as AdwHeaderBar;
|
|
51
48
|
headerBar.setAttribute('title', 'Three.js Teapot');
|
|
52
49
|
|
|
53
|
-
// Sidebar toggle button
|
|
54
50
|
const toggleBtn = document.createElement('button');
|
|
55
51
|
toggleBtn.className = 'adw-header-btn adw-sidebar-toggle-icon active';
|
|
56
52
|
toggleBtn.title = 'Toggle Sidebar';
|
|
57
53
|
|
|
58
|
-
// Pause/Resume rendering button — placed in header bar end section.
|
|
59
54
|
const pauseBtn = document.createElement('button');
|
|
60
55
|
pauseBtn.className = 'adw-header-btn';
|
|
61
56
|
pauseBtn.title = 'Pause Rendering';
|
|
62
57
|
setButtonIcon(pauseBtn, mediaPlaybackPauseSymbolic);
|
|
63
58
|
|
|
64
|
-
// OverlaySplitView — sidebar + content
|
|
65
59
|
const splitView = document.createElement('adw-overlay-split-view') as AdwOverlaySplitView;
|
|
66
60
|
splitView.setAttribute('min-sidebar-width', '280');
|
|
67
61
|
splitView.setAttribute('max-sidebar-width', '400');
|
|
68
62
|
splitView.setAttribute('sidebar-width-fraction', '0.30');
|
|
69
63
|
splitView.setAttribute('show-sidebar', '');
|
|
70
64
|
|
|
71
|
-
// Sidebar content
|
|
72
65
|
const sidebarContent = document.createElement('div');
|
|
73
66
|
sidebarContent.setAttribute('slot', 'sidebar');
|
|
74
67
|
sidebarContent.className = 'adw-sidebar-content';
|
|
75
68
|
|
|
76
|
-
// Geometry group
|
|
77
69
|
const geoGroup = document.createElement('adw-preferences-group');
|
|
78
70
|
geoGroup.setAttribute('title', 'Geometry');
|
|
79
71
|
|
|
@@ -102,7 +94,6 @@ export function mount(container: HTMLElement, options?: MountOptions): ShowcaseH
|
|
|
102
94
|
|
|
103
95
|
geoGroup.append(tessRow, lidRow, bodyRow, bottomRow, fitLidRow, nonblinnRow);
|
|
104
96
|
|
|
105
|
-
// Material group
|
|
106
97
|
const matGroup = document.createElement('adw-preferences-group');
|
|
107
98
|
matGroup.setAttribute('title', 'Material');
|
|
108
99
|
|
|
@@ -115,8 +106,7 @@ export function mount(container: HTMLElement, options?: MountOptions): ShowcaseH
|
|
|
115
106
|
|
|
116
107
|
sidebarContent.append(geoGroup, matGroup);
|
|
117
108
|
|
|
118
|
-
//
|
|
119
|
-
// is self-contained and works regardless of host CSS.
|
|
109
|
+
// Inline styles so the layout holds in the website embed too, which loads no showcase CSS.
|
|
120
110
|
const glContainer = document.createElement('div');
|
|
121
111
|
glContainer.setAttribute('slot', 'content');
|
|
122
112
|
glContainer.id = 'gl-area-container';
|
|
@@ -131,7 +121,7 @@ export function mount(container: HTMLElement, options?: MountOptions): ShowcaseH
|
|
|
131
121
|
win.append(headerBar, splitView);
|
|
132
122
|
container.append(win);
|
|
133
123
|
|
|
134
|
-
//
|
|
124
|
+
// AFTER DOM connection: connectedCallback is what creates the header-bar-start wrapper.
|
|
135
125
|
const startSection = headerBar.startSection ?? headerBar.querySelector('.adw-header-bar-start');
|
|
136
126
|
if (startSection) {
|
|
137
127
|
startSection.appendChild(toggleBtn);
|
|
@@ -146,7 +136,6 @@ export function mount(container: HTMLElement, options?: MountOptions): ShowcaseH
|
|
|
146
136
|
headerBar.append(pauseBtn);
|
|
147
137
|
}
|
|
148
138
|
|
|
149
|
-
// Sidebar toggle wiring
|
|
150
139
|
toggleBtn.addEventListener('click', () => {
|
|
151
140
|
splitView.toggleSidebar();
|
|
152
141
|
toggleBtn.classList.toggle('active', splitView.showSidebar);
|
|
@@ -156,7 +145,6 @@ export function mount(container: HTMLElement, options?: MountOptions): ShowcaseH
|
|
|
156
145
|
toggleBtn.classList.toggle('active', splitView.showSidebar);
|
|
157
146
|
});
|
|
158
147
|
|
|
159
|
-
// Responsive breakpoints — mirror GJS Adw.Breakpoint behavior
|
|
160
148
|
let lastCollapsed: boolean | null = null;
|
|
161
149
|
new ResizeObserver(([entry]) => {
|
|
162
150
|
const width = entry.contentRect.width;
|
|
@@ -168,8 +156,8 @@ export function mount(container: HTMLElement, options?: MountOptions): ShowcaseH
|
|
|
168
156
|
toggleBtn.classList.toggle('active', !shouldCollapse);
|
|
169
157
|
}).observe(win);
|
|
170
158
|
|
|
171
|
-
//
|
|
172
|
-
//
|
|
159
|
+
// Also covers a slide becoming visible after `display: none`, which reports a size for the first
|
|
160
|
+
// time.
|
|
173
161
|
canvas.width = glContainer.clientWidth;
|
|
174
162
|
canvas.height = glContainer.clientHeight;
|
|
175
163
|
|
|
@@ -204,7 +192,6 @@ export function mount(container: HTMLElement, options?: MountOptions): ShowcaseH
|
|
|
204
192
|
}
|
|
205
193
|
}).observe(glContainer);
|
|
206
194
|
|
|
207
|
-
// Pause button wiring — toggles demo state and swaps the icon.
|
|
208
195
|
function updatePauseButton(paused: boolean): void {
|
|
209
196
|
setButtonIcon(pauseBtn, paused ? mediaPlaybackStartSymbolic : mediaPlaybackPauseSymbolic);
|
|
210
197
|
pauseBtn.title = paused ? 'Resume Rendering' : 'Pause Rendering';
|
package/src/three-demo.ts
CHANGED
|
@@ -32,18 +32,15 @@ export interface TeapotEffectController {
|
|
|
32
32
|
|
|
33
33
|
export interface TeapotDemo {
|
|
34
34
|
readonly effectController: TeapotEffectController;
|
|
35
|
-
/** Call after
|
|
35
|
+
/** Call after mutating effectController to schedule a re-render. */
|
|
36
36
|
render(): void;
|
|
37
|
-
/**
|
|
37
|
+
/** Suppresses render() until `resume()`, which then flushes anything queued meanwhile. */
|
|
38
38
|
pause(): void;
|
|
39
|
-
/** Flush any pending render queued during pause. */
|
|
40
39
|
resume(): void;
|
|
41
|
-
/** Current paused state. */
|
|
42
40
|
readonly isPaused: boolean;
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
export interface StartOptions {
|
|
46
|
-
/** Base path for loading texture assets (default: './'). */
|
|
47
44
|
assetBase?: string;
|
|
48
45
|
}
|
|
49
46
|
|
|
@@ -52,29 +49,23 @@ const TEAPOT_SIZE = 300;
|
|
|
52
49
|
export function start(canvas: HTMLCanvasElement, options?: StartOptions): TeapotDemo {
|
|
53
50
|
const assetBase = options?.assetBase ?? './';
|
|
54
51
|
|
|
55
|
-
// Renderer
|
|
56
52
|
// oxlint-disable-next-line typescript/no-explicit-any -- THREE.WebGLRenderer canvas option expects OffscreenCanvas; GJS canvas type is incompatible
|
|
57
53
|
const renderer = new THREE.WebGLRenderer({ canvas: canvas as any, antialias: true });
|
|
58
54
|
// Pass updateStyle=false: the canvas CSS width/height are managed by the
|
|
59
55
|
// host container (flex layout). We only want to update the drawing buffer.
|
|
60
56
|
renderer.setSize(canvas.width, canvas.height, false);
|
|
61
|
-
//
|
|
62
|
-
// canvas
|
|
63
|
-
//
|
|
64
|
-
// the camera aspect would stay frozen at the first allocation. (The pixel
|
|
65
|
-
// post-processing showcase already carries this shape; keep the two aligned.)
|
|
57
|
+
// The last size handed to the renderer, tracked separately because `renderer.domElement` IS this
|
|
58
|
+
// canvas: the obvious `renderer.domElement.width !== w` compares a value with itself, leaving the
|
|
59
|
+
// resize branch dead and the camera aspect frozen at the first allocation.
|
|
66
60
|
let prevW = canvas.width;
|
|
67
61
|
let prevH = canvas.height;
|
|
68
62
|
|
|
69
|
-
// Camera
|
|
70
63
|
const camera = new THREE.PerspectiveCamera(45, canvas.width / canvas.height, 1, 80000);
|
|
71
64
|
camera.position.set(-600, 550, 1300);
|
|
72
65
|
|
|
73
|
-
// Scene
|
|
74
66
|
const scene = new THREE.Scene();
|
|
75
67
|
scene.background = new THREE.Color(0xaaaaaa);
|
|
76
68
|
|
|
77
|
-
// Lights
|
|
78
69
|
const ambientLight = new THREE.AmbientLight(0x7c7c7c, 2.0);
|
|
79
70
|
scene.add(ambientLight);
|
|
80
71
|
|
|
@@ -82,18 +73,15 @@ export function start(canvas: HTMLCanvasElement, options?: StartOptions): Teapot
|
|
|
82
73
|
light.position.set(0.32, 0.39, 0.7);
|
|
83
74
|
scene.add(light);
|
|
84
75
|
|
|
85
|
-
// Texture map
|
|
86
76
|
const textureMap = new THREE.TextureLoader().load(`${assetBase}assets/uv_grid_opengl.jpg`);
|
|
87
77
|
textureMap.wrapS = textureMap.wrapT = THREE.RepeatWrapping;
|
|
88
78
|
textureMap.anisotropy = 16;
|
|
89
79
|
textureMap.colorSpace = THREE.SRGBColorSpace;
|
|
90
80
|
|
|
91
|
-
// Reflection map (cubemap)
|
|
92
81
|
const path = `${assetBase}assets/pisa/`;
|
|
93
82
|
const urls = ['px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png'];
|
|
94
83
|
const textureCube = new THREE.CubeTextureLoader().setPath(path).load(urls);
|
|
95
84
|
|
|
96
|
-
// Materials
|
|
97
85
|
const materials: Record<ShadingMode, THREE.Material> = {
|
|
98
86
|
wireframe: new THREE.MeshBasicMaterial({ wireframe: true }),
|
|
99
87
|
flat: new THREE.MeshPhongMaterial({ specular: 0x000000, flatShading: true, side: THREE.DoubleSide }),
|
|
@@ -108,12 +96,11 @@ export function start(canvas: HTMLCanvasElement, options?: StartOptions): Teapot
|
|
|
108
96
|
reflective: new THREE.MeshPhongMaterial({ envMap: textureCube, side: THREE.DoubleSide }),
|
|
109
97
|
};
|
|
110
98
|
|
|
111
|
-
// OrbitControls
|
|
112
99
|
// oxlint-disable-next-line typescript/no-explicit-any -- OrbitControls domElement type is HTMLElement; GJS canvas type is incompatible
|
|
113
100
|
const cameraControls = new OrbitControls(camera, canvas as any);
|
|
114
101
|
cameraControls.addEventListener('change', render);
|
|
115
102
|
|
|
116
|
-
//
|
|
103
|
+
// An external GUI mutates this, then calls render().
|
|
117
104
|
const effectController: TeapotEffectController = {
|
|
118
105
|
newTess: 15,
|
|
119
106
|
lid: true,
|
|
@@ -124,7 +111,6 @@ export function start(canvas: HTMLCanvasElement, options?: StartOptions): Teapot
|
|
|
124
111
|
newShading: 'glossy',
|
|
125
112
|
};
|
|
126
113
|
|
|
127
|
-
// Track last-rendered state for change detection
|
|
128
114
|
let tess = -1; // force initial creation
|
|
129
115
|
let bBottom: boolean;
|
|
130
116
|
let bLid: boolean;
|
|
@@ -155,7 +141,6 @@ export function start(canvas: HTMLCanvasElement, options?: StartOptions): Teapot
|
|
|
155
141
|
}
|
|
156
142
|
|
|
157
143
|
function doRender() {
|
|
158
|
-
// Check if parameters changed
|
|
159
144
|
if (
|
|
160
145
|
effectController.newTess !== tess ||
|
|
161
146
|
effectController.bottom !== bBottom ||
|
|
@@ -176,14 +161,13 @@ export function start(canvas: HTMLCanvasElement, options?: StartOptions): Teapot
|
|
|
176
161
|
createNewTeapot();
|
|
177
162
|
}
|
|
178
163
|
|
|
179
|
-
// Reflective mode uses cubemap as background
|
|
180
164
|
if (shading === 'reflective') {
|
|
181
165
|
scene.background = textureCube;
|
|
182
166
|
} else {
|
|
183
167
|
scene.background = null;
|
|
184
168
|
}
|
|
185
169
|
|
|
186
|
-
//
|
|
170
|
+
// canvas.width/height are the host-owned drawing buffer.
|
|
187
171
|
const w = canvas.width;
|
|
188
172
|
const h = canvas.height;
|
|
189
173
|
if (prevW !== w || prevH !== h) {
|
|
@@ -197,9 +181,8 @@ export function start(canvas: HTMLCanvasElement, options?: StartOptions): Teapot
|
|
|
197
181
|
renderer.render(scene, camera);
|
|
198
182
|
}
|
|
199
183
|
|
|
200
|
-
// Render
|
|
201
|
-
//
|
|
202
|
-
// so direct calls from signal handlers would fail.
|
|
184
|
+
// Render requests coalesce into one frame, and must go through rAF: under GTK the GL context is
|
|
185
|
+
// only current inside the frame pipeline, so a direct call from a signal handler would fail.
|
|
203
186
|
let renderPending = false;
|
|
204
187
|
let paused = false;
|
|
205
188
|
// Set when render() is called while paused, so resume() can flush it.
|
|
@@ -217,7 +200,6 @@ export function start(canvas: HTMLCanvasElement, options?: StartOptions): Teapot
|
|
|
217
200
|
});
|
|
218
201
|
}
|
|
219
202
|
|
|
220
|
-
// Initial render
|
|
221
203
|
render();
|
|
222
204
|
|
|
223
205
|
return {
|