@gjsify/example-dom-three-geometry-teapot 0.35.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/example-dom-three-geometry-teapot",
3
- "version": "0.35.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.35.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.35.0",
39
- "@gjsify/adwaita-web": "^0.35.0",
40
- "@gjsify/cli": "^0.35.0",
41
- "@gjsify/devtools": "^0.35.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.35.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",
@@ -37,43 +37,35 @@ function setButtonIcon(btn: HTMLButtonElement, svgSource: string): void {
37
37
  }
38
38
 
39
39
  /**
40
- * Create the Adwaita teapot UI and mount it into the given container.
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
- // GL container (content slot) — inline styles so the showcase
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
- // Append toggle button to header bar start section AFTER DOM connection
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
- // Sync canvas size with container and re-render on resize
172
- // (needed when slide becomes visible after being display:none)
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 changing effectController properties to schedule a re-render. */
35
+ /** Call after mutating effectController to schedule a re-render. */
36
36
  render(): void;
37
- /** Suppress future render() calls until `resume()` is called. */
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
- // Track the size we last handed the renderer. `renderer.domElement` IS this
62
- // canvas, so the obvious `renderer.domElement.width !== w` compares a value
63
- // with itself and is never true — the resize branch below would be dead and
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
- // Effect controller — external GUI mutates this, then calls render()
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
- // Handle resize — canvas.width/height are the GTK-owned drawing buffer
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 scheduling: coalesce multiple render requests into one frame.
201
- // In GTK, the GL context is only current inside the frame pipeline (rAF),
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 {