@gtkx/cli 0.14.0 → 0.15.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.
@@ -1,5 +1,6 @@
1
1
  import * as net from "node:net";
2
2
  import { getNativeId, getNativeObject } from "@gtkx/ffi";
3
+ import { Value } from "@gtkx/ffi/gobject";
3
4
  import * as Gtk from "@gtkx/ffi/gtk";
4
5
  import { DEFAULT_SOCKET_PATH, IpcRequestSchema, IpcResponseSchema, McpError, McpErrorCode, methodNotFoundError, widgetNotFoundError, } from "@gtkx/mcp";
5
6
  import { getApplication } from "@gtkx/react";
@@ -354,7 +355,28 @@ class McpClient {
354
355
  if (!widget) {
355
356
  throw widgetNotFoundError(p.widgetId);
356
357
  }
357
- const signalArgs = (p.args ?? []);
358
+ const signalArgs = (p.args ?? []).map((arg) => {
359
+ switch (arg.type) {
360
+ case "boolean":
361
+ return Value.newFromBoolean(arg.value);
362
+ case "int":
363
+ return Value.newFromInt(arg.value);
364
+ case "uint":
365
+ return Value.newFromUint(arg.value);
366
+ case "int64":
367
+ return Value.newFromInt64(arg.value);
368
+ case "uint64":
369
+ return Value.newFromUint64(arg.value);
370
+ case "float":
371
+ return Value.newFromFloat(arg.value);
372
+ case "double":
373
+ return Value.newFromDouble(arg.value);
374
+ case "string":
375
+ return Value.newFromString(arg.value);
376
+ default:
377
+ throw new McpError(McpErrorCode.INVALID_REQUEST, `Unknown argument type: ${arg.type}`);
378
+ }
379
+ });
358
380
  await testing.fireEvent(widget, p.signal, ...signalArgs);
359
381
  return { success: true };
360
382
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtkx/cli",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "CLI for GTKX - create and develop GTK4 React applications",
5
5
  "keywords": [
6
6
  "gtkx",
@@ -58,19 +58,19 @@
58
58
  "ejs": "^3.1.10",
59
59
  "react-refresh": "^0.18.0",
60
60
  "vite": "^7.3.1",
61
- "@gtkx/ffi": "0.14.0",
62
- "@gtkx/mcp": "0.14.0",
63
- "@gtkx/react": "0.14.0"
61
+ "@gtkx/mcp": "0.15.0",
62
+ "@gtkx/react": "0.15.0",
63
+ "@gtkx/ffi": "0.15.0"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@types/ejs": "^3.1.5",
67
67
  "@types/react-refresh": "^0.14.7",
68
68
  "memfs": "^4.51.1",
69
- "@gtkx/testing": "0.14.0"
69
+ "@gtkx/testing": "0.15.0"
70
70
  },
71
71
  "peerDependencies": {
72
72
  "react": "^19",
73
- "@gtkx/testing": "0.14.0"
73
+ "@gtkx/testing": "0.15.0"
74
74
  },
75
75
  "peerDependenciesMeta": {
76
76
  "@gtkx/testing": {
@@ -339,7 +339,7 @@ const FileTable = () => {
339
339
 
340
340
  return (
341
341
  <GtkScrolledWindow vexpand cssClasses={["card"]}>
342
- <GtkColumnView estimatedRowHeight={48} sortColumn={sortColumn} sortOrder={sortOrder} onSortChange={handleSort}>
342
+ <GtkColumnView estimatedRowHeight={48} sortColumn={sortColumn} sortOrder={sortOrder} onSortChanged={handleSort}>
343
343
  <x.ColumnViewColumn<FileItem> title="Name" id="name" expand sortable renderCell={(f) => <GtkLabel label={f?.name ?? ""} />} />
344
344
  <x.ColumnViewColumn<FileItem> title="Size" id="size" fixedWidth={100} sortable renderCell={(f) => <GtkLabel label={`${f?.size ?? 0} KB`} />} />
345
345
  <x.ColumnViewColumn<FileItem> title="Modified" id="modified" fixedWidth={120} sortable renderCell={(f) => <GtkLabel label={f?.modified ?? ""} />} />
@@ -701,7 +701,15 @@ const ViewSwitcher = () => {
701
701
 
702
702
  return (
703
703
  <GtkBox orientation={Gtk.Orientation.VERTICAL} spacing={12}>
704
- <AdwToggleGroup active={view} onToggled={setView} halign={Gtk.Align.CENTER}>
704
+ <AdwToggleGroup
705
+ activeName={view}
706
+ onNotify={(group, prop) => {
707
+ if (prop === "active-name") {
708
+ setView(group.getActiveName() ?? "list");
709
+ }
710
+ }}
711
+ halign={Gtk.Align.CENTER}
712
+ >
705
713
  <x.Toggle id="list" iconName="view-list-symbolic" tooltip="List view" />
706
714
  <x.Toggle id="grid" iconName="view-grid-symbolic" tooltip="Grid view" />
707
715
  </AdwToggleGroup>
@@ -169,7 +169,7 @@ Grid-based virtual scrolling.
169
169
  Table with sortable columns.
170
170
 
171
171
  ```tsx
172
- <GtkColumnView estimatedRowHeight={48} sortColumn="name" sortOrder={Gtk.SortType.ASCENDING} onSortChange={handleSort}>
172
+ <GtkColumnView estimatedRowHeight={48} sortColumn="name" sortOrder={Gtk.SortType.ASCENDING} onSortChanged={handleSort}>
173
173
  <x.ColumnViewColumn<Item>
174
174
  title="Name"
175
175
  id="name"
@@ -500,7 +500,16 @@ Input in list row.
500
500
  Segmented button group for mutually exclusive options.
501
501
 
502
502
  ```tsx
503
- <AdwToggleGroup active={viewMode} onToggled={setViewMode}>
503
+ const [viewMode, setViewMode] = useState("list");
504
+
505
+ <AdwToggleGroup
506
+ activeName={viewMode}
507
+ onNotify={(group, prop) => {
508
+ if (prop === "active-name") {
509
+ setViewMode(group.getActiveName() ?? "list");
510
+ }
511
+ }}
512
+ >
504
513
  <x.Toggle id="list" iconName="view-list-symbolic" tooltip="List view" />
505
514
  <x.Toggle id="grid" iconName="view-grid-symbolic" tooltip="Grid view" />
506
515
  <x.Toggle id="flow" label="Flow" />
@@ -578,33 +587,26 @@ All widgets support drag-and-drop through props. Use `onDragPrepare`, `onDragBeg
578
587
 
579
588
  ```tsx
580
589
  import * as Gdk from "@gtkx/ffi/gdk";
581
- import * as GObject from "@gtkx/ffi/gobject";
582
- import { typeFromName } from "@gtkx/ffi/gobject";
590
+ import { Type, Value } from "@gtkx/ffi/gobject";
583
591
  import { GtkButton, GtkBox, GtkLabel } from "@gtkx/react";
584
592
  import { useState } from "react";
585
593
 
586
594
  const DraggableButton = ({ label }: { label: string }) => {
587
- const stringType = typeFromName("gchararray");
588
- const value = new GObject.Value();
589
- value.init(stringType);
590
- value.setString(label);
591
-
592
595
  return (
593
596
  <GtkButton
594
597
  label={label}
595
- onDragPrepare={() => Gdk.ContentProvider.newForValue(value)}
598
+ onDragPrepare={() => Gdk.ContentProvider.newForValue(Value.newFromString(label))}
596
599
  />
597
600
  );
598
601
  };
599
602
 
600
603
  const DropZone = () => {
601
604
  const [dropped, setDropped] = useState<string | null>(null);
602
- const stringType = typeFromName("gchararray");
603
605
 
604
606
  return (
605
607
  <GtkBox
606
- dropTypes={[stringType]}
607
- onDrop={(value: GObject.Value) => {
608
+ dropTypes={[Type.STRING]}
609
+ onDrop={(value: Value) => {
608
610
  setDropped(value.getString());
609
611
  return true;
610
612
  }}
@@ -613,3 +615,155 @@ const DropZone = () => {
613
615
  </GtkBox>
614
616
  );
615
617
  };
618
+ ```
619
+
620
+ ## GValue Factories
621
+
622
+ Create typed values for drag-and-drop and signal emission:
623
+
624
+ | Factory | Description |
625
+ | ------------------------------ | ----------------------------- |
626
+ | `Value.newFromString(str)` | String values |
627
+ | `Value.newFromDouble(num)` | 64-bit floating point |
628
+ | `Value.newFromInt(num)` | 32-bit signed integer |
629
+ | `Value.newFromBoolean(bool)` | Boolean values |
630
+ | `Value.newFromObject(obj)` | GObject instances |
631
+ | `Value.newFromBoxed(boxed)` | Boxed types (Gdk.RGBA, etc.) |
632
+ | `Value.newFromEnum(gtype, n)` | Enum values (requires GType) |
633
+ | `Value.newFromFlags(gtype, n)` | Flags values (requires GType) |
634
+
635
+ Type constants for `dropTypes`: `Type.STRING`, `Type.INT`, `Type.DOUBLE`, `Type.BOOLEAN`, `Type.OBJECT`.
636
+
637
+ ## Custom Drawing
638
+
639
+ Render custom graphics with `GtkDrawingArea` using the `onDraw` callback:
640
+
641
+ ```tsx
642
+ import { GtkDrawingArea } from "@gtkx/react";
643
+ import type { Context } from "@gtkx/ffi/cairo";
644
+ import * as Gtk from "@gtkx/ffi/gtk";
645
+
646
+ const Canvas = () => {
647
+ const handleDraw = (self: Gtk.DrawingArea, cr: Context, width: number, height: number) => {
648
+ cr.setSourceRgb(0.2, 0.4, 0.8);
649
+ cr.rectangle(10, 10, width - 20, height - 20);
650
+ cr.fill();
651
+ };
652
+
653
+ return <GtkDrawingArea contentWidth={400} contentHeight={300} onDraw={handleDraw} />;
654
+ };
655
+ ```
656
+
657
+ Use `onGestureDragBegin`, `onGestureDragUpdate`, `onGestureDragEnd` for interactive drawing. Call `widget.queueDraw()` to trigger redraws.
658
+
659
+ ## Adjustment
660
+
661
+ Configure adjustable widgets declaratively with `x.Adjustment`:
662
+
663
+ ```tsx
664
+ import { x, GtkScale, GtkBox, GtkLabel } from "@gtkx/react";
665
+ import * as Gtk from "@gtkx/ffi/gtk";
666
+ import { useState } from "react";
667
+
668
+ const VolumeControl = () => {
669
+ const [volume, setVolume] = useState(50);
670
+
671
+ return (
672
+ <GtkBox orientation={Gtk.Orientation.VERTICAL} spacing={12}>
673
+ <GtkScale drawValue hexpand>
674
+ <x.Adjustment value={volume} lower={0} upper={100} stepIncrement={1} pageIncrement={10} onValueChanged={setVolume} />
675
+ </GtkScale>
676
+ <GtkLabel label={`Volume: ${Math.round(volume)}%`} />
677
+ </GtkBox>
678
+ );
679
+ };
680
+ ```
681
+
682
+ Works with `GtkScale`, `GtkScrollbar`, `GtkScaleButton`, `GtkSpinButton`, `GtkListBox`.
683
+
684
+ **Props:** `value`, `lower`, `upper`, `stepIncrement`, `pageIncrement`, `pageSize`, `onValueChanged`
685
+
686
+ ## TextBuffer
687
+
688
+ Configure `GtkTextView` buffers declaratively with `x.TextBuffer`:
689
+
690
+ ```tsx
691
+ import { x, GtkTextView, GtkScrolledWindow } from "@gtkx/react";
692
+ import * as Gtk from "@gtkx/ffi/gtk";
693
+ import { useState } from "react";
694
+
695
+ const TextEditor = () => {
696
+ const [text, setText] = useState("Hello, World!");
697
+
698
+ return (
699
+ <GtkScrolledWindow minContentHeight={200}>
700
+ <GtkTextView wrapMode={Gtk.WrapMode.WORD_CHAR}>
701
+ <x.TextBuffer text={text} enableUndo onTextChanged={setText} />
702
+ </GtkTextView>
703
+ </GtkScrolledWindow>
704
+ );
705
+ };
706
+ ```
707
+
708
+ **Props:** `text`, `enableUndo`, `onTextChanged`, `onCanUndoChanged`, `onCanRedoChanged`
709
+
710
+ ## SourceBuffer
711
+
712
+ Configure `GtkSourceView` buffers with syntax highlighting using `x.SourceBuffer`:
713
+
714
+ ```tsx
715
+ import { x, GtkSourceView, GtkScrolledWindow } from "@gtkx/react";
716
+ import { useState } from "react";
717
+
718
+ const CodeEditor = () => {
719
+ const [code, setCode] = useState('console.log("Hello!");');
720
+
721
+ return (
722
+ <GtkScrolledWindow minContentHeight={300}>
723
+ <GtkSourceView showLineNumbers highlightCurrentLine tabWidth={4}>
724
+ <x.SourceBuffer
725
+ text={code}
726
+ language="typescript"
727
+ styleScheme="Adwaita-dark"
728
+ highlightSyntax
729
+ highlightMatchingBrackets
730
+ enableUndo
731
+ onTextChanged={setCode}
732
+ />
733
+ </GtkSourceView>
734
+ </GtkScrolledWindow>
735
+ );
736
+ };
737
+ ```
738
+
739
+ **Props:** `text`, `language`, `styleScheme`, `highlightSyntax`, `highlightMatchingBrackets`, `enableUndo`, `onTextChanged`, `onCanUndoChanged`, `onCanRedoChanged`, `onCursorMoved`
740
+
741
+ ## Keyboard Shortcuts
742
+
743
+ Attach shortcuts with `x.ShortcutController` and `x.Shortcut`:
744
+
745
+ ```tsx
746
+ import { x, GtkBox, GtkLabel } from "@gtkx/react";
747
+ import * as Gtk from "@gtkx/ffi/gtk";
748
+ import { useState } from "react";
749
+
750
+ const App = () => {
751
+ const [count, setCount] = useState(0);
752
+
753
+ return (
754
+ <GtkBox orientation={Gtk.Orientation.VERTICAL} spacing={12} focusable>
755
+ <x.ShortcutController scope={Gtk.ShortcutScope.LOCAL}>
756
+ <x.Shortcut trigger="<Control>equal" onActivate={() => setCount((c) => c + 1)} />
757
+ <x.Shortcut trigger="<Control>minus" onActivate={() => setCount((c) => c - 1)} />
758
+ </x.ShortcutController>
759
+ <GtkLabel label={`Count: ${count}`} />
760
+ </GtkBox>
761
+ );
762
+ };
763
+ ```
764
+
765
+ **Scopes:** `LOCAL` (widget focus), `MANAGED` (parent managed), `GLOBAL` (window-wide)
766
+
767
+ **Trigger syntax:** `<Control>s`, `<Control><Shift>s`, `<Alt>F4`, `<Primary>q`, `F5`
768
+
769
+ **Multiple triggers:** `trigger={["F5", "<Control>r"]}`