create-gtkx 0.21.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/LICENSE +373 -0
- package/bin/create-gtkx.js +5 -0
- package/dist/cli.d.ts +24 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +19 -0
- package/dist/cli.js.map +1 -0
- package/dist/command.d.ts +65 -0
- package/dist/command.d.ts.map +1 -0
- package/dist/command.js +69 -0
- package/dist/command.js.map +1 -0
- package/dist/create.d.ts +3 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +7 -0
- package/dist/create.js.map +1 -0
- package/dist/deps.d.ts +3 -0
- package/dist/deps.d.ts.map +1 -0
- package/dist/deps.js +51 -0
- package/dist/deps.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/options.d.ts +25 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +13 -0
- package/dist/options.js.map +1 -0
- package/dist/scaffolder.d.ts +51 -0
- package/dist/scaffolder.d.ts.map +1 -0
- package/dist/scaffolder.js +191 -0
- package/dist/scaffolder.js.map +1 -0
- package/dist/templates.d.ts +11 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +25 -0
- package/dist/templates.js.map +1 -0
- package/package.json +65 -0
- package/src/cli.ts +21 -0
- package/src/command.ts +87 -0
- package/src/create.ts +7 -0
- package/src/deps.ts +51 -0
- package/src/index.ts +4 -0
- package/src/options.ts +24 -0
- package/src/scaffolder.ts +306 -0
- package/src/templates.ts +42 -0
- package/templates/claude/EXAMPLES.md.ejs +790 -0
- package/templates/claude/SKILL.md.ejs +126 -0
- package/templates/claude/WIDGETS.md.ejs +934 -0
- package/templates/config/vitest.config.ts.ejs +11 -0
- package/templates/gitignore.ejs +4 -0
- package/templates/gtkx.config.ts.ejs +6 -0
- package/templates/package.json.ejs +16 -0
- package/templates/src/app.tsx.ejs +48 -0
- package/templates/src/gtkx-env.d.ts.ejs +2 -0
- package/templates/src/index.tsx.ejs +4 -0
- package/templates/tests/app.test.tsx.ejs +16 -0
- package/templates/tsconfig.json.ejs +14 -0
|
@@ -0,0 +1,934 @@
|
|
|
1
|
+
# GTKX Widget Reference
|
|
2
|
+
|
|
3
|
+
## Imports
|
|
4
|
+
|
|
5
|
+
Every element-like component is imported from its namespace module: `@gtkx/jsx/gtk` for GTK widgets (`GtkBox`, `GtkListView`, `GtkColumnView`, `GtkDrawingArea`, …), `@gtkx/jsx/adw` for Adwaita (`AdwHeaderBar`, `AdwComboRow`, …), and `@gtkx/jsx/gio` for Gio elements (`GMenu`, `GSimpleAction`). `@gtkx/react` provides `render`, `quit`, hooks (`useApplication`, `useProperty`, `useSetting`, `useSignal`, `useTickCallback`, `useAdjustment`), and shared types such as `MenuEntry` and `ListItem`. `@gtkx/animate` provides the animation components. Enums and classes come from `@gtkx/gi/<ns>` (e.g. `import * as Gtk from "@gtkx/gi/gtk"`).
|
|
6
|
+
|
|
7
|
+
## Common Props (All Widgets)
|
|
8
|
+
|
|
9
|
+
| Prop | Type | Description |
|
|
10
|
+
|------|------|-------------|
|
|
11
|
+
| `hexpand` / `vexpand` | boolean | Expand to fill available space |
|
|
12
|
+
| `halign` / `valign` | `Gtk.Align.START \| CENTER \| END \| FILL` | Alignment |
|
|
13
|
+
| `marginStart/End/Top/Bottom` | number | Margins in pixels |
|
|
14
|
+
| `sensitive` | boolean | Enabled/disabled state |
|
|
15
|
+
| `visible` | boolean | Visibility |
|
|
16
|
+
| `cssClasses` | string[] | CSS classes for styling |
|
|
17
|
+
| `widthRequest` / `heightRequest` | number | Minimum size |
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Containers
|
|
22
|
+
|
|
23
|
+
### GtkBox
|
|
24
|
+
Linear layout (horizontal or vertical).
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
<GtkBox orientation={Gtk.Orientation.VERTICAL} spacing={12}>
|
|
28
|
+
{children}
|
|
29
|
+
</GtkBox>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### GtkGrid
|
|
33
|
+
2D grid with explicit positioning using `GtkGridChild`.
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<GtkGrid rowSpacing={8} columnSpacing={12}>
|
|
37
|
+
<GtkGridChild column={0} row={0}><GtkLabel label="Name:" /></GtkGridChild>
|
|
38
|
+
<GtkGridChild column={1} row={0}><GtkEntry hexpand /></GtkGridChild>
|
|
39
|
+
<GtkGridChild column={0} row={1} columnSpan={2}><GtkButton label="Submit" /></GtkGridChild>
|
|
40
|
+
</GtkGrid>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**GtkGridChild props:** `column`, `row`, `columnSpan`, `rowSpan`
|
|
44
|
+
|
|
45
|
+
### GtkStack
|
|
46
|
+
Page container, shows one child at a time. Drive the visible page with `visibleChildName`, matching a page's `id`.
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
<GtkStack visibleChildName="page1" transitionType={Gtk.StackTransitionType.SLIDE_LEFT_RIGHT}>
|
|
50
|
+
<GtkStackPage id="page1" title="First" iconName="document-new">
|
|
51
|
+
<Content1 />
|
|
52
|
+
</GtkStackPage>
|
|
53
|
+
<GtkStackPage id="page2" title="Second">
|
|
54
|
+
<Content2 />
|
|
55
|
+
</GtkStackPage>
|
|
56
|
+
</GtkStack>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**GtkStackPage props:** `id` (matches the stack's `visibleChildName`), `title`, `iconName`, `needsAttention`, `badgeNumber` (AdwViewStack only). `AdwViewStack` pairs with `AdwViewStackPage` the same way.
|
|
60
|
+
|
|
61
|
+
### GtkNotebook
|
|
62
|
+
Tabbed container with visible tabs.
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
<GtkNotebook>
|
|
66
|
+
<GtkNotebookPage label="Tab 1"><Content1 /></GtkNotebookPage>
|
|
67
|
+
<GtkNotebookPage label="Tab 2" tabExpand tabFill><Content2 /></GtkNotebookPage>
|
|
68
|
+
</GtkNotebook>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Custom tab widget via the `tabLabel` prop:
|
|
72
|
+
```tsx
|
|
73
|
+
<GtkNotebookPage
|
|
74
|
+
tabLabel={
|
|
75
|
+
<GtkBox spacing={4}>
|
|
76
|
+
<GtkImage iconName="folder-symbolic" />
|
|
77
|
+
<GtkLabel label="Files" />
|
|
78
|
+
</GtkBox>
|
|
79
|
+
}
|
|
80
|
+
>
|
|
81
|
+
<Content />
|
|
82
|
+
</GtkNotebookPage>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**GtkNotebookPage props:** `label`, `tabLabel`, `tabExpand`, `tabFill`
|
|
86
|
+
|
|
87
|
+
### GtkPaned
|
|
88
|
+
Resizable split with draggable divider. Pass the two panes through the `startChild` and `endChild` props.
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
<GtkPaned position={280} shrinkStartChild={false} startChild={<Sidebar />} endChild={<MainContent />} />
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### GtkOverlay
|
|
95
|
+
Stack widgets on top of each other. First child is base layer, additional children need a `GtkOverlayChild` wrapper. Multiple children supported per overlay.
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
<GtkOverlay>
|
|
99
|
+
<GtkButton label="Notifications" />
|
|
100
|
+
<GtkOverlayChild>
|
|
101
|
+
<GtkLabel label="3" cssClasses={["badge"]} halign={Gtk.Align.END} valign={Gtk.Align.START} />
|
|
102
|
+
</GtkOverlayChild>
|
|
103
|
+
</GtkOverlay>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### GtkFixed
|
|
107
|
+
Absolute positioning with optional 3D transforms. Use a `GtkFixedChild` wrapper for children.
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
<GtkFixed>
|
|
111
|
+
<GtkFixedChild x={20} y={30}>
|
|
112
|
+
<GtkLabel label="Top Left" />
|
|
113
|
+
</GtkFixedChild>
|
|
114
|
+
<GtkFixedChild x={200} y={100} transform={someGskTransform}>
|
|
115
|
+
<GtkLabel label="Transformed" />
|
|
116
|
+
</GtkFixedChild>
|
|
117
|
+
</GtkFixed>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**GtkFixedChild props:** `x`, `y` (pixel coordinates), `transform` (optional `Gsk.Transform`)
|
|
121
|
+
|
|
122
|
+
### GtkScrolledWindow
|
|
123
|
+
Scrollable container.
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
<GtkScrolledWindow vexpand hscrollbarPolicy={Gtk.PolicyType.NEVER}>
|
|
127
|
+
<Content />
|
|
128
|
+
</GtkScrolledWindow>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Virtual Lists
|
|
134
|
+
|
|
135
|
+
All virtual list widgets use an `items` data prop and a `renderItem` function. Items are `{ id: string, value: T }` objects.
|
|
136
|
+
|
|
137
|
+
### GtkListView
|
|
138
|
+
High-performance scrollable list with selection.
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
<GtkListView
|
|
142
|
+
estimatedItemHeight={48}
|
|
143
|
+
vexpand
|
|
144
|
+
selected={selectedId ? [selectedId] : []}
|
|
145
|
+
selectionMode={Gtk.SelectionMode.SINGLE}
|
|
146
|
+
onSelectionChanged={(ids) => setSelectedId(ids[0])}
|
|
147
|
+
items={items.map(item => ({ id: item.id, value: item }))}
|
|
148
|
+
renderItem={(item: Item) => <GtkLabel label={item.name} />}
|
|
149
|
+
/>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### GtkGridView
|
|
153
|
+
Grid-based virtual scrolling.
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
<GtkGridView
|
|
157
|
+
estimatedItemHeight={100}
|
|
158
|
+
minColumns={2}
|
|
159
|
+
maxColumns={4}
|
|
160
|
+
items={items.map(item => ({ id: item.id, value: item }))}
|
|
161
|
+
renderItem={(item: Item) => (
|
|
162
|
+
<GtkBox orientation={Gtk.Orientation.VERTICAL}>
|
|
163
|
+
<GtkImage iconName={item.icon} />
|
|
164
|
+
<GtkLabel label={item.name} />
|
|
165
|
+
</GtkBox>
|
|
166
|
+
)}
|
|
167
|
+
/>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### GtkColumnView
|
|
171
|
+
Table with sortable columns.
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
<GtkColumnView
|
|
175
|
+
estimatedRowHeight={48}
|
|
176
|
+
sortColumn="name"
|
|
177
|
+
sortOrder={Gtk.SortType.ASCENDING}
|
|
178
|
+
onSortChanged={handleSort}
|
|
179
|
+
items={items.map(item => ({ id: item.id, value: item }))}
|
|
180
|
+
>
|
|
181
|
+
<GtkColumnViewColumn
|
|
182
|
+
title="Name"
|
|
183
|
+
id="name"
|
|
184
|
+
expand
|
|
185
|
+
resizable
|
|
186
|
+
sortable
|
|
187
|
+
renderCell={(item: Item) => <GtkLabel label={item.name} />}
|
|
188
|
+
/>
|
|
189
|
+
<GtkColumnViewColumn
|
|
190
|
+
title="Size"
|
|
191
|
+
id="size"
|
|
192
|
+
fixedWidth={100}
|
|
193
|
+
renderCell={(item: Item) => <GtkLabel label={`${item.size} KB`} />}
|
|
194
|
+
/>
|
|
195
|
+
</GtkColumnView>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### GtkDropDown
|
|
199
|
+
Selection dropdown.
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
<GtkDropDown
|
|
203
|
+
selectedId={selectedId}
|
|
204
|
+
onSelectionChanged={setSelectedId}
|
|
205
|
+
items={options.map(opt => ({ id: opt.id, value: opt.label }))}
|
|
206
|
+
/>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### GtkListView (tree mode)
|
|
210
|
+
Hierarchical tree with expand/collapse. Items with nested `children` arrays trigger tree behavior.
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
<GtkListView
|
|
214
|
+
estimatedItemHeight={48}
|
|
215
|
+
vexpand
|
|
216
|
+
autoexpand={false}
|
|
217
|
+
selectionMode={Gtk.SelectionMode.SINGLE}
|
|
218
|
+
selected={selectedId ? [selectedId] : []}
|
|
219
|
+
onSelectionChanged={(ids) => setSelectedId(ids[0])}
|
|
220
|
+
items={files.map(file => ({
|
|
221
|
+
id: file.id,
|
|
222
|
+
value: file,
|
|
223
|
+
children: file.children?.map(child => ({ id: child.id, value: child })),
|
|
224
|
+
}))}
|
|
225
|
+
renderItem={(item: FileNode, row) => (
|
|
226
|
+
<GtkBox spacing={8}>
|
|
227
|
+
<GtkImage iconName={item.isDirectory ? "folder-symbolic" : "text-x-generic-symbolic"} />
|
|
228
|
+
<GtkLabel label={item.name} />
|
|
229
|
+
</GtkBox>
|
|
230
|
+
)}
|
|
231
|
+
/>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**ListItem data props:** `id`, `value`, `children` (nested items for tree mode), `hideExpander`, `indentForDepth`, `indentForIcon`, `section` (for sectioned lists)
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Inputs
|
|
239
|
+
|
|
240
|
+
### GtkEntry
|
|
241
|
+
Single-line text input. **Requires two-way binding.**
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
const [text, setText] = useState("");
|
|
245
|
+
<GtkEntry text={text} onChanged={(e) => setText(e.getText())} placeholderText="Enter text..." />
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### GtkToggleButton
|
|
249
|
+
Toggle button. Auto-prevents signal feedback loops.
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
<GtkToggleButton active={isActive} onToggled={() => setIsActive(!isActive)} label="Toggle" />
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### GtkCheckButton
|
|
256
|
+
Checkbox.
|
|
257
|
+
|
|
258
|
+
```tsx
|
|
259
|
+
<GtkCheckButton active={checked} onToggled={() => setChecked(!checked)} label="Option" />
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### GtkSwitch
|
|
263
|
+
On/off switch.
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
<GtkSwitch active={enabled} onStateSet={() => { setEnabled(!enabled); return true; }} />
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### GtkSpinButton
|
|
270
|
+
Numeric input with increment/decrement. Build the `adjustment` with the `useAdjustment` hook from `@gtkx/react`.
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
const adjustment = useAdjustment({ value: count, lower: 0, upper: 100, stepIncrement: 1 });
|
|
274
|
+
|
|
275
|
+
<GtkSpinButton adjustment={adjustment} onValueChanged={(self) => setCount(self.getValue())} />
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### GtkScale
|
|
279
|
+
Slider with an adjustment and optional marks.
|
|
280
|
+
|
|
281
|
+
```tsx
|
|
282
|
+
const adjustment = useAdjustment({ value: volume, lower: 0, upper: 100, stepIncrement: 1 });
|
|
283
|
+
|
|
284
|
+
<GtkScale
|
|
285
|
+
drawValue
|
|
286
|
+
valuePos={Gtk.PositionType.TOP}
|
|
287
|
+
adjustment={adjustment}
|
|
288
|
+
onValueChanged={(self) => setVolume(self.getValue())}
|
|
289
|
+
marks={[
|
|
290
|
+
{ value: 0, label: "Min", position: Gtk.PositionType.BOTTOM },
|
|
291
|
+
{ value: 50, position: Gtk.PositionType.BOTTOM },
|
|
292
|
+
{ value: 100, label: "Max", position: Gtk.PositionType.BOTTOM },
|
|
293
|
+
]}
|
|
294
|
+
/>
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**`useAdjustment` config:** `value`, `lower`, `upper`, `stepIncrement`, `pageIncrement`, `pageSize`
|
|
298
|
+
**ScaleMark type:** `{ value: number, position?: Gtk.PositionType, label?: string }`
|
|
299
|
+
|
|
300
|
+
### GtkCalendar
|
|
301
|
+
Date picker with markable days.
|
|
302
|
+
|
|
303
|
+
```tsx
|
|
304
|
+
<GtkCalendar
|
|
305
|
+
onDaySelected={(cal) => setDate(cal.getDate())}
|
|
306
|
+
markedDays={[15, 20, 25]}
|
|
307
|
+
/>
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### GtkLevelBar
|
|
311
|
+
Progress/level indicator with customizable thresholds.
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
<GtkLevelBar
|
|
315
|
+
value={0.6}
|
|
316
|
+
offsets={[
|
|
317
|
+
{ id: "low", value: 0.25 },
|
|
318
|
+
{ id: "high", value: 0.75 },
|
|
319
|
+
{ id: "full", value: 1.0 },
|
|
320
|
+
]}
|
|
321
|
+
/>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
**LevelBarOffset type:** `{ id: string, value: number }`
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Display
|
|
329
|
+
|
|
330
|
+
### GtkLabel
|
|
331
|
+
```tsx
|
|
332
|
+
<GtkLabel label="Text" halign={Gtk.Align.START} wrap useMarkup />
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### GtkButton
|
|
336
|
+
```tsx
|
|
337
|
+
<GtkButton label="Click" onClicked={handleClick} iconName="document-new-symbolic" />
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### GtkImage
|
|
341
|
+
```tsx
|
|
342
|
+
<GtkImage iconName="folder-symbolic" pixelSize={48} />
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## Header & Action Bars
|
|
348
|
+
|
|
349
|
+
### GtkHeaderBar
|
|
350
|
+
Title bar with packed widgets. Pass packed widgets through the `packStart`/`packEnd` props and the title through `titleWidget`.
|
|
351
|
+
|
|
352
|
+
```tsx
|
|
353
|
+
<GtkHeaderBar
|
|
354
|
+
packStart={<GtkButton iconName="go-previous-symbolic" />}
|
|
355
|
+
titleWidget={<GtkLabel label="Title" cssClasses={["title"]} />}
|
|
356
|
+
packEnd={<GtkMenuButton iconName="open-menu-symbolic" />}
|
|
357
|
+
/>
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### GtkActionBar
|
|
361
|
+
Bottom action bar.
|
|
362
|
+
|
|
363
|
+
```tsx
|
|
364
|
+
<GtkActionBar
|
|
365
|
+
packStart={<GtkButton label="Cancel" />}
|
|
366
|
+
packEnd={<GtkButton label="Save" cssClasses={["suggested-action"]} />}
|
|
367
|
+
/>
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## Menus
|
|
373
|
+
|
|
374
|
+
Menus are data. `<GMenu>` (from `@gtkx/jsx/gio`) takes an `items` array of `MenuEntry` objects (`{ label?, action?, submenu?, section? }`, exported by `@gtkx/react`). Each leaf entry triggers a named action; declare the actions as `<GSimpleAction>` elements (also from `@gtkx/jsx/gio`) through the window's `addAction` prop (scope `win.`) or as children of the application (scope `app.`).
|
|
375
|
+
|
|
376
|
+
### GtkMenuButton with a menu model
|
|
377
|
+
|
|
378
|
+
```tsx
|
|
379
|
+
<GtkMenuButton
|
|
380
|
+
iconName="open-menu-symbolic"
|
|
381
|
+
menuModel={
|
|
382
|
+
<GMenu
|
|
383
|
+
items={[
|
|
384
|
+
{ label: "_New", action: "win.new" },
|
|
385
|
+
{ label: "_Open", action: "win.open" },
|
|
386
|
+
{
|
|
387
|
+
label: "Export",
|
|
388
|
+
submenu: [
|
|
389
|
+
{ label: "PDF", action: "win.export-pdf" },
|
|
390
|
+
{ label: "CSV", action: "win.export-csv" },
|
|
391
|
+
],
|
|
392
|
+
},
|
|
393
|
+
{ section: [{ label: "_Quit", action: "app.quit" }] },
|
|
394
|
+
]}
|
|
395
|
+
/>
|
|
396
|
+
}
|
|
397
|
+
/>
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Declaring the actions
|
|
401
|
+
|
|
402
|
+
```tsx
|
|
403
|
+
<GtkApplicationWindow
|
|
404
|
+
addAction={
|
|
405
|
+
<>
|
|
406
|
+
<GSimpleAction name="new" onActivate={handleNew} accels="<Control>n" />
|
|
407
|
+
<GSimpleAction name="open" onActivate={handleOpen} accels="<Control>o" />
|
|
408
|
+
<GSimpleAction name="export-pdf" onActivate={exportPdf} />
|
|
409
|
+
<GSimpleAction name="export-csv" onActivate={exportCsv} />
|
|
410
|
+
</>
|
|
411
|
+
}
|
|
412
|
+
>
|
|
413
|
+
{/* ... */}
|
|
414
|
+
</GtkApplicationWindow>
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
**MenuEntry fields:** `label` (underscore marks the mnemonic), `action` (detailed action name, e.g. `"win.open"`), `submenu` (nested `MenuEntry[]`), `section` (inline group of `MenuEntry[]`, with `label` as the heading)
|
|
418
|
+
|
|
419
|
+
**GSimpleAction props:** `name` (required), `onActivate`, `accels` (e.g., `"<Control>n"`), `enabled`
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## Windows
|
|
424
|
+
|
|
425
|
+
### GtkApplicationWindow
|
|
426
|
+
|
|
427
|
+
Handle `onCloseRequest` and return `true` to veto GTK's native close so React state unmounts the window.
|
|
428
|
+
|
|
429
|
+
```tsx
|
|
430
|
+
<GtkApplicationWindow
|
|
431
|
+
title="App"
|
|
432
|
+
defaultWidth={800}
|
|
433
|
+
defaultHeight={600}
|
|
434
|
+
onCloseRequest={() => {
|
|
435
|
+
quit();
|
|
436
|
+
return true;
|
|
437
|
+
}}
|
|
438
|
+
>
|
|
439
|
+
<Content />
|
|
440
|
+
</GtkApplicationWindow>
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
Custom titlebar:
|
|
444
|
+
```tsx
|
|
445
|
+
<GtkApplicationWindow ... titlebar={<GtkHeaderBar />}>
|
|
446
|
+
<Content />
|
|
447
|
+
</GtkApplicationWindow>
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## Adwaita (Libadwaita)
|
|
453
|
+
|
|
454
|
+
Import: `import * as Adw from "@gtkx/gi/adw";`
|
|
455
|
+
|
|
456
|
+
### AdwApplicationWindow + AdwToolbarView
|
|
457
|
+
Modern app structure.
|
|
458
|
+
|
|
459
|
+
```tsx
|
|
460
|
+
<AdwApplicationWindow
|
|
461
|
+
title="App"
|
|
462
|
+
defaultWidth={800}
|
|
463
|
+
defaultHeight={600}
|
|
464
|
+
onCloseRequest={() => {
|
|
465
|
+
quit();
|
|
466
|
+
return true;
|
|
467
|
+
}}
|
|
468
|
+
>
|
|
469
|
+
<AdwToolbarView
|
|
470
|
+
addTopBar={
|
|
471
|
+
<AdwHeaderBar titleWidget={<AdwWindowTitle title="App" subtitle="Description" />} />
|
|
472
|
+
}
|
|
473
|
+
addBottomBar={<GtkActionBar />}
|
|
474
|
+
>
|
|
475
|
+
<MainContent />
|
|
476
|
+
</AdwToolbarView>
|
|
477
|
+
</AdwApplicationWindow>
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### AdwStatusPage
|
|
481
|
+
Welcome, error, or empty state.
|
|
482
|
+
|
|
483
|
+
```tsx
|
|
484
|
+
<AdwStatusPage iconName="applications-system-symbolic" title="Welcome" description="Get started" vexpand>
|
|
485
|
+
<GtkButton label="Start" cssClasses={["suggested-action", "pill"]} halign={Gtk.Align.CENTER} />
|
|
486
|
+
</AdwStatusPage>
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### AdwBanner
|
|
490
|
+
Dismissable notification.
|
|
491
|
+
|
|
492
|
+
```tsx
|
|
493
|
+
<AdwBanner title="Update available" buttonLabel="Dismiss" revealed={show} onButtonClicked={() => setShow(false)} />
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### AdwPreferencesPage / AdwPreferencesGroup
|
|
497
|
+
Settings UI.
|
|
498
|
+
|
|
499
|
+
```tsx
|
|
500
|
+
<AdwPreferencesPage title="Settings">
|
|
501
|
+
<AdwPreferencesGroup title="Appearance" description="Customize look">
|
|
502
|
+
<AdwSwitchRow title="Dark Mode" active={dark} onNotifyActive={(active) => setDark(active ?? false)} />
|
|
503
|
+
<AdwActionRow
|
|
504
|
+
title="Theme"
|
|
505
|
+
subtitle="Select color"
|
|
506
|
+
addPrefix={<GtkImage iconName="preferences-color-symbolic" />}
|
|
507
|
+
addSuffix={<GtkImage iconName="go-next-symbolic" valign={Gtk.Align.CENTER} />}
|
|
508
|
+
/>
|
|
509
|
+
</AdwPreferencesGroup>
|
|
510
|
+
</AdwPreferencesPage>
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
**ActionRow children:** Use the `addPrefix` prop for left widgets, `addSuffix` for right widgets, or the `activatableWidget` prop for a clickable suffix.
|
|
514
|
+
|
|
515
|
+
### AdwExpanderRow
|
|
516
|
+
Expandable settings row with optional action widget.
|
|
517
|
+
|
|
518
|
+
```tsx
|
|
519
|
+
<AdwExpanderRow
|
|
520
|
+
title="Advanced"
|
|
521
|
+
subtitle="More options"
|
|
522
|
+
addAction={<GtkButton iconName="emblem-system-symbolic" cssClasses={["flat"]} />}
|
|
523
|
+
addRow={
|
|
524
|
+
<>
|
|
525
|
+
<AdwSwitchRow title="Option 1" active />
|
|
526
|
+
<AdwSwitchRow title="Option 2" />
|
|
527
|
+
</>
|
|
528
|
+
}
|
|
529
|
+
/>
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
**ExpanderRow slots:** the `addRow` prop for nested rows, the `addAction` prop for the header action widget. Direct children also work for simple cases.
|
|
533
|
+
|
|
534
|
+
### AdwEntryRow / AdwPasswordEntryRow
|
|
535
|
+
Input in list row.
|
|
536
|
+
|
|
537
|
+
```tsx
|
|
538
|
+
<AdwEntryRow title="Username" text={username} onChanged={(e) => setUsername(e.getText())} />
|
|
539
|
+
<AdwPasswordEntryRow title="Password" />
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
### AdwToggleGroup
|
|
543
|
+
Segmented button group for mutually exclusive options, built from `AdwToggle` children.
|
|
544
|
+
|
|
545
|
+
```tsx
|
|
546
|
+
const [mode, setMode] = useState("list");
|
|
547
|
+
|
|
548
|
+
<AdwToggleGroup activeName={mode} onNotifyActiveName={(name) => setMode(name ?? "list")}>
|
|
549
|
+
<AdwToggle name="list" iconName="view-list-symbolic" tooltip="List View" />
|
|
550
|
+
<AdwToggle name="grid" iconName="view-grid-symbolic" tooltip="Grid View" />
|
|
551
|
+
<AdwToggle name="flow" label="Flow" />
|
|
552
|
+
</AdwToggleGroup>
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
**ToggleGroup props:** `activeName`, `active` (index), `onNotifyActiveName`
|
|
556
|
+
|
|
557
|
+
**AdwToggle props:** `name`, `label`, `iconName`, `tooltip`, `enabled`
|
|
558
|
+
|
|
559
|
+
### AdwNavigationView
|
|
560
|
+
Push/pop navigation. Each `AdwNavigationPage` carries a `tag`; the view pushes a page when it mounts and pops it when it unmounts. Drive the active page with `visiblePageTag` and react to back navigation through `onPopped`.
|
|
561
|
+
|
|
562
|
+
```tsx
|
|
563
|
+
const [detail, setDetail] = useState(false);
|
|
564
|
+
|
|
565
|
+
<AdwNavigationView visiblePageTag={detail ? "details" : "home"} onPopped={() => setDetail(false)}>
|
|
566
|
+
<AdwNavigationPage tag="home" title="Home">
|
|
567
|
+
<GtkButton label="Go to Details" onClicked={() => setDetail(true)} />
|
|
568
|
+
</AdwNavigationPage>
|
|
569
|
+
{detail && (
|
|
570
|
+
<AdwNavigationPage tag="details" title="Details">
|
|
571
|
+
<GtkLabel label="Details content" />
|
|
572
|
+
</AdwNavigationPage>
|
|
573
|
+
)}
|
|
574
|
+
</AdwNavigationView>
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
**AdwNavigationPage props:** `tag`, `title`, `canPop`. The header bar inside a page shows a back button automatically when there is a page to pop.
|
|
578
|
+
|
|
579
|
+
### AdwNavigationSplitView
|
|
580
|
+
Sidebar/content split layout for master-detail interfaces. Each pane is an `AdwNavigationPage` passed through the `sidebar` and `content` slot props.
|
|
581
|
+
|
|
582
|
+
```tsx
|
|
583
|
+
const [selected, setSelected] = useState(items[0]);
|
|
584
|
+
|
|
585
|
+
<AdwNavigationSplitView
|
|
586
|
+
sidebarWidthFraction={0.33}
|
|
587
|
+
minSidebarWidth={200}
|
|
588
|
+
maxSidebarWidth={300}
|
|
589
|
+
sidebar={
|
|
590
|
+
<AdwNavigationPage title="Sidebar">
|
|
591
|
+
<AdwToolbarView addTopBar={<AdwHeaderBar />}>
|
|
592
|
+
<GtkListBox cssClasses={["navigation-sidebar"]} onRowSelected={(row) => {
|
|
593
|
+
if (!row) return;
|
|
594
|
+
const item = items[row.getIndex()];
|
|
595
|
+
if (item) setSelected(item);
|
|
596
|
+
}}>
|
|
597
|
+
{items.map((item) => <AdwActionRow key={item.id} title={item.title} />)}
|
|
598
|
+
</GtkListBox>
|
|
599
|
+
</AdwToolbarView>
|
|
600
|
+
</AdwNavigationPage>
|
|
601
|
+
}
|
|
602
|
+
content={
|
|
603
|
+
<AdwNavigationPage title={selected?.title ?? ""}>
|
|
604
|
+
<AdwToolbarView addTopBar={<AdwHeaderBar />}>
|
|
605
|
+
<GtkLabel label={selected?.title ?? ""} />
|
|
606
|
+
</AdwToolbarView>
|
|
607
|
+
</AdwNavigationPage>
|
|
608
|
+
}
|
|
609
|
+
/>
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
**Props:** `sidebarWidthFraction`, `minSidebarWidth`, `maxSidebarWidth`, `collapsed`, `showContent`, `sidebar`, `content`.
|
|
613
|
+
**Selection:** Use `GtkListBox` with `onRowSelected` (single click) not `onRowActivated` (double click).
|
|
614
|
+
|
|
615
|
+
### AdwAlertDialog
|
|
616
|
+
Modern modal alert dialogs with response buttons.
|
|
617
|
+
|
|
618
|
+
```tsx
|
|
619
|
+
const [showDialog, setShowDialog] = useState(false);
|
|
620
|
+
|
|
621
|
+
{showDialog && (
|
|
622
|
+
<AdwAlertDialog
|
|
623
|
+
heading="Delete File?"
|
|
624
|
+
body="This action cannot be undone."
|
|
625
|
+
onResponse={(id) => {
|
|
626
|
+
if (id === "delete") handleDelete();
|
|
627
|
+
setShowDialog(false);
|
|
628
|
+
}}
|
|
629
|
+
responses={[
|
|
630
|
+
{ id: "cancel", label: "Cancel" },
|
|
631
|
+
{ id: "delete", label: "Delete", appearance: Adw.ResponseAppearance.DESTRUCTIVE },
|
|
632
|
+
]}
|
|
633
|
+
/>
|
|
634
|
+
)}
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
**Response descriptor:** `{ id, label, appearance? (SUGGESTED, DESTRUCTIVE), enabled? }`
|
|
638
|
+
|
|
639
|
+
### GtkColorDialogButton / GtkFontDialogButton
|
|
640
|
+
Color and font picker buttons. The dialog is declared as a slot element (`GtkColorDialog`/`GtkFontDialog`, from `@gtkx/jsx/gtk`); observe the picked value through the `notify::` prop.
|
|
641
|
+
|
|
642
|
+
```tsx
|
|
643
|
+
<GtkColorDialogButton
|
|
644
|
+
rgba={color}
|
|
645
|
+
dialog={<GtkColorDialog title="Select Color" modal withAlpha />}
|
|
646
|
+
onNotifyRgba={(value) => value && setColor(value)}
|
|
647
|
+
/>
|
|
648
|
+
<GtkFontDialogButton
|
|
649
|
+
fontDesc={font}
|
|
650
|
+
useFont
|
|
651
|
+
useSize
|
|
652
|
+
dialog={<GtkFontDialog title="Select Font" modal />}
|
|
653
|
+
onNotifyFontDesc={(value) => value && setFont(value)}
|
|
654
|
+
/>
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
### Other Adwaita Widgets
|
|
658
|
+
|
|
659
|
+
| Widget | Description |
|
|
660
|
+
|--------|-------------|
|
|
661
|
+
| `AdwClamp` | Limits content width (`maximumSize={600}`) |
|
|
662
|
+
| `AdwAvatar` | User avatar (`size={48} text="Name" showInitials`) |
|
|
663
|
+
| `AdwSpinner` | Loading indicator |
|
|
664
|
+
| `AdwWindowTitle` | Title + subtitle for header bars |
|
|
665
|
+
| `AdwButtonRow` | Button styled as list row |
|
|
666
|
+
|
|
667
|
+
---
|
|
668
|
+
|
|
669
|
+
## Animations
|
|
670
|
+
|
|
671
|
+
Wrap widgets in `AdwTimedAnimation` (duration + easing) or `AdwSpringAnimation`
|
|
672
|
+
(physics: damping, stiffness, mass) for declarative animations.
|
|
673
|
+
|
|
674
|
+
Spring animation:
|
|
675
|
+
```tsx
|
|
676
|
+
<AdwSpringAnimation
|
|
677
|
+
initial={{ opacity: 0, scale: 0.8 }}
|
|
678
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
679
|
+
damping={0.8}
|
|
680
|
+
stiffness={200}
|
|
681
|
+
mass={1}
|
|
682
|
+
animateOnMount
|
|
683
|
+
onAnimationComplete={() => console.log("done")}
|
|
684
|
+
>
|
|
685
|
+
<GtkBox>...</GtkBox>
|
|
686
|
+
</AdwSpringAnimation>
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
Timed animation:
|
|
690
|
+
```tsx
|
|
691
|
+
<AdwTimedAnimation
|
|
692
|
+
initial={{ opacity: 0 }}
|
|
693
|
+
animate={{ opacity: 1 }}
|
|
694
|
+
duration={300}
|
|
695
|
+
easing={Adw.Easing.EASE_OUT_CUBIC}
|
|
696
|
+
animateOnMount
|
|
697
|
+
>
|
|
698
|
+
<GtkLabel label="Fade in" />
|
|
699
|
+
</AdwTimedAnimation>
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
**Shared props:** `initial`, `animate`, `exit`, `animateOnMount`, `onAnimationStart`, `onAnimationComplete`
|
|
703
|
+
|
|
704
|
+
**AdwSpringAnimation props:** `damping`, `stiffness`, `mass`, `initialVelocity`, `clamp`
|
|
705
|
+
|
|
706
|
+
**AdwTimedAnimation props:** `duration`, `easing` (from `Adw.Easing`), `repeat`, `reverse`, `alternate`
|
|
707
|
+
|
|
708
|
+
---
|
|
709
|
+
|
|
710
|
+
## Drag and Drop
|
|
711
|
+
|
|
712
|
+
Drag and drop uses the `GtkDragSource` and `GtkDropTarget` event controllers, attached through a widget's `addController` prop. `GtkDragSource` provides content from `onPrepare` and takes its drag icon through the `icon` object prop; `GtkDropTarget` declares accepted `types` and handles `onDrop`.
|
|
713
|
+
|
|
714
|
+
```tsx
|
|
715
|
+
import * as Gdk from "@gtkx/gi/gdk";
|
|
716
|
+
import { Type, Value } from "@gtkx/gi/gobject";
|
|
717
|
+
import { GtkBox, GtkButton, GtkDragSource, GtkDropTarget, GtkLabel } from "@gtkx/jsx/gtk";
|
|
718
|
+
|
|
719
|
+
const DraggableButton = ({ label }: { label: string }) => (
|
|
720
|
+
<GtkButton
|
|
721
|
+
label={label}
|
|
722
|
+
addController={
|
|
723
|
+
<GtkDragSource
|
|
724
|
+
actions={Gdk.DragAction.COPY}
|
|
725
|
+
onPrepare={() => Gdk.ContentProvider.newForValue(Value.newFromString(label))}
|
|
726
|
+
icon={{ paintable: someTexture, hotX: 16, hotY: 16 }}
|
|
727
|
+
/>
|
|
728
|
+
}
|
|
729
|
+
/>
|
|
730
|
+
);
|
|
731
|
+
|
|
732
|
+
const DropZone = () => {
|
|
733
|
+
const [dropped, setDropped] = useState<string | null>(null);
|
|
734
|
+
return (
|
|
735
|
+
<GtkBox
|
|
736
|
+
addController={
|
|
737
|
+
<GtkDropTarget
|
|
738
|
+
types={[Type.STRING]}
|
|
739
|
+
actions={Gdk.DragAction.COPY}
|
|
740
|
+
onDrop={(value: Value) => { setDropped(value.getString()); return true; }}
|
|
741
|
+
/>
|
|
742
|
+
}
|
|
743
|
+
>
|
|
744
|
+
<GtkLabel label={dropped ?? "Drop here"} />
|
|
745
|
+
</GtkBox>
|
|
746
|
+
);
|
|
747
|
+
};
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
**GtkDragSource props:** `actions`, `content`, `onPrepare`, `onDragBegin`, `onDragEnd`, `onDragCancel`, `icon` (`{ paintable, hotX?, hotY? }`)
|
|
751
|
+
|
|
752
|
+
**GtkDropTarget props:** `types`, `actions`, `preload`, `onAccept`, `onEnter`, `onMotion`, `onLeave`, `onDrop`
|
|
753
|
+
|
|
754
|
+
## GValue Factories
|
|
755
|
+
|
|
756
|
+
Create typed values for drag-and-drop and signal emission:
|
|
757
|
+
|
|
758
|
+
| Factory | Description |
|
|
759
|
+
| ------------------------------ | ----------------------------- |
|
|
760
|
+
| `Value.newFromString(str)` | String values |
|
|
761
|
+
| `Value.newFromDouble(num)` | 64-bit floating point |
|
|
762
|
+
| `Value.newFromInt(num)` | 32-bit signed integer |
|
|
763
|
+
| `Value.newFromBoolean(bool)` | Boolean values |
|
|
764
|
+
| `Value.newFromObject(obj)` | GObject instances |
|
|
765
|
+
| `Value.newFromBoxed(boxed)` | Boxed types (Gdk.RGBA, etc.) |
|
|
766
|
+
| `Value.newFromEnum(gtype, n)` | Enum values (requires GType) |
|
|
767
|
+
| `Value.newFromFlags(gtype, n)` | Flags values (requires GType) |
|
|
768
|
+
|
|
769
|
+
Type constants for `types`: `Type.STRING`, `Type.INT`, `Type.DOUBLE`, `Type.BOOLEAN`, `Type.OBJECT`.
|
|
770
|
+
|
|
771
|
+
## Custom Drawing
|
|
772
|
+
|
|
773
|
+
Render custom graphics with `GtkDrawingArea` (from `@gtkx/jsx/gtk`) using the `drawFunc` prop, with the GIR signature `(self, cr, width, height)`:
|
|
774
|
+
|
|
775
|
+
```tsx
|
|
776
|
+
import type * as Gtk from "@gtkx/gi/gtk";
|
|
777
|
+
import type { Context } from "@gtkx/gi/cairo";
|
|
778
|
+
import { GtkDrawingArea } from "@gtkx/jsx/gtk";
|
|
779
|
+
|
|
780
|
+
const Canvas = () => {
|
|
781
|
+
const handleDraw = (self: Gtk.DrawingArea, cr: Context, width: number, height: number) => {
|
|
782
|
+
cr.setSourceRgb(0.2, 0.4, 0.8);
|
|
783
|
+
cr.rectangle(10, 10, width - 20, height - 20);
|
|
784
|
+
cr.fill();
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
return <GtkDrawingArea contentWidth={400} contentHeight={300} drawFunc={handleDraw} />;
|
|
788
|
+
};
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
Changing the `drawFunc` callback identity automatically queues a redraw. Attach a `GtkGestureDrag` through the `addController` prop for interactive drawing.
|
|
792
|
+
|
|
793
|
+
## Event Controllers
|
|
794
|
+
|
|
795
|
+
Event controllers attach through any widget's `addController` prop (wrap several in a fragment). They are auto-generated from GTK's introspection data.
|
|
796
|
+
|
|
797
|
+
```tsx
|
|
798
|
+
<GtkBox
|
|
799
|
+
focusable
|
|
800
|
+
addController={
|
|
801
|
+
<>
|
|
802
|
+
<GtkEventControllerMotion
|
|
803
|
+
onEnter={(x, y) => console.log("Entered at", x, y)}
|
|
804
|
+
onMotion={(x, y) => setPosition({ x, y })}
|
|
805
|
+
onLeave={() => console.log("Left")}
|
|
806
|
+
/>
|
|
807
|
+
<GtkEventControllerKey
|
|
808
|
+
onKeyPressed={(keyval, keycode, state) => {
|
|
809
|
+
console.log("Key pressed:", keyval);
|
|
810
|
+
return false;
|
|
811
|
+
}}
|
|
812
|
+
/>
|
|
813
|
+
<GtkGestureClick onPressed={(nPress, x, y) => console.log("Clicked")} />
|
|
814
|
+
</>
|
|
815
|
+
}
|
|
816
|
+
>
|
|
817
|
+
<GtkLabel label="Hover or type here" />
|
|
818
|
+
</GtkBox>
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
**Input controllers:** `GtkEventControllerMotion`, `GtkEventControllerKey`, `GtkEventControllerScroll`, `GtkEventControllerFocus`
|
|
822
|
+
|
|
823
|
+
**Gesture controllers:** `GtkGestureClick`, `GtkGestureDrag`, `GtkGestureLongPress`, `GtkGestureZoom`, `GtkGestureRotate`, `GtkGestureSwipe`, `GtkGestureStylus`, `GtkGesturePan`
|
|
824
|
+
|
|
825
|
+
**Drag-and-drop:** `GtkDragSource`, `GtkDropTarget`, `GtkDropControllerMotion`
|
|
826
|
+
|
|
827
|
+
## SearchBar
|
|
828
|
+
|
|
829
|
+
```tsx
|
|
830
|
+
const [searchActive, setSearchActive] = useState(false);
|
|
831
|
+
|
|
832
|
+
<GtkSearchBar searchModeEnabled={searchActive} onNotifySearchModeEnabled={(enabled) => setSearchActive(enabled ?? false)}>
|
|
833
|
+
<GtkSearchEntry text={query} onSearchChanged={(entry) => setQuery(entry.getText())} />
|
|
834
|
+
</GtkSearchBar>
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
The `onNotifySearchModeEnabled` callback fires when search mode changes (e.g., user presses Escape).
|
|
838
|
+
|
|
839
|
+
## TextView / SourceView
|
|
840
|
+
|
|
841
|
+
Text content lives in an explicit `GtkTextBuffer` element passed through the view's `buffer` prop. Text nodes are allowed only inside `GtkLabel` and buffer elements. Use `GtkTextTag` for formatting and `GtkTextAnchor` for embedded widgets.
|
|
842
|
+
|
|
843
|
+
```tsx
|
|
844
|
+
<GtkTextView
|
|
845
|
+
wrapMode={Gtk.WrapMode.WORD}
|
|
846
|
+
buffer={
|
|
847
|
+
<GtkTextBuffer
|
|
848
|
+
enableUndo
|
|
849
|
+
onChanged={(buffer) => {
|
|
850
|
+
const text = buffer.getText(buffer.getStartIter(), buffer.getEndIter(), false);
|
|
851
|
+
console.log(text);
|
|
852
|
+
}}
|
|
853
|
+
>
|
|
854
|
+
Normal text, <GtkTextTag id="bold" weight={Pango.Weight.BOLD}>bold</GtkTextTag>, and
|
|
855
|
+
<GtkTextAnchor><GtkButton label="Click" /></GtkTextAnchor> inline.
|
|
856
|
+
</GtkTextBuffer>
|
|
857
|
+
}
|
|
858
|
+
/>
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
**GtkTextBuffer props:** `enableUndo`, `onChanged`, `onInsertText`, `onDeleteRange`, `onNotifyCanUndo`, `onNotifyCanRedo`
|
|
862
|
+
|
|
863
|
+
**GtkTextTag props:** `id` (required), `priority`, `foreground`, `background`, `weight`, `style`, `underline`, `strikethrough`, `family`, `size`, `sizePoints`, `scale`, `justification`, `leftMargin`, `rightMargin`, `indent`, `editable`, `invisible`
|
|
864
|
+
|
|
865
|
+
**GtkTextAnchor:** Embeds widgets inline with `children`
|
|
866
|
+
|
|
867
|
+
**GtkTextPaintable:** Embeds images inline with the `paintable` prop
|
|
868
|
+
|
|
869
|
+
`GtkSourceView` (from `@gtkx/jsx/gtksource`) pairs with `GtkSourceBuffer` the same way; language and style scheme objects come from `@gtkx/gi/gtksource`:
|
|
870
|
+
|
|
871
|
+
```tsx
|
|
872
|
+
import * as GtkSource from "@gtkx/gi/gtksource";
|
|
873
|
+
import { GtkSourceBuffer, GtkSourceView } from "@gtkx/jsx/gtksource";
|
|
874
|
+
|
|
875
|
+
<GtkSourceView
|
|
876
|
+
showLineNumbers
|
|
877
|
+
highlightCurrentLine
|
|
878
|
+
monospace
|
|
879
|
+
buffer={
|
|
880
|
+
<GtkSourceBuffer
|
|
881
|
+
language={GtkSource.LanguageManager.getDefault().getLanguage("typescript-jsx")}
|
|
882
|
+
styleScheme={GtkSource.StyleSchemeManager.getDefault().getScheme("Adwaita-dark")}
|
|
883
|
+
onChanged={(buffer) => setCode(buffer.getText(buffer.getStartIter(), buffer.getEndIter(), false))}
|
|
884
|
+
>
|
|
885
|
+
{code}
|
|
886
|
+
</GtkSourceBuffer>
|
|
887
|
+
}
|
|
888
|
+
/>
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
**GtkSourceBuffer additional props:** `language`, `styleScheme`, `highlightSyntax`, `highlightMatchingBrackets`, `implicitTrailingNewline`, `onCursorMoved`, `onHighlightUpdated`
|
|
892
|
+
|
|
893
|
+
## Keyboard Shortcuts
|
|
894
|
+
|
|
895
|
+
Pass `<GtkShortcut>` elements through a `<GtkShortcutController>`'s `addShortcut` prop, and attach the controller through the widget's `addController` prop. Each shortcut pairs a `trigger` (`Gtk.ShortcutTrigger.parseString`) with an `action` (`Gtk.CallbackAction.new`, returning `true` when handled):
|
|
896
|
+
|
|
897
|
+
```tsx
|
|
898
|
+
<GtkBox
|
|
899
|
+
orientation={Gtk.Orientation.VERTICAL}
|
|
900
|
+
spacing={12}
|
|
901
|
+
focusable
|
|
902
|
+
addController={
|
|
903
|
+
<GtkShortcutController
|
|
904
|
+
scope={Gtk.ShortcutScope.LOCAL}
|
|
905
|
+
addShortcut={
|
|
906
|
+
<>
|
|
907
|
+
<GtkShortcut
|
|
908
|
+
trigger={Gtk.ShortcutTrigger.parseString("<Control>equal")}
|
|
909
|
+
action={Gtk.CallbackAction.new(() => {
|
|
910
|
+
setCount((c) => c + 1);
|
|
911
|
+
return true;
|
|
912
|
+
})}
|
|
913
|
+
/>
|
|
914
|
+
<GtkShortcut
|
|
915
|
+
trigger={Gtk.ShortcutTrigger.parseString("<Control>minus")}
|
|
916
|
+
action={Gtk.CallbackAction.new(() => {
|
|
917
|
+
setCount((c) => c - 1);
|
|
918
|
+
return true;
|
|
919
|
+
})}
|
|
920
|
+
/>
|
|
921
|
+
</>
|
|
922
|
+
}
|
|
923
|
+
/>
|
|
924
|
+
}
|
|
925
|
+
>
|
|
926
|
+
<GtkLabel label={`Count: ${count}`} />
|
|
927
|
+
</GtkBox>
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
**Scopes:** `LOCAL` (attached widget focus), `MANAGED` (parent managed), `GLOBAL` (window-wide)
|
|
931
|
+
|
|
932
|
+
**Trigger strings for `parseString`:** `<Control>s`, `<Control><Shift>s`, `<Alt>F4`, `<Primary>q`, `F5`
|
|
933
|
+
|
|
934
|
+
**Multiple triggers:** combine with `Gtk.AlternativeTrigger.new(triggerA, triggerB)`
|