Просмотр исходного кода

docs: analyse V3 OOP facade for GTK4

Eric Streit 3 дней назад
Родитель
Сommit
c938c0a3c4
2 измененных файлов с 291 добавлено и 11 удалено
  1. 2 2
      docs/INDEX.md
  2. 289 9
      docs/future-work-2026-10-25.md

+ 2 - 2
docs/INDEX.md

@@ -39,8 +39,8 @@ bisecting *why* a decision was made, not for reading end-to-end).
   libraries, collections and the TopSpeed B-tree Toolkit, communications,
   pyserial, ModBase, network-library design, terminal escape libraries,
   C FFI and GTK4/SQLite/SDL bindings, audit of the existing gm2 bindings,
-  PIM vs ISO coroutines, external assembly, dynamic-set design and
-  suggested order.
+  GTK4 OOP facade, PIM vs ISO coroutines, external assembly, dynamic-set
+  design and suggested order.
 
 ## Designs / running records
 

+ 289 - 9
docs/future-work-2026-10-25.md

@@ -6,8 +6,9 @@ address arithmetic, set ordering, complex types, pragmas, UTF-8 string
 libraries, collections, the TopSpeed B-tree Toolkit, communications,
 pyserial, ModBase, network-library design, terminal escape libraries,
 C FFI and GTK4/SQLite/SDL bindings, an audit of the existing gm2
-bindings, PIM versus ISO coroutines, external assembly, and the
-dynamic-set design.  It is not a commitment to implement everything.
+bindings, the GTK4 OOP facade, PIM versus ISO coroutines, external
+assembly, and the dynamic-set design.  It is not a commitment to
+implement everything.
 
 **State at the time of writing:** suite 310/310, one-shot fixpoint OK
 (3,845,439 bytes), per-object fixpoint OK (11 objects), corpus
@@ -2375,7 +2376,282 @@ a small GTK4 subset using the existing closure/shim architecture.
 
 ---
 
-## 16. Suggested order of future work
+## 16. GTK4 OOP facade: limits and recommended layering
+
+V3 classes can make GTK4 application code look much more like
+Modula-2, but they should be an **optional high-level facade**, not the
+implementation mechanism for the binding itself.  Directly mapping V3
+classes onto GObject classes is not viable: V3's Clarion-form classes
+and GObject's C object model have incompatible layouts and lifecycles.
+
+### 16.1 Three layers
+
+The binding should distinguish three layers:
+
+```text
+1. raw GTK4 C binding       -- ADDRESS handles and free procedures
+2. typed/opaque wrapper     -- Modula-2 types and module procedures
+3. optional OOP facade      -- CLASS Window, CLASS Button, methods
+```
+
+The existing `m2-GTK4` is layer 1.  That is the right canonical
+low-level layer: generated or reviewed `FOR "C"` modules with opaque
+`ADDRESS` handles.
+
+V3 classes belong at layer 3, for application code.  A typed opaque
+layer can sit between them, especially where class fields would expose
+implementation details.
+
+### 16.2 Why V3 classes are not GObject classes
+
+V3 classes:
+
+- are records with fields;
+- have a hidden `THIS` receiver in methods;
+- have a hidden V3 vtable pointer for virtual methods;
+- use V3 method-call and subtype rules;
+- have value semantics when used as values;
+- have no destructors or finalizers;
+- support single inheritance only.
+
+GObject has:
+
+- its own `GObject` header, type system and class/instance structures;
+- reference counting and floating references;
+- signals and properties;
+- interfaces;
+- `GObjectClass`/`GtkWidgetClass` vfunc tables;
+- C-side construction and finalization.
+
+A V3 class instance cannot be passed to C as a `GtkWidget *`.  The V3
+wrapper and the GTK object must remain separate:
+
+```modula2
+CLASS Window;
+  handle : ADDRESS;   (* the real GtkWindow *, not the V3 object *)
+  ...
+END Window;
+```
+
+C never sees the V3 object.  It receives only the handle stored inside
+it.  Direct GObject subclassing still requires C shims such as the
+existing `m2gtkshim.c`.
+
+### 16.3 Ownership and reference counting
+
+A V3 class copy copies its fields.  If a wrapper contains a handle and
+an owner flag, copying the wrapper can create two apparent owners of
+the same GObject and lead to a double `Unref`.
+
+Use pointer wrappers and explicit lifetime operations:
+
+```modula2
+TYPE
+  CLASS Object;
+    handle : ADDRESS;
+    owned  : BOOLEAN;
+    PROCEDURE Ref;
+    PROCEDURE Unref;
+  END Object;
+
+  CLASS Widget (Object);
+    PROCEDURE SetVisible (on : BOOLEAN);
+  END Widget;
+
+  CLASS Window (Widget);
+    PROCEDURE SetTitle (s : ARRAY OF CHAR);
+    PROCEDURE Present;
+  END Window;
+
+  WindowPtr = POINTER TO Window;
+```
+
+Constructors should return pointer wrappers, for example
+`NewWindow (app) : WindowPtr`.
+
+Rules:
+
+- distinguish owned from borrowed handles;
+- sink floating references where necessary;
+- `DISPOSE` frees only the V3 wrapper, never the GObject;
+- call `Unref`/`Close` explicitly before disposing the wrapper;
+- do not rely on V3 `BEGIN ... END` as a per-object constructor: a
+  class initialisation body runs once at startup, not for each object;
+- keep wrapper objects on the main GTK thread.
+
+V3 has no destructors, so ownership must be documented and tested, not
+inferred from scope exit.
+
+### 16.4 Encapsulation
+
+V3 classes have no private fields.  A class declared in a definition
+module exposes its fields to clients, including the GTK handle and owner
+flag.  This weakens the abstraction.
+
+Options:
+
+1. accept the exposed handle and document it as an internal field;
+2. use an opaque handle type plus module procedures for the most
+   sensitive objects;
+3. add private class fields or opaque classes to V3 later.
+
+A true private-field facility is compiler work and should not block the
+first GTK4 facade.  For a small facade, exposing `handle` as an
+implementation detail may be acceptable; for a public library API, an
+opaque wrapper is cleaner.
+
+### 16.5 Inheritance and GTK interfaces
+
+V3 single inheritance can model GTK's primary class chain:
+
+```text
+GObject -> GtkWidget -> GtkWindow -> GtkApplicationWindow
+GObject -> GtkWidget -> GtkButton
+GObject -> GtkWidget -> GtkBox
+```
+
+It cannot model GTK's interfaces.  GTK and Gio use interfaces such as
+`GtkAccessible`, `GtkBuildable`, `GtkNative`, `GtkRoot`,
+`GtkShortcutManager`, `GListModel` and `GtkTreeModel`.
+
+Do not encode interfaces as extra V3 parents: V3 rejects multiple
+parents.  Provide interface capabilities as separate modules or
+free-function groups:
+
+```text
+Accessible.SetLabel
+Buildable.AddFromString
+Native.GetSurface
+```
+
+Only the main widget chain should use class inheritance.  Services and
+non-widget objects such as `GtkApplication`, `GtkBuilder`, models and
+file dialogs can use their own wrapper roots or opaque handles.
+
+### 16.6 Constructors and properties
+
+GTK construction is C-style:
+
+```c
+gtk_application_window_new (app)
+g_object_new (type, "property", value, ...)
+```
+
+V3 has no named-property arguments or general varargs.  Constructors in
+the facade should be ordinary module procedures:
+
+```modula2
+PROCEDURE NewWindow (app : Application) : WindowPtr;
+PROCEDURE NewButton (label : ARRAY OF CHAR) : ButtonPtr;
+```
+
+Property-heavy construction should use C wrappers or
+`g_object_new_with_properties`, not an invented V3 constructor syntax.
+Keep `gtk_*_new` and `g_object_new` wrappers in the raw binding; the
+OOP facade should call them rather than duplicate them.
+
+### 16.7 Signals and callbacks
+
+Do not pass a V3 instance method directly to `g_signal_connect_data`.
+A V3 method has a hidden receiver and is not a C function pointer with
+GObject's callback ABI.
+
+Use the existing `GtkClosures` pattern:
+
+- one top-level trampoline per callback signature;
+- a wrapper/context pointer as `gpointer` user data;
+- an explicit destroy handler;
+- optional V3 virtual dispatch inside the trampoline.
+
+For example, a clicked handler can receive the wrapper pointer as user
+data, cast it back to `ButtonPtr`, and invoke a V3 method or virtual
+method.  The trampoline must still be a top-level procedure with the C
+callback signature.
+
+The same rule applies to `GtkApplication` activate/startup handlers,
+`GtkDrawingArea` draw functions and model callbacks.
+
+### 16.8 GObject subclassing
+
+Custom widgets, custom `GdkPaintable` implementations, custom
+`GtkLayoutManager`s and custom `GListModel`s still require C:
+
+- register the GObject type;
+- provide `class_init`/`instance_init`;
+- override vfuncs;
+- handle finalization and properties.
+
+V3 classes cannot do this.  Keep `m2gtkshim.c` for subclassing.  The
+OOP facade can wrap the resulting C object afterward, but it cannot
+replace the shim.
+
+### 16.9 Recommended package layout
+
+```text
+bindings/gtk4/raw/       FOR "C" modules, ADDRESS handles
+bindings/gtk4/wrap/      optional typed wrappers / opaque types
+bindings/gtk4/oop/       optional CLASS facade
+bindings/gtk4/shim/      m2gtkshim.c, GtkClosures
+```
+
+The raw binding remains canonical.  The OOP facade should cover only a
+small, useful application subset:
+
+```text
+Object, Widget, Window, ApplicationWindow,
+Button, Label, Box, Entry, ListBox, DrawingArea,
+Application, Builder
+```
+
+Do not create a parallel class hierarchy for all 432 GTK/GDK/Gsk/Gio/
+Adw modules.  Keep uncommon APIs at the raw or typed-wrapper layer.
+The GIR generator can help produce raw modules; the OOP facade should
+be hand-curated because ownership and API shape need judgement.
+
+### 16.10 Is it more "Modula-2"?
+
+Not necessarily.  Classic Modula-2 style is primarily:
+
+- modules as namespaces;
+- opaque types;
+- explicit procedures;
+- strong typing;
+- explicit ownership.
+
+V3 `CLASS` is a Clarion-form extension, not ISO Modula-2 OO.  An OOP
+facade can be nicer for application code, but it is not automatically
+more standard.  The most idiomatic design is probably:
+
+```text
+opaque handle types + module procedures
+```
+
+with classes as an optional convenience layer for users who prefer:
+
+```modula2
+win^.SetTitle("Demo");
+btn^.OnClicked(OnQuit);
+```
+
+### 16.11 Bottom line
+
+- Use V3 OOP for a high-level GTK4 facade, not for the raw binding.
+- Keep `ADDRESS`-based `FOR "C"` modules as the canonical layer.
+- Use `POINTER TO CLASS` wrappers holding a C handle and ownership flag.
+- Add explicit `Ref`/`Unref`/`Close`; do not rely on `DISPOSE`.
+- Model only GTK's primary single-inheritance chain with classes.
+- Keep GTK interfaces as separate module capabilities.
+- Use `GtkClosures`/top-level trampolines for signals; never pass V3
+  methods directly to C.
+- Keep `m2gtkshim.c` for GObject subclassing.
+- Wrap a small common subset first; do not mirror all 432 modules.
+
+This gives a more Modula-2 application API without pretending that V3
+classes are GObject classes.
+
+---
+
+## 17. Suggested order of future work
 
 1. `TBITSIZE` (small, standard-facing).
 2. `ADDADR`/`SUBADR`/`DIFADR` (small, ISO SYSTEM).
@@ -2395,14 +2671,18 @@ a small GTK4 subset using the existing closure/shim architecture.
    pkg-config; SQLite, then SDL3, then GTK4.
 10. Port the existing gm2 bindings: SQLite first, SDL3 second, GTK4
     subset last; keep them as separate `bindings/` packages.
-11. Communications: `Serial`/`Transport` first; protocols and terminal
+11. Add an optional GTK4 OOP facade over the raw binding: small
+    `Object`/`Widget` hierarchy, pointer wrappers, explicit
+    `Ref`/`Unref`/`Close`, `GtkClosures` trampolines; no direct GObject
+    subclassing.
+12. Communications: `Serial`/`Transport` first; protocols and terminal
     emulation optional.
-12. Network: `Net`/`Transport`, TCP/UDP, `Poll`, then optional TLS/HTTP.
-13. Database: SQLite binding or typed B-tree table layer; dBase III
+13. Network: `Net`/`Transport`, TCP/UDP, `Poll`, then optional TLS/HTTP.
+14. Database: SQLite binding or typed B-tree table layer; dBase III
     only if required.
-14. ISO `COROUTINES` subset, then interrupt/protection layer.
-15. Dynamic `SET CONST` and deep composite copies.
-16. Parser error recovery and diagnostics.
+15. ISO `COROUTINES` subset, then interrupt/protection layer.
+16. Dynamic `SET CONST` and deep composite copies.
+17. Parser error recovery and diagnostics.
 
 External assembly needs no compiler work; document it as the supported
 low-level escape hatch.