@gtkx/ffi 0.3.2 → 0.3.3

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/README.md CHANGED
@@ -5,239 +5,138 @@
5
5
  <h1 align="center">GTKX</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>Build native GTK4 desktop applications with React and TypeScript</strong>
8
+ <strong>Build native GTK4 desktop apps with React</strong>
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
12
  <a href="https://eugeniodepalo.github.io/gtkx">Documentation</a> ·
13
13
  <a href="#quick-start">Quick Start</a> ·
14
- <a href="#examples">Examples</a>
14
+ <a href="#examples">Examples</a> ·
15
+ <a href="#contributing">Contributing</a>
15
16
  </p>
16
17
 
17
18
  ---
18
19
 
19
- GTKX bridges React's component model with GTK4's native widget system. Write familiar React code and render it as native Linux desktop applications with full access to GTK4 widgets, signals, and styling.
20
+ GTKX lets you build native Linux desktop applications using React and TypeScript. Write familiar React code that renders as native GTK4 widgets—no Electron, no web views.
20
21
 
21
22
  ## Features
22
23
 
23
- - **React Components** — Use React hooks, state, and component patterns you already know
24
- - **Hot Module Replacement** — Edit your code and see changes instantly, powered by Vite
25
- - **Native Performance** — Direct FFI bindings to GTK4 via Rust and libffi
26
- - **CLI & Scaffolding** — Get started in seconds with `npx @gtkx/cli@latest create`
27
- - **CSS-in-JS Styling** — Emotion-style `css` template literals for GTK widgets
28
- - **Testing Library** — Familiar `screen`, `userEvent`, and query APIs for testing components
24
+ - **React** — Hooks, state, props, and components you already know
25
+ - **Hot Reload** — Edit code and see changes instantly via Vite
26
+ - **Native** — Direct FFI bindings to GTK4 via Rust and libffi
27
+ - **CLI** — `npx @gtkx/cli@latest create` scaffolds a ready-to-go project
28
+ - **CSS-in-JS** — Emotion-style `css` template literals for GTK styling
29
+ - **Testing** — Testing Library-style `screen`, `userEvent`, and queries
29
30
 
30
31
  ## Quick Start
31
32
 
32
- Create a new GTKX app with a single command:
33
-
34
- ```bash
35
- npx @gtkx/cli@latest create
36
- ```
37
-
38
- This launches an interactive wizard that sets up your project with TypeScript, your preferred package manager, and optional testing support.
39
-
40
- You can also pass options directly:
41
-
42
- ```bash
43
- npx @gtkx/cli@latest create my-app --app-id com.example.myapp --pm pnpm --testing vitest
44
- ```
45
-
46
- Then start developing with HMR:
47
-
48
33
  ```bash
34
+ npx @gtkx/cli@latest create my-app
49
35
  cd my-app
50
36
  npm run dev
51
37
  ```
52
38
 
53
- Edit your code and see changes instantly without restarting the app!
54
-
55
- ### Manual Setup
39
+ Edit your code and see changes instantly—no restart needed.
56
40
 
57
- Alternatively, install packages directly:
58
-
59
- ```bash
60
- npm install @gtkx/cli @gtkx/react @gtkx/ffi react
61
- npm install -D @types/react typescript
62
- ```
63
-
64
- Create your first app:
41
+ ### Example
65
42
 
66
43
  ```tsx
44
+ import { render, ApplicationWindow, Box, Button, Label, quit } from "@gtkx/react";
45
+ import { Orientation } from "@gtkx/ffi/gtk";
67
46
  import { useState } from "react";
68
- import * as Gtk from "@gtkx/ffi/gtk";
69
- import { ApplicationWindow, Box, Button, Label, quit } from "@gtkx/react";
70
47
 
71
- export default function App() {
48
+ const App = () => {
72
49
  const [count, setCount] = useState(0);
73
50
 
74
51
  return (
75
- <ApplicationWindow
76
- title="My App"
77
- defaultWidth={400}
78
- defaultHeight={300}
79
- onCloseRequest={quit}
80
- >
81
- <Box
82
- orientation={Gtk.Orientation.VERTICAL}
83
- spacing={12}
84
- marginStart={20}
85
- marginEnd={20}
86
- marginTop={20}
87
- marginBottom={20}
88
- >
52
+ <ApplicationWindow title="Counter" onCloseRequest={quit}>
53
+ <Box orientation={Orientation.VERTICAL} spacing={12} margin={20}>
89
54
  <Label.Root label={`Count: ${count}`} />
90
55
  <Button label="Increment" onClicked={() => setCount((c) => c + 1)} />
91
56
  </Box>
92
57
  </ApplicationWindow>
93
58
  );
94
- }
95
-
96
- export const appId = "org.example.MyApp";
97
- ```
98
-
99
- ```tsx
100
- import { render } from "@gtkx/react";
101
- import App, { appId } from "./app.js";
102
-
103
- render(<App />, appId);
104
- ```
59
+ };
105
60
 
106
- Run with HMR:
107
-
108
- ```bash
109
- npx gtkx dev src/app.tsx
110
- ```
111
-
112
- Or without HMR (production):
113
-
114
- ```bash
115
- npx tsc -b && node dist/index.js
61
+ render(<App />, "org.example.Counter");
116
62
  ```
117
63
 
118
64
  ## Styling
119
65
 
120
- Use `@gtkx/css` for CSS-in-JS styling:
121
-
122
66
  ```tsx
123
67
  import { css } from "@gtkx/css";
124
68
  import { Button } from "@gtkx/react";
125
69
 
126
- const primaryButton = css`
70
+ const primary = css`
127
71
  padding: 16px 32px;
128
72
  border-radius: 24px;
129
73
  background: linear-gradient(135deg, #3584e4, #9141ac);
130
74
  color: white;
131
- font-weight: bold;
132
75
  `;
133
76
 
134
- const MyButton = () => <Button label="Click me" cssClasses={[primaryButton]} />;
77
+ <Button label="Click me" cssClasses={[primary]} />
135
78
  ```
136
79
 
137
- GTK also provides built-in CSS classes like `suggested-action`, `destructive-action`, `card`, and `heading`.
80
+ GTK also provides built-in classes like `suggested-action`, `destructive-action`, `card`, and `heading`.
138
81
 
139
82
  ## Testing
140
83
 
141
- Use `@gtkx/testing` for Testing Library-style component tests:
142
-
143
84
  ```tsx
144
- import { cleanup, render, screen, userEvent, fireEvent } from "@gtkx/testing";
85
+ import { cleanup, render, screen, userEvent } from "@gtkx/testing";
145
86
  import { AccessibleRole } from "@gtkx/ffi/gtk";
146
- import { App } from "./app.js";
147
87
 
148
- afterEach(async () => {
149
- await cleanup();
150
- });
88
+ afterEach(() => cleanup());
151
89
 
152
- test("increments count when clicking button", async () => {
90
+ test("increments count", async () => {
153
91
  await render(<App />);
154
92
 
155
- const button = await screen.findByRole(AccessibleRole.BUTTON, {
156
- name: "Increment",
157
- });
93
+ const button = await screen.findByRole(AccessibleRole.BUTTON, { name: "Increment" });
158
94
  await userEvent.click(button);
159
95
 
160
96
  await screen.findByText("Count: 1");
161
97
  });
162
-
163
- test("can also use fireEvent for low-level signals", async () => {
164
- await render(<App />);
165
-
166
- const button = await screen.findByRole(AccessibleRole.BUTTON, {
167
- name: "Increment",
168
- });
169
- fireEvent(button, "clicked");
170
-
171
- await screen.findByText("Count: 1");
172
- });
173
98
  ```
174
99
 
175
- ### Available APIs
176
-
177
- **Queries** - Find elements in the rendered tree (all async):
178
-
179
- - `findBy*` / `findAllBy*` - Waits for element to appear
180
-
181
- Query types: `ByRole`, `ByText`, `ByLabelText`, `ByTestId`
182
-
183
- **User Interactions**:
100
+ Queries: `findByRole`, `findByText`, `findByLabelText`, `findByTestId`
184
101
 
185
- - `userEvent.click(element)` - Simulate click
186
- - `userEvent.dblClick(element)` - Simulate double click
187
- - `userEvent.activate(element)` - Activate element (e.g., press Enter in input)
188
- - `userEvent.type(element, text)` - Type text into input
189
- - `userEvent.clear(element)` - Clear input text
190
- - `userEvent.tab(element, options?)` - Simulate Tab navigation
191
- - `userEvent.selectOptions(element, values)` - Select options in ComboBox/ListBox
192
- - `userEvent.deselectOptions(element, values)` - Deselect options in ListBox
193
-
194
- **Low-level Events**:
195
-
196
- - `fireEvent(element, signalName, ...args)` - Emit any GTK signal with optional arguments
197
-
198
- **Utilities**:
199
-
200
- - `waitFor(callback)` - Wait for condition
201
- - `waitForElementToBeRemoved(element)` - Wait for element removal
102
+ User events: `click`, `dblClick`, `type`, `clear`, `tab`, `selectOptions`
202
103
 
203
104
  ## Examples
204
105
 
205
- ### GTK4 Demo
206
-
207
- A comprehensive showcase of GTK4 widgets and features:
106
+ | Example | Description |
107
+ | ------- | ----------- |
108
+ | [gtk4-demo](examples/gtk4-demo) | Widget showcase |
109
+ | [todo](examples/todo) | Todo app with tests |
208
110
 
209
111
  ```bash
210
- cd examples/gtk4-demo
211
- pnpm dev
212
- ```
213
-
214
- ### Todo App
215
-
216
- A todo app demonstrating `@gtkx/testing` with realistic component tests:
217
-
218
- ```bash
219
- cd examples/todo
220
- pnpm dev
221
- pnpm test
112
+ cd examples/gtk4-demo && pnpm dev
222
113
  ```
223
114
 
224
115
  ## Packages
225
116
 
226
- | Package | Description |
227
- | --------------------------------- | -------------------------------------------------- |
228
- | [@gtkx/cli](packages/cli) | CLI for creating and developing GTKX apps with HMR |
229
- | [@gtkx/react](packages/react) | React reconciler and JSX components |
230
- | [@gtkx/ffi](packages/ffi) | TypeScript FFI bindings for GTK4 |
231
- | [@gtkx/native](packages/native) | Rust native module for FFI bridge |
232
- | [@gtkx/css](packages/css) | CSS-in-JS styling for GTK widgets |
233
- | [@gtkx/testing](packages/testing) | Testing utilities for GTKX components |
234
- | [@gtkx/gir](packages/gir) | GObject Introspection parser for codegen |
117
+ | Package | Description |
118
+ | ------- | ----------- |
119
+ | [@gtkx/cli](packages/cli) | CLI with HMR dev server |
120
+ | [@gtkx/react](packages/react) | React reconciler and JSX components |
121
+ | [@gtkx/ffi](packages/ffi) | TypeScript bindings for GTK4/GLib/GIO |
122
+ | [@gtkx/native](packages/native) | Rust native module (libffi bridge) |
123
+ | [@gtkx/css](packages/css) | CSS-in-JS styling |
124
+ | [@gtkx/testing](packages/testing) | Testing utilities |
125
+ | [@gtkx/gir](packages/gir) | GObject Introspection parser |
235
126
 
236
127
  ## Requirements
237
128
 
238
129
  - Node.js 20+
239
- - GTK4
240
- - Linux (GTK4 is Linux-native)
130
+ - GTK4 (`gtk4-devel` on Fedora, `libgtk-4-dev` on Ubuntu)
131
+ - Linux
132
+
133
+ ## Contributing
134
+
135
+ We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
136
+
137
+ - [Report a bug](https://github.com/eugeniodepalo/gtkx/issues/new?template=bug_report.md)
138
+ - [Request a feature](https://github.com/eugeniodepalo/gtkx/issues/new?template=feature_request.md)
139
+ - [Read the Code of Conduct](CODE_OF_CONDUCT.md)
241
140
 
242
141
  ## License
243
142
 
@@ -572,14 +572,12 @@ export class CodeGenerator {
572
572
  }
573
573
  let current = cls.parent ? classMap.get(cls.parent) : undefined;
574
574
  while (current) {
575
- // Collect signals from the current parent class first
576
575
  for (const signal of current.signals) {
577
576
  if (!seenNames.has(signal.name)) {
578
577
  allSignals.push(signal);
579
578
  seenNames.add(signal.name);
580
579
  }
581
580
  }
582
- // Then check if this parent has a cross-namespace grandparent
583
581
  if (current.parent?.includes(".")) {
584
582
  return { signals: allSignals, hasCrossNamespaceParent: true };
585
583
  }
@@ -1111,6 +1109,8 @@ ${allArgs ? `${allArgs},` : ""}
1111
1109
  lines.push(` if (ptr === null) return null;`);
1112
1110
  }
1113
1111
  if (isCyclic) {
1112
+ // For cyclic types, return a minimal NativeObject wrapper. Cast is necessary
1113
+ // because the full type interface isn't available without circular imports.
1114
1114
  lines.push(` return { id: ptr } as unknown as ${baseReturnType};`);
1115
1115
  }
1116
1116
  else {
@@ -1263,6 +1263,8 @@ ${allArgs ? `${allArgs},` : ""}
1263
1263
  kind: "class",
1264
1264
  });
1265
1265
  }
1266
+ // Signal handler catch-all overload must use `any` for compatibility with typed overloads.
1267
+ // The typed overloads have specific return types, and unknown is not assignable to them.
1266
1268
  signalOverloads.push(` ${methodName}(signal: string, handler: (...args: any[]) => any, after?: boolean): number;`);
1267
1269
  this.usesType = true;
1268
1270
  this.usesGetObject = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtkx/ffi",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Generated TypeScript FFI bindings for GTK4 libraries",
5
5
  "keywords": [
6
6
  "gtk",
@@ -46,10 +46,10 @@
46
46
  "dist"
47
47
  ],
48
48
  "dependencies": {
49
- "@gtkx/native": "0.3.2"
49
+ "@gtkx/native": "0.3.3"
50
50
  },
51
51
  "devDependencies": {
52
- "@gtkx/gir": "0.3.2"
52
+ "@gtkx/gir": "0.3.3"
53
53
  },
54
54
  "scripts": {
55
55
  "build": "tsc -b && cp ../../README.md .",