@gtkx/cli 0.9.3 → 0.10.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/bin/gtkx.js +2 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -0
- package/dist/create.d.ts +37 -6
- package/dist/create.js +44 -844
- package/dist/dev-server.d.ts +26 -4
- package/dist/dev-server.js +64 -9
- package/dist/refresh-runtime.d.ts +9 -0
- package/dist/refresh-runtime.js +44 -0
- package/dist/templates.d.ts +8 -0
- package/dist/templates.js +18 -0
- package/dist/vite-plugin-gtkx-refresh.d.ts +7 -0
- package/dist/vite-plugin-gtkx-refresh.js +36 -0
- package/dist/vite-plugin-swc-ssr-refresh.d.ts +7 -0
- package/dist/vite-plugin-swc-ssr-refresh.js +45 -0
- package/package.json +14 -5
- package/templates/claude/EXAMPLES.md.ejs +364 -0
- package/templates/claude/SKILL.md.ejs +372 -0
- package/templates/claude/WIDGETS.md.ejs +531 -0
- package/templates/config/jest.config.js.ejs +19 -0
- package/templates/config/vitest.config.ts.ejs +13 -0
- package/templates/gitignore.ejs +4 -0
- package/templates/package.json.ejs +15 -0
- package/templates/src/app.tsx.ejs +17 -0
- package/templates/src/dev.tsx.ejs +5 -0
- package/templates/src/index.tsx.ejs +4 -0
- package/templates/tests/app.test.tsx.ejs +27 -0
- package/templates/tsconfig.json.ejs +14 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: developing-gtkx-apps
|
|
3
|
+
description: Build GTK4 desktop applications with GTKX React framework. Use when creating GTKX components, working with GTK widgets, handling signals, or building Linux desktop UIs with React.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Developing GTKX Applications
|
|
7
|
+
|
|
8
|
+
GTKX is a React framework for building native GTK4 desktop applications on Linux. It uses a custom React reconciler to render React components as native GTK widgets.
|
|
9
|
+
|
|
10
|
+
## Quick Start
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { GtkApplicationWindow, GtkBox, GtkButton, GtkLabel, render, quit } from "@gtkx/react";
|
|
14
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
15
|
+
|
|
16
|
+
const App = () => (
|
|
17
|
+
<GtkApplicationWindow title="My App" defaultWidth={800} defaultHeight={600} onCloseRequest={quit}>
|
|
18
|
+
<GtkBox orientation={Gtk.Orientation.VERTICAL} spacing={12}>
|
|
19
|
+
Hello, GTKX!
|
|
20
|
+
<GtkButton label="Quit" onClicked={quit} />
|
|
21
|
+
</GtkBox>
|
|
22
|
+
</GtkApplicationWindow>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
render(<App />, "com.example.myapp");
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Widget Patterns
|
|
29
|
+
|
|
30
|
+
### Container Widgets
|
|
31
|
+
|
|
32
|
+
**GtkBox** - Linear layout:
|
|
33
|
+
```tsx
|
|
34
|
+
<GtkBox orientation={Gtk.Orientation.VERTICAL} spacing={12}>
|
|
35
|
+
First
|
|
36
|
+
Second
|
|
37
|
+
</GtkBox>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**GtkGrid** - 2D positioning:
|
|
41
|
+
```tsx
|
|
42
|
+
<GtkGrid rowSpacing={10} columnSpacing={10}>
|
|
43
|
+
<GridChild column={0} row={0}>Top-left</GridChild>
|
|
44
|
+
<GridChild column={1} row={0} columnSpan={2}>Spans 2 columns</GridChild>
|
|
45
|
+
</GtkGrid>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**GtkStack** - Page-based container:
|
|
49
|
+
```tsx
|
|
50
|
+
<GtkStack visibleChildName="page1">
|
|
51
|
+
<StackPage name="page1" title="Page 1">Content 1</StackPage>
|
|
52
|
+
<StackPage name="page2" title="Page 2">Content 2</StackPage>
|
|
53
|
+
</GtkStack>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**GtkNotebook** - Tabbed container:
|
|
57
|
+
```tsx
|
|
58
|
+
<GtkNotebook>
|
|
59
|
+
<Notebook.Page label="Tab 1">
|
|
60
|
+
<Content1 />
|
|
61
|
+
</Notebook.Page>
|
|
62
|
+
<Notebook.Page label="Tab 2">
|
|
63
|
+
<Content2 />
|
|
64
|
+
</Notebook.Page>
|
|
65
|
+
</GtkNotebook>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**GtkPaned** - Resizable split:
|
|
69
|
+
```tsx
|
|
70
|
+
<GtkPaned orientation={Gtk.Orientation.HORIZONTAL} position={280}>
|
|
71
|
+
<Slot for={GtkPaned} id="startChild">
|
|
72
|
+
<SideBar />
|
|
73
|
+
</Slot>
|
|
74
|
+
<Slot for={GtkPaned} id="endChild">
|
|
75
|
+
<MainContent />
|
|
76
|
+
</Slot>
|
|
77
|
+
</GtkPaned>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Virtual Scrolling Lists
|
|
81
|
+
|
|
82
|
+
**ListView** - High-performance scrollable list with selection:
|
|
83
|
+
```tsx
|
|
84
|
+
<ListView<Item>
|
|
85
|
+
vexpand
|
|
86
|
+
selected={[selectedId]}
|
|
87
|
+
selectionMode={Gtk.SelectionMode.SINGLE}
|
|
88
|
+
onSelectionChanged={(ids) => setSelectedId(ids[0])}
|
|
89
|
+
renderItem={(item) => (
|
|
90
|
+
<GtkLabel label={item?.text ?? ""} />
|
|
91
|
+
)}
|
|
92
|
+
>
|
|
93
|
+
{items.map(item => (
|
|
94
|
+
<ListItem key={item.id} id={item.id} value={item} />
|
|
95
|
+
))}
|
|
96
|
+
</ListView>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**GridView** - Grid-based virtual scrolling:
|
|
100
|
+
```tsx
|
|
101
|
+
<GridView<Item>
|
|
102
|
+
vexpand
|
|
103
|
+
minColumns={2}
|
|
104
|
+
maxColumns={4}
|
|
105
|
+
renderItem={(item) => (
|
|
106
|
+
<GtkBox orientation={Gtk.Orientation.VERTICAL}>
|
|
107
|
+
<GtkImage iconName={item?.icon ?? "image-missing"} />
|
|
108
|
+
<GtkLabel label={item?.name ?? ""} />
|
|
109
|
+
</GtkBox>
|
|
110
|
+
)}
|
|
111
|
+
>
|
|
112
|
+
{items.map(item => (
|
|
113
|
+
<ListItem key={item.id} id={item.id} value={item} />
|
|
114
|
+
))}
|
|
115
|
+
</GridView>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**GtkColumnView** - Table with sortable columns:
|
|
119
|
+
```tsx
|
|
120
|
+
<GtkColumnView
|
|
121
|
+
sortColumn="name"
|
|
122
|
+
sortOrder={Gtk.SortType.ASCENDING}
|
|
123
|
+
onSortChange={handleSort}
|
|
124
|
+
>
|
|
125
|
+
<ColumnViewColumn<Item>
|
|
126
|
+
title="Name"
|
|
127
|
+
id="name"
|
|
128
|
+
expand
|
|
129
|
+
sortable
|
|
130
|
+
renderCell={(item) => (
|
|
131
|
+
<GtkLabel label={item?.name ?? ""} />
|
|
132
|
+
)}
|
|
133
|
+
/>
|
|
134
|
+
{items.map(item => (
|
|
135
|
+
<ListItem key={item.id} id={item.id} value={item} />
|
|
136
|
+
))}
|
|
137
|
+
</GtkColumnView>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**GtkDropDown** - String selection dropdown with controlled state:
|
|
141
|
+
```tsx
|
|
142
|
+
<GtkDropDown selectedId={selectedId} onSelectionChanged={setSelectedId}>
|
|
143
|
+
{options.map(opt => (
|
|
144
|
+
<SimpleListItem key={opt.id} id={opt.id} value={opt.label} />
|
|
145
|
+
))}
|
|
146
|
+
</GtkDropDown>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**GtkOverlay** - Stack widgets on top of each other (first child is base):
|
|
150
|
+
```tsx
|
|
151
|
+
<GtkOverlay>
|
|
152
|
+
<GtkButton label="Notifications" />
|
|
153
|
+
<GtkLabel
|
|
154
|
+
label="3"
|
|
155
|
+
cssClasses={["badge"]}
|
|
156
|
+
halign={Gtk.Align.END}
|
|
157
|
+
valign={Gtk.Align.START}
|
|
158
|
+
/>
|
|
159
|
+
</GtkOverlay>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### GtkHeaderBar
|
|
163
|
+
|
|
164
|
+
Pack widgets at start and end of the title bar using Slot or Pack components:
|
|
165
|
+
```tsx
|
|
166
|
+
<GtkHeaderBar>
|
|
167
|
+
<Pack.Start>
|
|
168
|
+
<GtkButton iconName="go-previous-symbolic" />
|
|
169
|
+
</Pack.Start>
|
|
170
|
+
<Slot for={GtkHeaderBar} id="titleWidget">
|
|
171
|
+
<GtkLabel label="My App" cssClasses={["title"]} />
|
|
172
|
+
</Slot>
|
|
173
|
+
<Pack.End>
|
|
174
|
+
<GtkMenuButton iconName="open-menu-symbolic" />
|
|
175
|
+
</Pack.End>
|
|
176
|
+
</GtkHeaderBar>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### GtkActionBar
|
|
180
|
+
|
|
181
|
+
Bottom bar with packed widgets:
|
|
182
|
+
```tsx
|
|
183
|
+
<GtkActionBar>
|
|
184
|
+
<Pack.Start>
|
|
185
|
+
<GtkButton label="Cancel" />
|
|
186
|
+
</Pack.Start>
|
|
187
|
+
<Pack.End>
|
|
188
|
+
<GtkButton label="Save" cssClasses={["suggested-action"]} />
|
|
189
|
+
</Pack.End>
|
|
190
|
+
</GtkActionBar>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Controlled Input
|
|
194
|
+
|
|
195
|
+
GtkEntry requires two-way binding:
|
|
196
|
+
```tsx
|
|
197
|
+
const [text, setText] = useState("");
|
|
198
|
+
|
|
199
|
+
<GtkEntry
|
|
200
|
+
text={text}
|
|
201
|
+
onChanged={(entry) => setText(entry.getText())}
|
|
202
|
+
placeholderText="Type here..."
|
|
203
|
+
/>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Declarative Menus
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
<GtkPopoverMenu>
|
|
210
|
+
<Menu.Section>
|
|
211
|
+
<Menu.Item
|
|
212
|
+
id="new"
|
|
213
|
+
label="New"
|
|
214
|
+
onActivate={handleNew}
|
|
215
|
+
accels="<Control>n"
|
|
216
|
+
/>
|
|
217
|
+
</Menu.Section>
|
|
218
|
+
<Menu.Section>
|
|
219
|
+
<Menu.Submenu label="Export">
|
|
220
|
+
<Menu.Item id="pdf" label="PDF" onActivate={handleExportPdf} />
|
|
221
|
+
<Menu.Item id="csv" label="CSV" onActivate={handleExportCsv} />
|
|
222
|
+
</Menu.Submenu>
|
|
223
|
+
</Menu.Section>
|
|
224
|
+
<Menu.Section>
|
|
225
|
+
<Menu.Item id="quit" label="Quit" onActivate={quit} accels="<Control>q" />
|
|
226
|
+
</Menu.Section>
|
|
227
|
+
</GtkPopoverMenu>
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Signal Handling
|
|
231
|
+
|
|
232
|
+
GTK signals map to `on<SignalName>` props:
|
|
233
|
+
- `clicked` → `onClicked`
|
|
234
|
+
- `toggled` → `onToggled`
|
|
235
|
+
- `changed` → `onChanged`
|
|
236
|
+
- `notify::selected` → `onNotify` (with property name arg)
|
|
237
|
+
|
|
238
|
+
## Widget References
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
import { useRef } from "react";
|
|
242
|
+
|
|
243
|
+
const entryRef = useRef<Gtk.Entry | null>(null);
|
|
244
|
+
<GtkEntry ref={entryRef} />
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Portals
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
import { createPortal } from "@gtkx/react";
|
|
251
|
+
|
|
252
|
+
{createPortal(<GtkAboutDialog programName="My App" />)}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Adwaita (Libadwaita) Widgets
|
|
256
|
+
|
|
257
|
+
Import Adwaita widgets and enums:
|
|
258
|
+
```tsx
|
|
259
|
+
import * as Adw from "@gtkx/ffi/adw";
|
|
260
|
+
import { AdwApplicationWindow, AdwHeaderBar, AdwToolbarView, Toolbar } from "@gtkx/react";
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### AdwToolbarView
|
|
264
|
+
Modern app layout with top/bottom bars:
|
|
265
|
+
```tsx
|
|
266
|
+
<AdwToolbarView>
|
|
267
|
+
<Toolbar.Top>
|
|
268
|
+
<AdwHeaderBar>
|
|
269
|
+
<Slot for={AdwHeaderBar} id="titleWidget">
|
|
270
|
+
<AdwWindowTitle title="App" subtitle="Description" />
|
|
271
|
+
</Slot>
|
|
272
|
+
</AdwHeaderBar>
|
|
273
|
+
</Toolbar.Top>
|
|
274
|
+
<MainContent />
|
|
275
|
+
<Toolbar.Bottom>
|
|
276
|
+
<GtkActionBar>...</GtkActionBar>
|
|
277
|
+
</Toolbar.Bottom>
|
|
278
|
+
</AdwToolbarView>
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### AdwApplicationWindow
|
|
282
|
+
Modern window requiring content slot:
|
|
283
|
+
```tsx
|
|
284
|
+
<AdwApplicationWindow title="My App" defaultWidth={800} defaultHeight={600} onCloseRequest={quit}>
|
|
285
|
+
<Slot for={AdwApplicationWindow} id="content">
|
|
286
|
+
<AdwToolbarView>...</AdwToolbarView>
|
|
287
|
+
</Slot>
|
|
288
|
+
</AdwApplicationWindow>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### AdwStatusPage
|
|
292
|
+
Welcome or empty state pages:
|
|
293
|
+
```tsx
|
|
294
|
+
<AdwStatusPage
|
|
295
|
+
iconName="applications-system-symbolic"
|
|
296
|
+
title="Welcome"
|
|
297
|
+
description="Get started with your app"
|
|
298
|
+
vexpand
|
|
299
|
+
/>
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### AdwBanner
|
|
303
|
+
Dismissable notification banner:
|
|
304
|
+
```tsx
|
|
305
|
+
<AdwBanner
|
|
306
|
+
title="Update available!"
|
|
307
|
+
buttonLabel="Dismiss"
|
|
308
|
+
revealed={showBanner}
|
|
309
|
+
onButtonClicked={() => setShowBanner(false)}
|
|
310
|
+
/>
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### AdwPreferencesPage / AdwPreferencesGroup
|
|
314
|
+
Settings UI layout:
|
|
315
|
+
```tsx
|
|
316
|
+
<AdwPreferencesPage title="Settings">
|
|
317
|
+
<AdwPreferencesGroup title="Appearance" description="Customize the look">
|
|
318
|
+
<AdwSwitchRow title="Dark Mode" active={dark} onActivate={() => setDark(!dark)} />
|
|
319
|
+
<AdwActionRow title="Theme" subtitle="Select color theme">
|
|
320
|
+
<Slot for={AdwActionRow} id="activatableWidget">
|
|
321
|
+
<GtkImage iconName="go-next-symbolic" />
|
|
322
|
+
</Slot>
|
|
323
|
+
</AdwActionRow>
|
|
324
|
+
</AdwPreferencesGroup>
|
|
325
|
+
</AdwPreferencesPage>
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### AdwExpanderRow
|
|
329
|
+
Expandable row with nested content:
|
|
330
|
+
```tsx
|
|
331
|
+
<AdwExpanderRow title="Advanced" subtitle="More options">
|
|
332
|
+
<AdwSwitchRow title="Option 1" active />
|
|
333
|
+
<AdwSwitchRow title="Option 2" active={false} />
|
|
334
|
+
</AdwExpanderRow>
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### AdwEntryRow / AdwPasswordEntryRow
|
|
338
|
+
Input fields in list rows:
|
|
339
|
+
```tsx
|
|
340
|
+
<AdwEntryRow title="Username" text={username} onChanged={(e) => setUsername(e.getText())} />
|
|
341
|
+
<AdwPasswordEntryRow title="Password" />
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### AdwClamp
|
|
345
|
+
Limit content width for readability:
|
|
346
|
+
```tsx
|
|
347
|
+
<AdwClamp maximumSize={600}>
|
|
348
|
+
<GtkBox>...</GtkBox>
|
|
349
|
+
</AdwClamp>
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### AdwAvatar
|
|
353
|
+
User avatar with initials fallback:
|
|
354
|
+
```tsx
|
|
355
|
+
<AdwAvatar size={48} text="John Doe" showInitials />
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### AdwSpinner
|
|
359
|
+
Loading indicator:
|
|
360
|
+
```tsx
|
|
361
|
+
{isLoading && <AdwSpinner widthRequest={32} heightRequest={32} />}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Constraints
|
|
365
|
+
|
|
366
|
+
- **GTK is single-threaded**: All widget operations on main thread
|
|
367
|
+
- **Virtual lists need immutable data**: Use stable object references
|
|
368
|
+
- **GtkToggleButton auto-prevents feedback loops**: Safe for controlled state
|
|
369
|
+
- **GtkEntry needs two-way binding**: Use `onChanged` to sync state
|
|
370
|
+
|
|
371
|
+
For detailed widget reference, see [WIDGETS.md](WIDGETS.md).
|
|
372
|
+
For code examples, see [EXAMPLES.md](EXAMPLES.md).
|