@gjsify/example-gtk-adwaita-storybook 0.54.0 → 0.55.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.
Files changed (243) hide show
  1. package/dist/browser-main.js +8089 -3038
  2. package/dist/gjs.js +14 -9
  3. package/dist/gjs.node.mjs +10 -5
  4. package/package.json +5 -5
  5. package/src/browser/buttons/color-dialog-button.web.ts +42 -0
  6. package/src/browser/buttons/font-dialog-button.web.ts +42 -0
  7. package/src/browser/buttons/link-button.web.ts +37 -0
  8. package/src/browser/buttons/scale-button.web.ts +36 -0
  9. package/src/browser/buttons/toggle-button.web.ts +35 -0
  10. package/src/browser/controls/check-button.web.ts +38 -0
  11. package/src/browser/controls/gtk-spinner.web.ts +39 -0
  12. package/src/browser/controls/level-bar.web.ts +43 -0
  13. package/src/browser/controls/password-entry.web.ts +36 -0
  14. package/src/browser/controls/progress-bar.web.ts +39 -0
  15. package/src/browser/controls/scale.web.ts +44 -0
  16. package/src/browser/controls/search-entry.web.ts +49 -0
  17. package/src/browser/controls/spin-button.web.ts +40 -0
  18. package/src/browser/controls/switch.web.ts +38 -0
  19. package/src/browser/drawing/drag-icon.web.ts +112 -0
  20. package/src/browser/drawing/drawing-area.web.ts +66 -0
  21. package/src/browser/drawing/gl-area.web.ts +162 -0
  22. package/src/browser/drawing/graphics-offload.web.ts +74 -0
  23. package/src/browser/feedback/dialog.web.ts +76 -0
  24. package/src/browser/feedback/emoji-chooser.web.ts +93 -0
  25. package/src/browser/feedback/gtk-about-dialog.web.ts +105 -0
  26. package/src/browser/feedback/page-setup-unix-dialog.web.ts +93 -0
  27. package/src/browser/feedback/print-unix-dialog.web.ts +111 -0
  28. package/src/browser/feedback/shortcuts-dialog.web.ts +90 -0
  29. package/src/browser/layout/application-window.web.ts +94 -0
  30. package/src/browser/layout/aspect-frame.web.ts +49 -0
  31. package/src/browser/layout/bin.web.ts +41 -0
  32. package/src/browser/layout/breakpoint-bin.web.ts +61 -0
  33. package/src/browser/layout/center-box.web.ts +45 -0
  34. package/src/browser/layout/clamp-scrollable.web.ts +47 -0
  35. package/src/browser/layout/expander.web.ts +52 -0
  36. package/src/browser/layout/fixed.web.ts +51 -0
  37. package/src/browser/layout/frame.web.ts +40 -0
  38. package/src/browser/layout/grid.web.ts +46 -0
  39. package/src/browser/layout/layout-slot.web.ts +60 -0
  40. package/src/browser/layout/multi-layout-view.web.ts +94 -0
  41. package/src/browser/layout/overlay.web.ts +57 -0
  42. package/src/browser/layout/paned.web.ts +69 -0
  43. package/src/browser/layout/popover-bin.web.ts +54 -0
  44. package/src/browser/layout/popover-menu.web.ts +48 -0
  45. package/src/browser/layout/popover.web.ts +61 -0
  46. package/src/browser/layout/revealer.web.ts +53 -0
  47. package/src/browser/layout/scrollbar.web.ts +54 -0
  48. package/src/browser/layout/scrolled-window.web.ts +52 -0
  49. package/src/browser/layout/separator.web.ts +47 -0
  50. package/src/browser/layout/viewport.web.ts +51 -0
  51. package/src/browser/layout/window.web.ts +60 -0
  52. package/src/browser/media/image.web.ts +39 -0
  53. package/src/browser/media/inscription.web.ts +52 -0
  54. package/src/browser/media/media-controls.web.ts +65 -0
  55. package/src/browser/media/picture.web.ts +56 -0
  56. package/src/browser/media/video.web.ts +56 -0
  57. package/src/browser/navigation/popover-menu-bar.web.ts +40 -0
  58. package/src/browser/presentation/calendar.web.ts +49 -0
  59. package/src/browser/presentation/column-view.web.ts +45 -0
  60. package/src/browser/presentation/flow-box.web.ts +52 -0
  61. package/src/browser/presentation/grid-view.web.ts +37 -0
  62. package/src/browser/presentation/list-box.web.ts +48 -0
  63. package/src/browser/presentation/list-view.web.ts +36 -0
  64. package/src/browser/presentation/tree-expander.web.ts +68 -0
  65. package/src/browser/presentation/window-handle.web.ts +42 -0
  66. package/src/browser/rows/preferences-row.web.ts +43 -0
  67. package/src/browser/stories.ts +158 -0
  68. package/src/browser/text/editable-label.web.ts +45 -0
  69. package/src/browser/text/search-bar.web.ts +45 -0
  70. package/src/browser/text/text-view.web.ts +54 -0
  71. package/src/browser/text/text.web.ts +48 -0
  72. package/src/browser/view-switching/notebook.web.ts +67 -0
  73. package/src/browser/view-switching/stack-sidebar.web.ts +68 -0
  74. package/src/browser/view-switching/stack-switcher.web.ts +70 -0
  75. package/src/browser/view-switching/stack.web.ts +72 -0
  76. package/src/browser/view-switching/tab-bar.web.ts +105 -0
  77. package/src/browser/view-switching/tab-button.web.ts +52 -0
  78. package/src/browser/view-switching/tab-overview.web.ts +87 -0
  79. package/src/browser/view-switching/view-switcher-sidebar.web.ts +77 -0
  80. package/src/browser/windows/action-bar.web.ts +66 -0
  81. package/src/browser/windows/gtk-application-window.web.ts +67 -0
  82. package/src/browser/windows/gtk-header-bar.web.ts +61 -0
  83. package/src/browser/windows/gtk-window.web.ts +58 -0
  84. package/src/browser/windows/window-controls.web.ts +54 -0
  85. package/src/buttons/color-dialog-button.meta.ts +16 -0
  86. package/src/buttons/color-dialog-button.story.ts +49 -0
  87. package/src/buttons/font-dialog-button.meta.ts +29 -0
  88. package/src/buttons/font-dialog-button.story.ts +52 -0
  89. package/src/buttons/link-button.meta.ts +18 -0
  90. package/src/buttons/link-button.story.ts +48 -0
  91. package/src/buttons/scale-button.meta.ts +34 -0
  92. package/src/buttons/scale-button.story.ts +50 -0
  93. package/src/buttons/toggle-button.meta.ts +17 -0
  94. package/src/buttons/toggle-button.story.ts +46 -0
  95. package/src/controls/check-button.meta.ts +19 -0
  96. package/src/controls/check-button.story.ts +56 -0
  97. package/src/controls/gtk-spinner.meta.ts +24 -0
  98. package/src/controls/gtk-spinner.story.ts +53 -0
  99. package/src/controls/level-bar.meta.ts +39 -0
  100. package/src/controls/level-bar.story.ts +58 -0
  101. package/src/controls/password-entry.meta.ts +18 -0
  102. package/src/controls/password-entry.story.ts +54 -0
  103. package/src/controls/progress-bar.meta.ts +20 -0
  104. package/src/controls/progress-bar.story.ts +53 -0
  105. package/src/controls/scale.meta.ts +47 -0
  106. package/src/controls/scale.story.ts +66 -0
  107. package/src/controls/search-entry.meta.ts +18 -0
  108. package/src/controls/search-entry.story.ts +57 -0
  109. package/src/controls/spin-button.meta.ts +40 -0
  110. package/src/controls/spin-button.story.ts +55 -0
  111. package/src/controls/switch.meta.ts +18 -0
  112. package/src/controls/switch.story.ts +50 -0
  113. package/src/drawing/drag-icon.meta.ts +39 -0
  114. package/src/drawing/drag-icon.story.ts +117 -0
  115. package/src/drawing/drawing-area.meta.ts +45 -0
  116. package/src/drawing/drawing-area.story.ts +78 -0
  117. package/src/drawing/gl-area.meta.ts +30 -0
  118. package/src/drawing/gl-area.story.ts +113 -0
  119. package/src/drawing/graphics-offload.meta.ts +39 -0
  120. package/src/drawing/graphics-offload.story.ts +73 -0
  121. package/src/feedback/dialog.meta.ts +25 -0
  122. package/src/feedback/dialog.story.ts +78 -0
  123. package/src/feedback/emoji-chooser.meta.ts +10 -0
  124. package/src/feedback/emoji-chooser.story.ts +98 -0
  125. package/src/feedback/gtk-about-dialog.meta.ts +29 -0
  126. package/src/feedback/gtk-about-dialog.story.ts +90 -0
  127. package/src/feedback/page-setup-unix-dialog.meta.ts +37 -0
  128. package/src/feedback/page-setup-unix-dialog.story.ts +70 -0
  129. package/src/feedback/print-unix-dialog.meta.ts +30 -0
  130. package/src/feedback/print-unix-dialog.story.ts +93 -0
  131. package/src/feedback/shortcuts-dialog.meta.ts +12 -0
  132. package/src/feedback/shortcuts-dialog.story.ts +79 -0
  133. package/src/layout/application-window.meta.ts +16 -0
  134. package/src/layout/application-window.story.ts +97 -0
  135. package/src/layout/aspect-frame.meta.ts +19 -0
  136. package/src/layout/aspect-frame.story.ts +62 -0
  137. package/src/layout/bin.meta.ts +15 -0
  138. package/src/layout/bin.story.ts +56 -0
  139. package/src/layout/breakpoint-bin.meta.ts +38 -0
  140. package/src/layout/breakpoint-bin.story.ts +62 -0
  141. package/src/layout/center-box.meta.ts +26 -0
  142. package/src/layout/center-box.story.ts +54 -0
  143. package/src/layout/clamp-scrollable.meta.ts +30 -0
  144. package/src/layout/clamp-scrollable.story.ts +51 -0
  145. package/src/layout/expander.meta.ts +30 -0
  146. package/src/layout/expander.story.ts +62 -0
  147. package/src/layout/fixed.meta.ts +16 -0
  148. package/src/layout/fixed.story.ts +54 -0
  149. package/src/layout/frame.meta.ts +24 -0
  150. package/src/layout/frame.story.ts +51 -0
  151. package/src/layout/grid.meta.ts +45 -0
  152. package/src/layout/grid.story.ts +80 -0
  153. package/src/layout/layout-slot.meta.ts +22 -0
  154. package/src/layout/layout-slot.story.ts +68 -0
  155. package/src/layout/multi-layout-view.meta.ts +31 -0
  156. package/src/layout/multi-layout-view.story.ts +101 -0
  157. package/src/layout/overlay.meta.ts +42 -0
  158. package/src/layout/overlay.story.ts +72 -0
  159. package/src/layout/paned.meta.ts +45 -0
  160. package/src/layout/paned.story.ts +70 -0
  161. package/src/layout/popover-bin.meta.ts +29 -0
  162. package/src/layout/popover-bin.story.ts +66 -0
  163. package/src/layout/popover-menu.meta.ts +54 -0
  164. package/src/layout/popover-menu.story.ts +119 -0
  165. package/src/layout/popover.meta.ts +38 -0
  166. package/src/layout/popover.story.ts +66 -0
  167. package/src/layout/revealer.meta.ts +44 -0
  168. package/src/layout/revealer.story.ts +79 -0
  169. package/src/layout/scrollbar.meta.ts +33 -0
  170. package/src/layout/scrollbar.story.ts +61 -0
  171. package/src/layout/scrolled-window.meta.ts +57 -0
  172. package/src/layout/scrolled-window.story.ts +74 -0
  173. package/src/layout/separator.meta.ts +25 -0
  174. package/src/layout/separator.story.ts +53 -0
  175. package/src/layout/viewport.meta.ts +19 -0
  176. package/src/layout/viewport.story.ts +53 -0
  177. package/src/layout/window.meta.ts +13 -0
  178. package/src/layout/window.story.ts +66 -0
  179. package/src/media/image.meta.ts +24 -0
  180. package/src/media/image.story.ts +48 -0
  181. package/src/media/inscription.meta.ts +45 -0
  182. package/src/media/inscription.story.ts +64 -0
  183. package/src/media/media-controls.meta.ts +30 -0
  184. package/src/media/media-controls.story.ts +58 -0
  185. package/src/media/picture.meta.ts +42 -0
  186. package/src/media/picture.story.ts +81 -0
  187. package/src/media/video.meta.ts +32 -0
  188. package/src/media/video.story.ts +57 -0
  189. package/src/metas.ts +79 -0
  190. package/src/navigation/popover-menu-bar.meta.ts +51 -0
  191. package/src/navigation/popover-menu-bar.story.ts +72 -0
  192. package/src/presentation/calendar.meta.ts +28 -0
  193. package/src/presentation/calendar.story.ts +64 -0
  194. package/src/presentation/column-view.meta.ts +30 -0
  195. package/src/presentation/column-view.story.ts +73 -0
  196. package/src/presentation/flow-box.meta.ts +51 -0
  197. package/src/presentation/flow-box.story.ts +72 -0
  198. package/src/presentation/grid-view.meta.ts +28 -0
  199. package/src/presentation/grid-view.story.ts +64 -0
  200. package/src/presentation/list-box.meta.ts +55 -0
  201. package/src/presentation/list-box.story.ts +68 -0
  202. package/src/presentation/list-view.meta.ts +38 -0
  203. package/src/presentation/list-view.story.ts +73 -0
  204. package/src/presentation/tree-expander.meta.ts +24 -0
  205. package/src/presentation/tree-expander.story.ts +105 -0
  206. package/src/presentation/window-handle.meta.ts +27 -0
  207. package/src/presentation/window-handle.story.ts +53 -0
  208. package/src/rows/preferences-row.meta.ts +15 -0
  209. package/src/rows/preferences-row.story.ts +50 -0
  210. package/src/text/editable-label.meta.ts +17 -0
  211. package/src/text/editable-label.story.ts +47 -0
  212. package/src/text/search-bar.meta.ts +17 -0
  213. package/src/text/search-bar.story.ts +48 -0
  214. package/src/text/text-view.meta.ts +49 -0
  215. package/src/text/text-view.story.ts +69 -0
  216. package/src/text/text.meta.ts +21 -0
  217. package/src/text/text.story.ts +50 -0
  218. package/src/view-switching/notebook.meta.ts +44 -0
  219. package/src/view-switching/notebook.story.ts +77 -0
  220. package/src/view-switching/stack-sidebar.meta.ts +50 -0
  221. package/src/view-switching/stack-sidebar.story.ts +85 -0
  222. package/src/view-switching/stack-switcher.meta.ts +53 -0
  223. package/src/view-switching/stack-switcher.story.ts +95 -0
  224. package/src/view-switching/stack.meta.ts +53 -0
  225. package/src/view-switching/stack.story.ts +95 -0
  226. package/src/view-switching/tab-bar.meta.ts +22 -0
  227. package/src/view-switching/tab-bar.story.ts +85 -0
  228. package/src/view-switching/tab-button.meta.ts +14 -0
  229. package/src/view-switching/tab-button.story.ts +52 -0
  230. package/src/view-switching/tab-overview.meta.ts +21 -0
  231. package/src/view-switching/tab-overview.story.ts +86 -0
  232. package/src/view-switching/view-switcher-sidebar.meta.ts +42 -0
  233. package/src/view-switching/view-switcher-sidebar.story.ts +73 -0
  234. package/src/windows/action-bar.meta.ts +27 -0
  235. package/src/windows/action-bar.story.ts +65 -0
  236. package/src/windows/gtk-application-window.meta.ts +27 -0
  237. package/src/windows/gtk-application-window.story.ts +89 -0
  238. package/src/windows/gtk-header-bar.meta.ts +39 -0
  239. package/src/windows/gtk-header-bar.story.ts +76 -0
  240. package/src/windows/gtk-window.meta.ts +30 -0
  241. package/src/windows/gtk-window.story.ts +64 -0
  242. package/src/windows/window-controls.meta.ts +42 -0
  243. package/src/windows/window-controls.story.ts +61 -0
@@ -0,0 +1,112 @@
1
+ // Browser port of the Drag Icon story. Shares metadata with drag-icon.story.ts.
2
+ //
3
+ // THE DRAG SOURCE IS A `draggable` ROW HERE, and that is the whole of the difference: a
4
+ // browser has no drag source object to attach to a widget, but it does have the HTML
5
+ // drag-and-drop events, and `dragstart` is the same moment as `GtkDragSource::drag-begin`.
6
+
7
+ import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
8
+ // The one place a story needs an element's VALUE: `getForDrag` and `createWidgetForValue`
9
+ // are the static half of `GtkDragIcon`'s API — an icon is not something a page declares and
10
+ // fills in, it is something a drag asks for. Everywhere else the stories create elements by
11
+ // tag, which needs no import at all.
12
+ import { Gtk } from '@gjsify/adwaita-web';
13
+ import { dragIconMeta } from '../../drawing/drag-icon.meta.js';
14
+
15
+ /** The `rgba()` a `GdkRGBA` value paints, as the colour swatch shows it. */
16
+ interface Rgba {
17
+ readonly red: number;
18
+ readonly green: number;
19
+ readonly blue: number;
20
+ readonly alpha?: number;
21
+ }
22
+
23
+ export class DragIconWebStory extends StoryElement {
24
+ private _row: HTMLElement | null = null;
25
+ private _hint: HTMLElement | null = null;
26
+
27
+ constructor() {
28
+ super(DragIconWebStory.getMetadata(), 'Default');
29
+ }
30
+
31
+ static getMetadata(): StoryMeta {
32
+ return dragIconMeta;
33
+ }
34
+
35
+ initialize(): void {
36
+ const box = document.createElement('div');
37
+ box.style.display = 'flex';
38
+ box.style.flexDirection = 'column';
39
+ box.style.alignItems = 'center';
40
+ box.style.gap = '12px';
41
+
42
+ const hint = document.createElement('p');
43
+ hint.className = 'dim-label';
44
+ this._hint = hint;
45
+ box.append(hint);
46
+
47
+ // The DRAGGABLE ROW. What `gtk_widget_add_controller (widget,
48
+ // GTK_EVENT_CONTROLLER (drag_source))` is in GTK (gtkdragsource.c:44-58) is a
49
+ // `draggable` element plus a `dragstart` handler in a browser.
50
+ this._row = document.createElement('div');
51
+ this._row.className = 'card';
52
+ this._row.draggable = true;
53
+ this._row.style.display = 'flex';
54
+ this._row.style.alignItems = 'center';
55
+ this._row.style.gap = '12px';
56
+ this._row.style.padding = '12px';
57
+ const label = document.createElement('span');
58
+ label.className = 'heading';
59
+ label.textContent = 'Report.odt';
60
+ this._row.append(label);
61
+ this._row.addEventListener('dragstart', (event) => this._onDragStart(event as DragEvent));
62
+ box.append(this._row);
63
+
64
+ this._apply();
65
+ this.addContent(box);
66
+ }
67
+
68
+ updateArgs(_args: StoryArgs): void {
69
+ this._apply();
70
+ }
71
+
72
+ /** The value the drag offers, which is also what decides the icon — the two are one thing. */
73
+ private _value(): string | Rgba | null {
74
+ switch (this.args.value) {
75
+ case 'rgba':
76
+ return { red: 0.25, green: 0.55, blue: 0.95, alpha: 1 };
77
+ case 'none':
78
+ return null;
79
+ default:
80
+ return 'Report.odt';
81
+ }
82
+ }
83
+
84
+ /**
85
+ * `::drag-begin`: `GtkDragIcon.getForDrag` is the only way to reach an icon, and
86
+ * `createWidgetForValue` is what decides what a value shows (gtkdragicon.c:529-556).
87
+ */
88
+ private _onDragStart(event: DragEvent): void {
89
+ // `actions` on the source is the effect this drag may have; a browser states it as
90
+ // `effectAllowed`, and 'copy' / 'move' are the two Gdk.DragAction bits of the story.
91
+ if (event.dataTransfer !== null) {
92
+ event.dataTransfer.effectAllowed = this.args.actions === 'move' ? 'move' : 'copy';
93
+ }
94
+ const value = this._value();
95
+ if (value === null) return;
96
+ const icon = Gtk.DragIcon.getForDrag(event);
97
+ // `set_child` unparents the old child and shows the icon (:468-489), so this is also
98
+ // how an icon that had none starts showing.
99
+ icon.setChild(Gtk.DragIcon.createWidgetForValue(value));
100
+ }
101
+
102
+ private _apply(): void {
103
+ if (this._hint === null || this._row === null) return;
104
+ this._hint.textContent =
105
+ this.args.value === 'none'
106
+ ? 'Drag the row — no icon, because the value has no widget to show'
107
+ : 'Drag the row and watch the pointer';
108
+ this._row.title = 'Draggable — the icon that follows the pointer is a <gtk-drag-icon>';
109
+ }
110
+ }
111
+
112
+ export const DragIconWebStories: WebStoryModule = { stories: [DragIconWebStory] };
@@ -0,0 +1,66 @@
1
+ // Browser port of the Drawing Area story. Shares metadata with drawing-area.story.ts.
2
+
3
+ import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
4
+ // TYPE-ONLY: the draw function is installed imperatively, exactly as the GTK story
5
+ // installs its callback, so the element type is what the story needs to say — not a
6
+ // hand-written interface that would be a second copy of the API. The import erases.
7
+ import type { Gtk } from '@gjsify/adwaita-web';
8
+ import { drawingAreaMeta } from '../../drawing/drawing-area.meta.js';
9
+
10
+ export class DrawingAreaWebStory extends StoryElement {
11
+ private _area: Gtk.DrawingArea | null = null;
12
+
13
+ constructor() {
14
+ super(DrawingAreaWebStory.getMetadata(), 'Default');
15
+ }
16
+
17
+ static getMetadata(): StoryMeta {
18
+ return drawingAreaMeta;
19
+ }
20
+
21
+ initialize(): void {
22
+ this._area = document.createElement('gtk-drawing-area') as Gtk.DrawingArea;
23
+ // The same three shapes the GTK draw function draws, on the same 2D context: the
24
+ // element scales it by the device pixel ratio, so the numbers here are logical
25
+ // pixels on both surfaces.
26
+ const area = this._area;
27
+ area.setDrawFunc((_area, cr, width, height) => {
28
+ // The area paints itself in the widget's own colour, which is what
29
+ // `gtk_widget_get_color` is for (gtkdrawingarea.c:98-101) — `currentColor` is
30
+ // the browser's spelling of the same question.
31
+ cr.fillStyle = 'currentColor';
32
+ cr.globalCompositeOperation = 'source-over';
33
+ cr.fillRect(0, 0, width, height);
34
+ cr.globalCompositeOperation = 'difference';
35
+ switch (this.args.shape) {
36
+ case 'grid':
37
+ for (let x = 0; x < width; x += 16) cr.fillRect(x, 0, 8, height);
38
+ break;
39
+ case 'crosshair':
40
+ cr.fillRect(width / 2 - 1, 0, 2, height);
41
+ cr.fillRect(0, height / 2 - 1, width, 2);
42
+ break;
43
+ default:
44
+ cr.beginPath();
45
+ cr.arc(width / 2, height / 2, Math.min(width, height) / 2, 0, 2 * Math.PI);
46
+ cr.fill();
47
+ break;
48
+ }
49
+ cr.globalCompositeOperation = 'source-over';
50
+ });
51
+ this._apply();
52
+ this.addContent(this._area);
53
+ }
54
+
55
+ updateArgs(_args: StoryArgs): void {
56
+ this._apply();
57
+ }
58
+
59
+ private _apply(): void {
60
+ if (!this._area) return;
61
+ this._area.setAttribute('content-width', String(this.args.contentWidth as number));
62
+ this._area.setAttribute('content-height', String(this.args.contentHeight as number));
63
+ }
64
+ }
65
+
66
+ export const DrawingAreaWebStories: WebStoryModule = { stories: [DrawingAreaWebStory] };
@@ -0,0 +1,162 @@
1
+ // Browser port of the GL Area story. Shares metadata with gl-area.story.ts.
2
+
3
+ import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
4
+ import type { Gtk } from '@gjsify/adwaita-web';
5
+ import { glAreaMeta } from '../../drawing/gl-area.meta.js';
6
+
7
+ const VERTEX_SHADER = `
8
+ attribute vec2 position;
9
+ uniform float angle;
10
+ void main () {
11
+ float c = cos (angle);
12
+ float s = sin (angle);
13
+ gl_Position = vec4 ((position.x * c - position.y * s) * 0.7,
14
+ (position.x * s + position.y * c) * 0.7,
15
+ 0.0, 1.0);
16
+ }`;
17
+
18
+ const FRAGMENT_SHADER = `
19
+ precision mediump float;
20
+ void main () { gl_FragColor = vec4 (0.25, 0.55, 0.9, 1.0); }`;
21
+
22
+ export class GLAreaWebStory extends StoryElement {
23
+ private _area: Gtk.GLArea | null = null;
24
+ private _readout: HTMLElement | null = null;
25
+ private _frame = 0;
26
+ private _program: WebGLProgram | null = null;
27
+ private _buffer: WebGLBuffer | null = null;
28
+ private _angle = 0;
29
+ private _renders = 0;
30
+ private _viewport = '—';
31
+
32
+ constructor() {
33
+ super(GLAreaWebStory.getMetadata(), 'Default');
34
+ }
35
+
36
+ static getMetadata(): StoryMeta {
37
+ return glAreaMeta;
38
+ }
39
+
40
+ initialize(): void {
41
+ this._area = document.createElement('gtk-gl-area') as Gtk.GLArea;
42
+ this._area.style.width = '240px';
43
+ this._area.style.height = '160px';
44
+ this._readout = document.createElement('div');
45
+ this._readout.className = 'adw-gl-area-story-readout';
46
+
47
+ // The SAME two signals the GTK story connects, in the SAME order: `resize` arrives
48
+ // before the first `render` and on every later allocation, and its numbers are
49
+ // device pixels (gtkglarea.c:761-763, :797-807).
50
+ this._area.addEventListener('resize', (event: Event) => {
51
+ const { width, height } = (event as CustomEvent).detail as { width: number; height: number };
52
+ this._viewport = `${width} × ${height}`;
53
+ this._refresh();
54
+ });
55
+ this._area.addEventListener('render', (event: Event) => {
56
+ const { context } = (event as CustomEvent).detail as {
57
+ context: WebGLRenderingContext | WebGL2RenderingContext | null;
58
+ };
59
+ if (context) this._draw(context);
60
+ this._renders += 1;
61
+ this._refresh();
62
+ });
63
+
64
+ this._apply();
65
+ this._start();
66
+ const box = document.createElement('div');
67
+ box.style.display = 'flex';
68
+ box.style.flexDirection = 'column';
69
+ box.style.gap = '6px';
70
+ box.style.alignItems = 'center';
71
+ box.append(this._area, this._readout);
72
+ this.addContent(box);
73
+ }
74
+
75
+ updateArgs(_args: StoryArgs): void {
76
+ this._stop();
77
+ this._apply();
78
+ this._start();
79
+ }
80
+
81
+ private _apply(): void {
82
+ if (!this._area) return;
83
+ this._area.setAttribute('auto-render', String(this.args.autoRender as boolean));
84
+ this._area.setAttribute('has-depth-buffer', String(this.args.hasDepthBuffer as boolean));
85
+ // `allowed-apis` is refused once the area is realized (gtkglarea.c:1242), so it is
86
+ // set before the first render — which is also what the GTK story does.
87
+ this._area.setAttribute('allowed-apis', this.args.allowedApis as string);
88
+ }
89
+
90
+ /**
91
+ * `queue_render()` is how a GL application asks for the next frame (gtkglarea.c:1465).
92
+ * The browser's frame clock is `requestAnimationFrame`, and the element coalesces
93
+ * through it, so this is a scheduler rather than a synchronous draw.
94
+ */
95
+ private _start(): void {
96
+ if (!this.args.spinning) return;
97
+ const step = () => {
98
+ this._angle += 0.05;
99
+ this._area?.queueRender();
100
+ this._frame = requestAnimationFrame(step);
101
+ };
102
+ this._frame = requestAnimationFrame(step);
103
+ }
104
+
105
+ private _stop(): void {
106
+ if (this._frame !== 0) cancelAnimationFrame(this._frame);
107
+ this._frame = 0;
108
+ }
109
+
110
+ /** The triangle the GTK story cannot draw here: a program, a buffer and a draw call. */
111
+ private _draw(context: WebGLRenderingContext | WebGL2RenderingContext): void {
112
+ // WebGL2 extends WebGL, so one `instanceof` covers both.
113
+ if (!(context instanceof WebGLRenderingContext)) return;
114
+ if (!this._program) this._build(context);
115
+ const program = this._program;
116
+ // `_build` compiles and links; a driver that refuses either leaves nothing to draw,
117
+ // which is the end of this frame rather than an error thrown out of the handler.
118
+ if (program === null) return;
119
+ context.clearColor(0.15, 0.16, 0.18, 1);
120
+ context.clear(context.COLOR_BUFFER_BIT);
121
+ context.useProgram(program);
122
+ context.uniform1f(context.getUniformLocation(program, 'angle'), this._angle);
123
+ context.bindBuffer(context.ARRAY_BUFFER, this._buffer);
124
+ context.enableVertexAttribArray(0);
125
+ context.vertexAttribPointer(0, 2, context.FLOAT, false, 0, 0);
126
+ context.drawArrays(context.TRIANGLES, 0, 3);
127
+ }
128
+
129
+ /** Compiled once, with the context current — the C's own advice. */
130
+ private _build(context: WebGLRenderingContext | WebGL2RenderingContext): void {
131
+ const compile = (type: number, source: string): WebGLShader | null => {
132
+ const shader = context.createShader(type);
133
+ if (!shader) return null;
134
+ context.shaderSource(shader, source);
135
+ context.compileShader(shader);
136
+ return shader;
137
+ };
138
+ const program = context.createProgram();
139
+ const vertex = compile(context.VERTEX_SHADER, VERTEX_SHADER);
140
+ const fragment = compile(context.FRAGMENT_SHADER, FRAGMENT_SHADER);
141
+ if (!program || !vertex || !fragment) return;
142
+ context.attachShader(program, vertex);
143
+ context.attachShader(program, fragment);
144
+ context.linkProgram(program);
145
+ this._program = program;
146
+ const buffer = context.createBuffer();
147
+ context.bindBuffer(context.ARRAY_BUFFER, buffer);
148
+ context.bufferData(
149
+ context.ARRAY_BUFFER,
150
+ new Float32Array([-0.6, -0.6, 0.6, -0.6, 0.0, 0.6]),
151
+ context.STATIC_DRAW,
152
+ );
153
+ this._buffer = buffer;
154
+ }
155
+
156
+ private _refresh(): void {
157
+ if (!this._readout) return;
158
+ this._readout.textContent = `viewport ${this._viewport} device px · ${this._renders} renders`;
159
+ }
160
+ }
161
+
162
+ export const GLAreaWebStories: WebStoryModule = { stories: [GLAreaWebStory] };
@@ -0,0 +1,74 @@
1
+ // Browser port of the Graphics Offload story. Shares metadata with
2
+ // graphics-offload.story.ts.
3
+
4
+ import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
5
+ import { graphicsOffloadMeta } from '../../drawing/graphics-offload.meta.js';
6
+
7
+ export class GraphicsOffloadWebStory extends StoryElement {
8
+ private _offload: HTMLElement | null = null;
9
+
10
+ constructor() {
11
+ super(GraphicsOffloadWebStory.getMetadata(), 'Default');
12
+ }
13
+
14
+ static getMetadata(): StoryMeta {
15
+ return graphicsOffloadMeta;
16
+ }
17
+
18
+ initialize(): void {
19
+ this._offload = document.createElement('gtk-graphics-offload');
20
+ this._offload.style.width = '280px';
21
+ this._offload.style.height = '96px';
22
+ this._apply();
23
+ const box = document.createElement('div');
24
+ box.style.display = 'flex';
25
+ box.style.justifyContent = 'center';
26
+ box.append(this._offload);
27
+ this.addContent(box);
28
+ }
29
+
30
+ updateArgs(_args: StoryArgs): void {
31
+ this._apply();
32
+ }
33
+
34
+ private _apply(): void {
35
+ if (!this._offload) return;
36
+ // `enabled` before `black-background`, the C's property order
37
+ // (gtkgraphicsoffload.c:366-380, :423-437).
38
+ this._offload.setAttribute('enabled', this.args.enabled as string);
39
+ this._offload.setAttribute('black-background', String(this.args.blackBackground as boolean));
40
+ // The wrapper holds ONE child, so changing it means replacing it — which is what
41
+ // `set_child` does, unparenting the old one (gtkgraphicsoffload.c:317-336).
42
+ let child: HTMLElement;
43
+ switch (this.args.child) {
44
+ case 'label': {
45
+ const label = document.createElement('gtk-label');
46
+ label.setAttribute('label', 'The child of an offloaded layer');
47
+ child = label;
48
+ break;
49
+ }
50
+ case 'box': {
51
+ const row = document.createElement('gtk-box');
52
+ row.setAttribute('orientation', 'horizontal');
53
+ row.setAttribute('spacing', '12');
54
+ for (const text of ['One', 'Two', 'Three']) {
55
+ const label = document.createElement('gtk-label');
56
+ label.setAttribute('label', text);
57
+ row.appendChild(label);
58
+ }
59
+ child = row;
60
+ break;
61
+ }
62
+ default: {
63
+ const image = document.createElement('gtk-image');
64
+ image.setAttribute('icon-name', 'camera-photo');
65
+ image.setAttribute('pixel-size', '48');
66
+ child = image;
67
+ break;
68
+ }
69
+ }
70
+ this._offload.replaceChildren(child);
71
+ }
72
+ }
73
+
74
+ export const GraphicsOffloadWebStories: WebStoryModule = { stories: [GraphicsOffloadWebStory] };
@@ -0,0 +1,76 @@
1
+ // Browser port of the Dialog story. Shares metadata with dialog.story.ts.
2
+
3
+ import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
4
+ import { dialogMeta } from '../../feedback/dialog.meta.js';
5
+
6
+ /**
7
+ * Story: adw-dialog presented from a button, holding a preferences page as its
8
+ * child — a 1:1 port of the GTK DialogStory.
9
+ */
10
+ export class DialogWebStory extends StoryElement {
11
+ constructor() {
12
+ super(DialogWebStory.getMetadata(), 'Default');
13
+ }
14
+
15
+ static getMetadata(): StoryMeta {
16
+ return dialogMeta;
17
+ }
18
+
19
+ initialize(): void {
20
+ const button = document.createElement('gtk-button');
21
+ button.textContent = 'Show dialog';
22
+ button.setAttribute('pill', '');
23
+ button.setAttribute('suggested', '');
24
+ button.addEventListener('click', () => this._present());
25
+
26
+ // Centre the trigger button so the preview matches the GTK story's
27
+ // centred halign/valign placement.
28
+ const center = document.createElement('div');
29
+ center.style.display = 'flex';
30
+ center.style.alignItems = 'center';
31
+ center.style.justifyContent = 'center';
32
+ center.style.minHeight = '160px';
33
+ center.append(button);
34
+
35
+ this.addContent(center);
36
+ }
37
+
38
+ /** Build + present a fresh dialog, reading the latest args (as the GTK story does). */
39
+ private _present(): void {
40
+ const dialog = document.createElement('adw-dialog');
41
+ dialog.setAttribute('title', this.args.title as string);
42
+ // The NICK, not the enum member: `presentation-mode` is the string the GIR
43
+ // enum resolves to on the other two renderers (ADR 0034 § 4).
44
+ dialog.setAttribute('presentation-mode', this.args.presentation as string);
45
+
46
+ // ONE child, and it is the `Adw.Dialog:child` property — the element moves
47
+ // every child into its content area, so this is a declaration, not a pack.
48
+ const group = document.createElement('adw-preferences-group');
49
+ group.setAttribute('title', 'Appearance');
50
+ const row = document.createElement('adw-switch-row');
51
+ row.setAttribute('title', 'Dark style');
52
+ row.setAttribute('subtitle', 'Use a dark colour scheme');
53
+ row.setAttribute('active', '');
54
+ group.appendChild(row);
55
+
56
+ const page = document.createElement('adw-preferences-page');
57
+ page.setAttribute('title', 'General');
58
+ page.appendChild(group);
59
+
60
+ dialog.appendChild(page);
61
+
62
+ // A DISCONNECTED element is attached to `document.body` on present, the
63
+ // `Adw.Dialog.present(parent)` idiom; one already in the DOM is revealed in
64
+ // place and stays reusable.
65
+ (dialog as HTMLElement & { present(): void }).present();
66
+
67
+ // Close it from the story, the way the GTK story's dialog is destroyed on close.
68
+ dialog.addEventListener('closed', () => dialog.remove(), { once: true });
69
+ }
70
+
71
+ updateArgs(_args: StoryArgs): void {
72
+ // The dialog reads the latest args each time it is presented; nothing to mutate live.
73
+ }
74
+ }
75
+
76
+ export const DialogWebStories: WebStoryModule = { stories: [DialogWebStory] };
@@ -0,0 +1,93 @@
1
+ // Browser port of the Emoji Chooser story. Shares metadata with emoji-chooser.story.ts.
2
+
3
+ import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
4
+ import { emojiChooserMeta } from '../../feedback/emoji-chooser.meta.js';
5
+
6
+ /** The half of `<gtk-emoji-chooser>` the markup cannot spell. */
7
+ interface EmojiChooserElement extends HTMLElement {
8
+ popup(): void;
9
+ popdown(): void;
10
+ }
11
+
12
+ /** The search entry the chooser builds inside itself. */
13
+ function searchEntryOf(chooser: HTMLElement): HTMLInputElement | null {
14
+ return chooser.querySelector<HTMLInputElement>('.emoji-search-entry');
15
+ }
16
+
17
+ export class EmojiChooserWebStory extends StoryElement {
18
+ private _entry: HTMLInputElement | null = null;
19
+ private _button: HTMLElement | null = null;
20
+ private _chooser: EmojiChooserElement | null = null;
21
+
22
+ constructor() {
23
+ super(EmojiChooserWebStory.getMetadata(), 'Default');
24
+ }
25
+
26
+ static getMetadata(): StoryMeta {
27
+ return emojiChooserMeta;
28
+ }
29
+
30
+ initialize(): void {
31
+ const row = document.createElement('div');
32
+ row.className = 'sb-emoji-row';
33
+
34
+ this._entry = document.createElement('input');
35
+ this._entry.className = 'sb-emoji-entry';
36
+ this._entry.type = 'text';
37
+ this._entry.placeholder = 'Type a message…';
38
+ row.appendChild(this._entry);
39
+
40
+ // The chooser is a POPOVER, so it needs something to pop up from — the same shape
41
+ // the GTK story builds: a button in the entry, the chooser as its child.
42
+ const anchor = document.createElement('div');
43
+ anchor.className = 'sb-emoji-anchor';
44
+ this._button = document.createElement('button');
45
+ this._button.className = 'adw-button flat circular';
46
+ this._button.setAttribute('aria-label', 'Insert an emoji');
47
+ const icon = document.createElement('gtk-image');
48
+ icon.setAttribute('icon-name', 'face-smile');
49
+ this._button.appendChild(icon);
50
+ this._button.addEventListener('click', () => {
51
+ this._chooser?.popup();
52
+ searchEntryOf(this._chooser as HTMLElement)?.focus();
53
+ });
54
+ anchor.appendChild(this._button);
55
+ row.appendChild(anchor);
56
+
57
+ this._chooser = document.createElement('gtk-emoji-chooser') as EmojiChooserElement;
58
+ this._chooser.setAttribute('position', 'bottom');
59
+ this._chooser.setAttribute('align', 'end');
60
+ // `GtkPopover` positions against the widget it is a child of; here that is the
61
+ // button, which is NOT its DOM parent (the wrapper is), so the anchor is set.
62
+ (this._chooser as unknown as HTMLElement & { anchor: HTMLElement | null }).anchor = this._button;
63
+ this._chooser.addEventListener('emoji-picked', (event) => {
64
+ const { text } = (event as CustomEvent).detail as { text: string };
65
+ if (this._entry !== null) this._entry.value += text;
66
+ });
67
+ anchor.appendChild(this._chooser);
68
+
69
+ this.addContent(row);
70
+ this._applySearch(this.args.search as string);
71
+ this._chooser.popup();
72
+ }
73
+
74
+ private _applySearch(text: string): void {
75
+ const entry = this._chooser === null ? null : searchEntryOf(this._chooser);
76
+ if (entry === null) return;
77
+ entry.value = text;
78
+ entry.dispatchEvent(new Event('input', { bubbles: true }));
79
+ }
80
+
81
+ updateArgs(args: StoryArgs): void {
82
+ if (typeof args.search === 'string') this._applySearch(args.search);
83
+ }
84
+
85
+ teardown(): void {
86
+ this._chooser?.popdown();
87
+ this._chooser = null;
88
+ this._entry = null;
89
+ this._button = null;
90
+ }
91
+ }
92
+
93
+ export const EmojiChooserWebStories: WebStoryModule = { stories: [EmojiChooserWebStory] };