@gtkx/cli 0.11.0 → 0.12.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/cli.js +6 -0
- package/dist/mcp-client.d.ts +30 -0
- package/dist/mcp-client.js +401 -0
- package/dist/refresh-runtime.d.ts +27 -0
- package/dist/refresh-runtime.js +27 -0
- package/package.json +17 -5
- package/templates/claude/EXAMPLES.md.ejs +372 -234
- package/templates/claude/SKILL.md.ejs +29 -319
- package/templates/claude/WIDGETS.md.ejs +213 -332
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: developing-gtkx-apps
|
|
3
|
-
description: Build GTK4 desktop applications with GTKX React framework. Use when creating
|
|
3
|
+
description: Build GTK4 desktop applications with GTKX React framework. Use when creating React components that render as native GTK widgets, working with GTK4/Libadwaita UI, handling signals, virtual lists, menus, or building Linux desktop UIs.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Developing GTKX Applications
|
|
7
7
|
|
|
8
|
-
GTKX
|
|
8
|
+
GTKX renders React components as native GTK4 widgets through a Rust FFI bridge.
|
|
9
9
|
|
|
10
10
|
## Quick Start
|
|
11
11
|
|
|
12
12
|
```tsx
|
|
13
|
-
import { GtkApplicationWindow, GtkBox, GtkButton,
|
|
13
|
+
import { GtkApplicationWindow, GtkBox, GtkButton, render, quit } from "@gtkx/react";
|
|
14
14
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
15
15
|
|
|
16
16
|
const App = () => (
|
|
@@ -25,346 +25,56 @@ const App = () => (
|
|
|
25
25
|
render(<App />, "com.example.myapp");
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
##
|
|
28
|
+
## Essential Patterns
|
|
29
29
|
|
|
30
|
-
###
|
|
30
|
+
### Layout
|
|
31
31
|
|
|
32
|
-
**GtkBox** - Linear layout:
|
|
33
32
|
```tsx
|
|
34
33
|
<GtkBox orientation={Gtk.Orientation.VERTICAL} spacing={12}>
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
<GtkLabel label="Title" />
|
|
35
|
+
<GtkButton label="Click" onClicked={handleClick} />
|
|
37
36
|
</GtkBox>
|
|
38
37
|
```
|
|
39
38
|
|
|
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
39
|
### Controlled Input
|
|
194
40
|
|
|
195
|
-
GtkEntry requires two-way binding:
|
|
196
41
|
```tsx
|
|
197
42
|
const [text, setText] = useState("");
|
|
198
|
-
|
|
199
|
-
<GtkEntry
|
|
200
|
-
text={text}
|
|
201
|
-
onChanged={(entry) => setText(entry.getText())}
|
|
202
|
-
placeholderText="Type here..."
|
|
203
|
-
/>
|
|
43
|
+
<GtkEntry text={text} onChanged={(e) => setText(e.getText())} />
|
|
204
44
|
```
|
|
205
45
|
|
|
206
|
-
###
|
|
46
|
+
### Signals
|
|
207
47
|
|
|
208
|
-
|
|
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
|
|
48
|
+
GTK signals map to `on<SignalName>` props: `clicked` → `onClicked`, `toggled` → `onToggled`.
|
|
231
49
|
|
|
232
|
-
|
|
233
|
-
- `clicked` → `onClicked`
|
|
234
|
-
- `toggled` → `onToggled`
|
|
235
|
-
- `changed` → `onChanged`
|
|
236
|
-
- `notify::selected` → `onNotify` (with property name arg)
|
|
50
|
+
### Widget Slots
|
|
237
51
|
|
|
238
|
-
|
|
52
|
+
Some widgets require children in specific slots:
|
|
239
53
|
|
|
240
54
|
```tsx
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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 application window:
|
|
283
|
-
```tsx
|
|
284
|
-
<AdwApplicationWindow title="My App" defaultWidth={800} defaultHeight={600} onCloseRequest={quit}>
|
|
285
|
-
<AdwToolbarView>...</AdwToolbarView>
|
|
286
|
-
</AdwApplicationWindow>
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
### AdwStatusPage
|
|
290
|
-
Welcome or empty state pages:
|
|
291
|
-
```tsx
|
|
292
|
-
<AdwStatusPage
|
|
293
|
-
iconName="applications-system-symbolic"
|
|
294
|
-
title="Welcome"
|
|
295
|
-
description="Get started with your app"
|
|
296
|
-
vexpand
|
|
297
|
-
/>
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
### AdwBanner
|
|
301
|
-
Dismissable notification banner:
|
|
302
|
-
```tsx
|
|
303
|
-
<AdwBanner
|
|
304
|
-
title="Update available!"
|
|
305
|
-
buttonLabel="Dismiss"
|
|
306
|
-
revealed={showBanner}
|
|
307
|
-
onButtonClicked={() => setShowBanner(false)}
|
|
308
|
-
/>
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
### AdwPreferencesPage / AdwPreferencesGroup
|
|
312
|
-
Settings UI layout:
|
|
313
|
-
```tsx
|
|
314
|
-
<AdwPreferencesPage title="Settings">
|
|
315
|
-
<AdwPreferencesGroup title="Appearance" description="Customize the look">
|
|
316
|
-
<AdwSwitchRow title="Dark Mode" active={dark} onActivate={() => setDark(!dark)} />
|
|
317
|
-
<AdwActionRow title="Theme" subtitle="Select color theme">
|
|
318
|
-
<Slot for={AdwActionRow} id="activatableWidget">
|
|
319
|
-
<GtkImage iconName="go-next-symbolic" />
|
|
320
|
-
</Slot>
|
|
321
|
-
</AdwActionRow>
|
|
322
|
-
</AdwPreferencesGroup>
|
|
323
|
-
</AdwPreferencesPage>
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
### AdwExpanderRow
|
|
327
|
-
Expandable row with nested content:
|
|
328
|
-
```tsx
|
|
329
|
-
<AdwExpanderRow title="Advanced" subtitle="More options">
|
|
330
|
-
<AdwSwitchRow title="Option 1" active />
|
|
331
|
-
<AdwSwitchRow title="Option 2" active={false} />
|
|
332
|
-
</AdwExpanderRow>
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
### AdwEntryRow / AdwPasswordEntryRow
|
|
336
|
-
Input fields in list rows:
|
|
337
|
-
```tsx
|
|
338
|
-
<AdwEntryRow title="Username" text={username} onChanged={(e) => setUsername(e.getText())} />
|
|
339
|
-
<AdwPasswordEntryRow title="Password" />
|
|
55
|
+
<GtkPaned orientation={Gtk.Orientation.HORIZONTAL}>
|
|
56
|
+
<Slot for={GtkPaned} id="startChild"><Sidebar /></Slot>
|
|
57
|
+
<Slot for={GtkPaned} id="endChild"><Content /></Slot>
|
|
58
|
+
</GtkPaned>
|
|
340
59
|
```
|
|
341
60
|
|
|
342
|
-
###
|
|
343
|
-
Limit content width for readability:
|
|
344
|
-
```tsx
|
|
345
|
-
<AdwClamp maximumSize={600}>
|
|
346
|
-
<GtkBox>...</GtkBox>
|
|
347
|
-
</AdwClamp>
|
|
348
|
-
```
|
|
61
|
+
### Packing (HeaderBar, ActionBar)
|
|
349
62
|
|
|
350
|
-
### AdwAvatar
|
|
351
|
-
User avatar with initials fallback:
|
|
352
63
|
```tsx
|
|
353
|
-
<
|
|
64
|
+
<GtkHeaderBar>
|
|
65
|
+
<Pack.Start><GtkButton iconName="go-previous-symbolic" /></Pack.Start>
|
|
66
|
+
<Pack.End><GtkMenuButton iconName="open-menu-symbolic" /></Pack.End>
|
|
67
|
+
</GtkHeaderBar>
|
|
354
68
|
```
|
|
355
69
|
|
|
356
|
-
|
|
357
|
-
Loading indicator:
|
|
358
|
-
```tsx
|
|
359
|
-
{isLoading && <AdwSpinner widthRequest={32} heightRequest={32} />}
|
|
360
|
-
```
|
|
70
|
+
## Key Constraints
|
|
361
71
|
|
|
362
|
-
|
|
72
|
+
- GTK is single-threaded: all widget operations on main thread
|
|
73
|
+
- GtkEntry requires two-way binding with `onChanged`
|
|
74
|
+
- Virtual lists need stable object references (immutable data patterns)
|
|
75
|
+
- Use `quit` from `@gtkx/react` to close the application
|
|
363
76
|
|
|
364
|
-
|
|
365
|
-
- **Virtual lists need immutable data**: Use stable object references
|
|
366
|
-
- **GtkToggleButton auto-prevents feedback loops**: Safe for controlled state
|
|
367
|
-
- **GtkEntry needs two-way binding**: Use `onChanged` to sync state
|
|
77
|
+
## References
|
|
368
78
|
|
|
369
|
-
For
|
|
370
|
-
For code examples, see [EXAMPLES.md](EXAMPLES.md).
|
|
79
|
+
For complete widget API, see [WIDGETS.md](WIDGETS.md).
|
|
80
|
+
For code patterns and examples, see [EXAMPLES.md](EXAMPLES.md).
|