@gjsify/example-dom-three-geometry-teapot 0.55.0 → 0.56.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/index.html CHANGED
@@ -4,7 +4,6 @@
4
4
  <meta charset="utf-8" />
5
5
  <title>Three.js Teapot — gjsify</title>
6
6
  <link rel="stylesheet" href="./webgl.css" type="text/css" />
7
- <link rel="stylesheet" href="./browser.css" type="text/css" />
8
7
  <script src="./browser.js" type="module" defer></script>
9
8
  </head>
10
9
  <body></body>
package/dist/webgl.css CHANGED
@@ -14,7 +14,7 @@ html, body {
14
14
  justify-content: center;
15
15
  }
16
16
 
17
- adw-window {
17
+ adw-application-window {
18
18
  max-width: 100%;
19
19
  max-height: 100%;
20
20
  }
@@ -38,13 +38,13 @@ adw-window {
38
38
  gap: 12px;
39
39
  }
40
40
 
41
- #gl-area-container {
41
+ #glAreaContainer {
42
42
  flex: 1;
43
43
  position: relative;
44
44
  min-width: 0;
45
45
  }
46
46
 
47
- #gl-area-container canvas {
47
+ #glAreaContainer canvas {
48
48
  display: block;
49
49
  width: 100%;
50
50
  height: 100%;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/example-dom-three-geometry-teapot",
3
- "version": "0.55.0",
3
+ "version": "0.56.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.55.0"
30
+ "@gjsify/webgl": "^0.56.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@girs/adw-1": "5.4.0",
@@ -35,12 +35,12 @@
35
35
  "@girs/gjs": "5.4.0",
36
36
  "@girs/gobject-2.0": "5.4.0",
37
37
  "@girs/gtk-4.0": "5.4.0",
38
- "@gjsify/adwaita-icons": "^0.55.0",
39
- "@gjsify/adwaita-web": "^0.55.0",
40
- "@gjsify/cli": "^0.55.0",
41
- "@gjsify/devtools": "^0.55.0",
38
+ "@gjsify/adwaita-icons": "^0.56.0",
39
+ "@gjsify/adwaita-web": "^0.56.0",
40
+ "@gjsify/cli": "^0.56.0",
41
+ "@gjsify/devtools": "^0.56.0",
42
42
  "@gjsify/node-gi": "file:../../../packages/node-gi/node-gi",
43
- "@gjsify/vite-plugin-blueprint": "^0.55.0",
43
+ "@gjsify/vite-plugin-blueprint": "^0.56.0",
44
44
  "@types/node": "^25.9.2",
45
45
  "@types/three": "^0.185.0",
46
46
  "http-server": "^14.1.1",
@@ -1,15 +1,16 @@
1
1
  // Browser UI for three-geometry-teapot example.
2
- // Mirrors the GJS/Adwaita UI from gjs/teapot-window.ts using @gjsify/adwaita-web.
2
+ // The widget tree is the GJS window's own `teapot-window.blp`, projected and mounted by
3
+ // @gjsify/adwaita-web — one authored file, so the sidebar toggle, the `bind`s between the toggle
4
+ // and the split view and the two `[breakpoint]`s cannot drift from the GTK build.
3
5
 
4
- import '@gjsify/adwaita-web'; // registers the custom elements + self-injects the stylesheet
6
+ import { mountSharedTree } from '@gjsify/adwaita-web'; // also registers the custom elements + self-injects the stylesheet
5
7
  // A showcase is served to whatever browser opens it, so it cannot assume the host has
6
8
  // Adwaita Sans the way a GNOME desktop does. `import '@gjsify/adwaita-web'` names the
7
9
  // family and ships no `@font-face`, so without this call the chrome renders in the host's
8
10
  // default sans on macOS, on Windows and on any Linux that is not GNOME — and looks right
9
11
  // only on the machine it was written on.
10
12
  import { applyAdwaitaFonts } from '@gjsify/adwaita-web/fonts';
11
- import type { Adw } from '@gjsify/adwaita-web';
12
- import { mediaPlaybackPauseSymbolic, mediaPlaybackStartSymbolic } from '@gjsify/adwaita-icons/actions';
13
+ import tree from '../gjs/teapot-window.blp?shared-tree';
13
14
  import {
14
15
  start,
15
16
  TESS_VALUES,
@@ -35,91 +36,42 @@ export interface ShowcaseHandle {
35
36
  readonly isPaused: boolean;
36
37
  }
37
38
 
38
- /** Parse a trusted literal SVG string into an SVGElement. */
39
- function parseSvg(svgSource: string): SVGElement {
40
- const doc = new DOMParser().parseFromString(svgSource, 'image/svg+xml');
41
- return doc.documentElement as unknown as SVGElement;
42
- }
43
-
44
- /** Replace a button's icon with a freshly-parsed copy of the given SVG source. */
45
- function setButtonIcon(btn: HTMLButtonElement, svgSource: string): void {
46
- btn.replaceChildren(parseSvg(svgSource));
39
+ /** The one element of the mounted tree with this authored id. */
40
+ function byId(root: HTMLElement, id: string): HTMLElement {
41
+ const el = root.querySelector<HTMLElement>(`#${id}`);
42
+ if (el === null) throw new Error(`teapot-window.blp declares no object with the id "${id}"`);
43
+ return el;
47
44
  }
48
45
 
49
46
  /**
50
- * Mounts an adw-window with sidebar controls and a WebGL canvas into `container`.
47
+ * Mounts the teapot window's shared tree into `container`.
48
+ *
49
+ * The two combo rows' models are not in the tree: on GTK they are `Gtk.StringList`s the window's
50
+ * TypeScript builds (`teapot-window.ts`), and Blueprint has no spelling for a list model here. The
51
+ * browser reads the same `TESS_VALUES` / `SHADING_VALUES` / `DEFAULT_*_INDEX` from `three-demo.ts`
52
+ * and writes them onto the rows, so the lists themselves have one source; only the wiring is copied.
51
53
  */
52
54
  export function mount(container: HTMLElement, options?: MountOptions): ShowcaseHandle {
53
- const win = document.createElement('adw-window');
54
- win.setAttribute('width', '1100');
55
- win.setAttribute('height', '700');
56
-
57
- const headerBar = document.createElement('adw-header-bar') as Adw.HeaderBar;
58
- headerBar.setAttribute('title', 'Three.js Teapot');
59
-
60
- const toggleBtn = document.createElement('button');
61
- toggleBtn.className = 'adw-header-btn adw-sidebar-toggle-icon active';
62
- toggleBtn.title = 'Toggle Sidebar';
63
-
64
- const pauseBtn = document.createElement('button');
65
- pauseBtn.className = 'adw-header-btn';
66
- pauseBtn.title = 'Pause Rendering';
67
- setButtonIcon(pauseBtn, mediaPlaybackPauseSymbolic);
68
-
69
- const splitView = document.createElement('adw-overlay-split-view') as Adw.OverlaySplitView;
70
- splitView.setAttribute('min-sidebar-width', '280');
71
- splitView.setAttribute('max-sidebar-width', '400');
72
- splitView.setAttribute('sidebar-width-fraction', '0.30');
73
- splitView.setAttribute('show-sidebar', '');
74
-
75
- const sidebarContent = document.createElement('div');
76
- sidebarContent.setAttribute('slot', 'sidebar');
77
- sidebarContent.className = 'adw-sidebar-content';
78
-
79
- const geoGroup = document.createElement('adw-preferences-group');
80
- geoGroup.setAttribute('title', 'Geometry');
81
-
82
- const tessRow = document.createElement('adw-combo-row');
83
- tessRow.setAttribute('title', 'Tessellation Level');
84
- tessRow.setAttribute('items', JSON.stringify(TESS_VALUES.map(String)));
85
- tessRow.setAttribute('selected', String(DEFAULT_TESS_INDEX));
86
-
87
- const lidRow = document.createElement('adw-switch-row');
88
- lidRow.setAttribute('title', 'Display Lid');
89
- lidRow.setAttribute('active', '');
90
-
91
- const bodyRow = document.createElement('adw-switch-row');
92
- bodyRow.setAttribute('title', 'Display Body');
93
- bodyRow.setAttribute('active', '');
94
-
95
- const bottomRow = document.createElement('adw-switch-row');
96
- bottomRow.setAttribute('title', 'Display Bottom');
97
- bottomRow.setAttribute('active', '');
98
-
99
- const fitLidRow = document.createElement('adw-switch-row');
100
- fitLidRow.setAttribute('title', 'Snug Lid');
101
-
102
- const nonblinnRow = document.createElement('adw-switch-row');
103
- nonblinnRow.setAttribute('title', 'Original Scale');
104
-
105
- geoGroup.append(tessRow, lidRow, bodyRow, bottomRow, fitLidRow, nonblinnRow);
106
-
107
- const matGroup = document.createElement('adw-preferences-group');
108
- matGroup.setAttribute('title', 'Material');
109
-
110
- const shadingRow = document.createElement('adw-combo-row');
111
- shadingRow.setAttribute('title', 'Shading');
112
- shadingRow.setAttribute('items', JSON.stringify([...SHADING_VALUES]));
113
- shadingRow.setAttribute('selected', String(DEFAULT_SHADING_INDEX));
114
-
115
- matGroup.append(shadingRow);
116
-
117
- sidebarContent.append(geoGroup, matGroup);
55
+ const { root: win } = mountSharedTree(tree, { into: container });
56
+
57
+ const tessRow = byId(win, 'tessRow');
58
+ const shadingRow = byId(win, 'shadingRow');
59
+ for (const [row, values, selected] of [
60
+ [tessRow, TESS_VALUES.map(String), DEFAULT_TESS_INDEX],
61
+ [shadingRow, [...SHADING_VALUES], DEFAULT_SHADING_INDEX],
62
+ ] as const) {
63
+ row.setAttribute('model', JSON.stringify(values));
64
+ row.setAttribute('selected', String(selected));
65
+ }
66
+ const lidRow = byId(win, 'lidRow');
67
+ const bodyRow = byId(win, 'bodyRow');
68
+ const bottomRow = byId(win, 'bottomRow');
69
+ const fitLidRow = byId(win, 'fitLidRow');
70
+ const nonblinnRow = byId(win, 'nonblinnRow');
71
+ const pauseBtn = byId(win, 'pauseButton');
118
72
 
119
73
  // Inline styles so the layout holds in the website embed too, which loads no showcase CSS.
120
- const glContainer = document.createElement('div');
121
- glContainer.setAttribute('slot', 'content');
122
- glContainer.id = 'gl-area-container';
74
+ const glContainer = byId(win, 'glAreaContainer');
123
75
  glContainer.style.cssText = 'flex:1;position:relative;min-width:0;min-height:0';
124
76
 
125
77
  const canvas = document.createElement('canvas');
@@ -127,45 +79,6 @@ export function mount(container: HTMLElement, options?: MountOptions): ShowcaseH
127
79
  canvas.style.cssText = 'display:block;width:100%;height:100%;position:absolute;inset:0';
128
80
  glContainer.append(canvas);
129
81
 
130
- splitView.append(sidebarContent, glContainer);
131
- win.append(headerBar, splitView);
132
- container.append(win);
133
-
134
- // AFTER DOM connection: connectedCallback is what creates the header-bar-start wrapper.
135
- const startSection = headerBar.startSection ?? headerBar.querySelector('.adw-header-bar-start');
136
- if (startSection) {
137
- startSection.appendChild(toggleBtn);
138
- } else {
139
- headerBar.prepend(toggleBtn);
140
- }
141
-
142
- const endSection = headerBar.endSection ?? headerBar.querySelector('.adw-header-bar-end');
143
- if (endSection) {
144
- endSection.appendChild(pauseBtn);
145
- } else {
146
- headerBar.append(pauseBtn);
147
- }
148
-
149
- toggleBtn.addEventListener('click', () => {
150
- splitView.toggleSidebar();
151
- toggleBtn.classList.toggle('active', splitView.showSidebar);
152
- });
153
-
154
- splitView.addEventListener('sidebar-toggled', () => {
155
- toggleBtn.classList.toggle('active', splitView.showSidebar);
156
- });
157
-
158
- let lastCollapsed: boolean | null = null;
159
- new ResizeObserver(([entry]) => {
160
- const width = entry.contentRect.width;
161
- const shouldCollapse = width < 800;
162
- if (shouldCollapse === lastCollapsed) return;
163
- lastCollapsed = shouldCollapse;
164
- splitView.collapsed = shouldCollapse;
165
- splitView.showSidebar = !shouldCollapse;
166
- toggleBtn.classList.toggle('active', !shouldCollapse);
167
- }).observe(win);
168
-
169
82
  // Also covers a slide becoming visible after `display: none`, which reports a size for the first
170
83
  // time.
171
84
  canvas.width = glContainer.clientWidth;
@@ -203,8 +116,8 @@ export function mount(container: HTMLElement, options?: MountOptions): ShowcaseH
203
116
  }).observe(glContainer);
204
117
 
205
118
  function updatePauseButton(paused: boolean): void {
206
- setButtonIcon(pauseBtn, paused ? mediaPlaybackStartSymbolic : mediaPlaybackPauseSymbolic);
207
- pauseBtn.title = paused ? 'Resume Rendering' : 'Pause Rendering';
119
+ pauseBtn.setAttribute('icon-name', paused ? 'media-playback-start-symbolic' : 'media-playback-pause-symbolic');
120
+ pauseBtn.setAttribute('tooltip-text', paused ? 'Resume Rendering' : 'Pause Rendering');
208
121
  }
209
122
  pauseBtn.addEventListener('click', () => {
210
123
  if (demo) {
@@ -4,7 +4,6 @@
4
4
  <meta charset="utf-8" />
5
5
  <title>Three.js Teapot — gjsify</title>
6
6
  <link rel="stylesheet" href="./webgl.css" type="text/css" />
7
- <link rel="stylesheet" href="./browser.css" type="text/css" />
8
7
  <script src="./browser.js" type="module" defer></script>
9
8
  </head>
10
9
  <body></body>
@@ -14,7 +14,7 @@ html, body {
14
14
  justify-content: center;
15
15
  }
16
16
 
17
- adw-window {
17
+ adw-application-window {
18
18
  max-width: 100%;
19
19
  max-height: 100%;
20
20
  }
@@ -38,13 +38,13 @@ adw-window {
38
38
  gap: 12px;
39
39
  }
40
40
 
41
- #gl-area-container {
41
+ #glAreaContainer {
42
42
  flex: 1;
43
43
  position: relative;
44
44
  min-width: 0;
45
45
  }
46
46
 
47
- #gl-area-container canvas {
47
+ #glAreaContainer canvas {
48
48
  display: block;
49
49
  width: 100%;
50
50
  height: 100%;