basalt-gtk 0.0.0-rc5

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.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/cli/runLinux.js +29 -0
  3. package/cli/shared.js +75 -0
  4. package/native/CMakeLists.txt +362 -0
  5. package/native/gtk/ColorSchemeGtk.cpp +58 -0
  6. package/native/gtk/ComponentRegistryGtk.cpp +101 -0
  7. package/native/gtk/FontRegistryFontconfig.cpp +127 -0
  8. package/native/gtk/GtkAnimationChoreographer.cpp +73 -0
  9. package/native/gtk/GtkAnimationChoreographer.h +49 -0
  10. package/native/gtk/GtkDropTarget.cpp +269 -0
  11. package/native/gtk/GtkDropTarget.h +40 -0
  12. package/native/gtk/GtkFocus.cpp +296 -0
  13. package/native/gtk/GtkFocus.h +113 -0
  14. package/native/gtk/GtkImageLoader.cpp +128 -0
  15. package/native/gtk/GtkImageLoader.h +87 -0
  16. package/native/gtk/GtkKeyEvents.cpp +91 -0
  17. package/native/gtk/GtkKeyEvents.h +21 -0
  18. package/native/gtk/GtkMenuBar.cpp +45 -0
  19. package/native/gtk/GtkMountingManager.cpp +1027 -0
  20. package/native/gtk/GtkMountingManager.h +146 -0
  21. package/native/gtk/GtkRunLoopObserver.cpp +77 -0
  22. package/native/gtk/GtkRunLoopObserver.h +41 -0
  23. package/native/gtk/GtkScrollView.cpp +626 -0
  24. package/native/gtk/GtkScrollView.h +209 -0
  25. package/native/gtk/GtkSkiaContext.cpp +248 -0
  26. package/native/gtk/GtkSkiaContext.h +62 -0
  27. package/native/gtk/GtkSkiaModule.cpp +80 -0
  28. package/native/gtk/GtkSkiaModule.h +51 -0
  29. package/native/gtk/GtkSkiaPeer.cpp +285 -0
  30. package/native/gtk/GtkSkiaPeer.h +63 -0
  31. package/native/gtk/GtkTextInput.cpp +428 -0
  32. package/native/gtk/GtkTextInput.h +123 -0
  33. package/native/gtk/GtkTextPeer.cpp +425 -0
  34. package/native/gtk/GtkTextPeer.h +87 -0
  35. package/native/gtk/GtkTitleBar.cpp +273 -0
  36. package/native/gtk/GtkTitleBar.h +96 -0
  37. package/native/gtk/GtkTitleBarLayout.cpp +45 -0
  38. package/native/gtk/GtkTitleBarLayout.h +36 -0
  39. package/native/gtk/GtkTouchDispatcher.cpp +671 -0
  40. package/native/gtk/GtkTouchDispatcher.h +139 -0
  41. package/native/gtk/GtkWindowControl.cpp +223 -0
  42. package/native/gtk/GtkWindowModule.cpp +194 -0
  43. package/native/gtk/GtkWindowModule.h +62 -0
  44. package/native/gtk/PangoTextLayout.cpp +295 -0
  45. package/native/gtk/PangoTextLayout.h +54 -0
  46. package/native/gtk/PangoTextLayoutManager.cpp +140 -0
  47. package/native/gtk/PlatformServicesGtk.cpp +583 -0
  48. package/native/gtk/RnView.cpp +1407 -0
  49. package/native/gtk/RnView.h +339 -0
  50. package/native/gtk/demo_layout_gtk.cpp +57 -0
  51. package/native/gtk/main_gtk.cpp +1981 -0
  52. package/native/gtk/mount_harness_gtk.cpp +143 -0
  53. package/native/tests/TestMainGtk.cpp +25 -0
  54. package/native/tests/notification_stub.cpp +103 -0
  55. package/native/tests/test_accessibility.cpp +251 -0
  56. package/native/tests/test_focus.cpp +194 -0
  57. package/native/tests/test_hittest.cpp +242 -0
  58. package/native/tests/test_image.cpp +135 -0
  59. package/native/tests/test_key_events_gtk.cpp +112 -0
  60. package/native/tests/test_mounting.cpp +415 -0
  61. package/native/tests/test_scrollview.cpp +258 -0
  62. package/native/tests/test_skia_texture.cpp +120 -0
  63. package/native/tests/test_text.cpp +147 -0
  64. package/native/tests/test_textinput.cpp +598 -0
  65. package/native/tests/test_titlebar.cpp +137 -0
  66. package/native/tests/test_titlebar_drag.cpp +138 -0
  67. package/native/tests/test_version.cpp +49 -0
  68. package/native/tests/test_view.cpp +162 -0
  69. package/native/tests/test_viewprops.cpp +254 -0
  70. package/package.json +26 -0
  71. package/react-native.config.js +21 -0
@@ -0,0 +1,339 @@
1
+ // basalt-core — GTK4 view layer
2
+ //
3
+ // Deliberately free of React Native headers: this file only knows about
4
+ // absolute frames and paint properties. The mounting manager translates
5
+ // Fabric's ShadowViewMutation stream into calls on this API, so the widget
6
+ // layer can be built and exercised without the RN C++ core present.
7
+
8
+ #pragma once
9
+
10
+ #include <gtk/gtk.h>
11
+ #include <pango/pango.h>
12
+
13
+ G_BEGIN_DECLS
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // RnLayout — a GtkLayoutManager that does no layout of its own.
17
+ //
18
+ // Yoga has already computed absolute frames by the time a mutation reaches us,
19
+ // so this places each child at the rect the shadow tree assigned it. Measuring
20
+ // returns zero: RN is the sole source of truth for size.
21
+ // ---------------------------------------------------------------------------
22
+
23
+ #define RN_TYPE_LAYOUT (rn_layout_get_type())
24
+ G_DECLARE_FINAL_TYPE(RnLayout, rn_layout, RN, LAYOUT, GtkLayoutManager)
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // RnView — the GTK peer of a Fabric <View>.
28
+ // ---------------------------------------------------------------------------
29
+
30
+ #define RN_TYPE_VIEW (rn_view_get_type())
31
+ G_DECLARE_FINAL_TYPE(RnView, rn_view, RN, VIEW, GtkWidget)
32
+
33
+ RnView *rn_view_new(int tag);
34
+
35
+ // As above, but with an accessible role.
36
+ //
37
+ // GTK4 has no per-instance role setter: a role is a construct-only property, or
38
+ // is set once per widget class. RnView is one class for every React Native
39
+ // view, so the role has to be chosen when the widget is made. That is possible
40
+ // because Fabric delivers a view's props with the Create mutation that makes
41
+ // it -- but it does mean accessibilityRole cannot change afterwards.
42
+ RnView *rn_view_new_with_role(int tag, GtkAccessibleRole role);
43
+
44
+ // Fabric tag, for debugging and hit-test attribution.
45
+ int rn_view_get_tag(RnView *self);
46
+
47
+ // Frame in parent coordinates, straight from LayoutMetrics::frame.
48
+ void rn_view_set_frame(RnView *self, float x, float y, float width, float height);
49
+ void rn_view_get_frame(RnView *self, graphene_rect_t *out);
50
+
51
+ // BaseViewProps::backgroundColor. Passing has_color = FALSE paints nothing.
52
+ void rn_view_set_background_color(RnView *self, gboolean has_color, const GdkRGBA *color);
53
+
54
+ // BaseViewProps::opacity.
55
+ void rn_view_set_opacity(RnView *self, double opacity);
56
+
57
+ // Corner radii, in the order top-left, top-right, bottom-right, bottom-left.
58
+ // Each is a width/height pair, because React Native's radii are elliptical.
59
+ // Pass NULL for square corners.
60
+ void rn_view_set_border_radii(RnView *self, const graphene_size_t radii[4]);
61
+
62
+ // Border widths and colours, in the order top, right, bottom, left -- which is
63
+ // the order GTK's border node wants and the order CSS names them in.
64
+ void rn_view_set_borders(RnView *self, const float widths[4], const GdkRGBA colors[4]);
65
+
66
+ // BaseViewProps::transform, already resolved by React Native.
67
+ //
68
+ // Applied in the layout manager rather than at paint time, so that GTK's own
69
+ // hit testing follows it: a transformed view is picked where it appears, not
70
+ // where its frame says it is. Anchored on the view's centre, which is what
71
+ // every other React Native platform does and what transformOrigin is measured
72
+ // against. Pass NULL for identity.
73
+ void rn_view_set_transform(RnView *self, const graphene_matrix_t *matrix);
74
+
75
+ // `backfaceVisibility: 'hidden'`. A view whose transform has turned it away
76
+ // from the viewer is not drawn, and nor are its children -- which is what a
77
+ // card's back should do, and what takes it out of hit testing too, since
78
+ // `gtk_widget_pick` skips a widget that is not visible.
79
+ //
80
+ // Whether it has turned away is `basalt::facesAway`; see core/Backface.h for
81
+ // why the rule is shared rather than left to each toolkit.
82
+ void rn_view_set_hides_back_face(RnView *self, gboolean hides);
83
+
84
+ // `display: 'none'`. Separate from the back face above because the two are
85
+ // independent reasons to hide the same widget, and Fabric applies them through
86
+ // different calls -- props, then layout metrics.
87
+ void rn_view_set_hidden(RnView *self, gboolean hidden);
88
+
89
+ // BaseViewProps::zIndex. GTK paints in child order, so this reorders painting
90
+ // without touching the child list that mutations index into.
91
+ void rn_view_set_z_index(RnView *self, int z_index);
92
+
93
+ // `pointerEvents`, which decides what a press can land on rather than what is
94
+ // drawn. CSS's four values, and React Native's.
95
+ //
96
+ // GTK expresses exactly one of them. `none` is `can-target`, which makes
97
+ // `gtk_widget_pick` skip the widget and everything inside it, so a press
98
+ // reaches whatever is behind -- which is precisely the semantics. The other two
99
+ // have no equivalent, so they are stored here and resolved by the hit test;
100
+ // see GtkTouchDispatcher.cpp.
101
+ typedef enum {
102
+ RN_POINTER_EVENTS_AUTO,
103
+ RN_POINTER_EVENTS_NONE,
104
+ RN_POINTER_EVENTS_BOX_NONE,
105
+ RN_POINTER_EVENTS_BOX_ONLY,
106
+ } RnPointerEvents;
107
+
108
+ void rn_view_set_pointer_events(RnView *self, RnPointerEvents mode);
109
+ RnPointerEvents rn_view_get_pointer_events(RnView *self);
110
+
111
+ // Whether this view takes keyboard focus, and therefore whether Tab stops on
112
+ // it.
113
+ //
114
+ // React Native has a `focusable` prop and it does not reach this platform:
115
+ // ReactCommon parses it only into Android's and tvOS's HostPlatformViewProps,
116
+ // and the C++ host's is a bare alias of BaseViewProps. What does reach here is
117
+ // `accessible`, which is what <Pressable> sets on everything it renders and
118
+ // what an app sets on anything else it means as a control -- so that is the
119
+ // signal, and it is also the one both desktops use for their own focus rings.
120
+ // See GtkFocus.h.
121
+ //
122
+ // GTK owns the chain: a focusable widget joins the window's focus order, so Tab
123
+ // and Shift+Tab work without this project deciding what "next" means.
124
+ void rn_view_set_focusable(RnView *self, gboolean focusable);
125
+ gboolean rn_view_get_focusable(RnView *self);
126
+
127
+ // The text of a <Paragraph>, already laid out.
128
+ //
129
+ // A Paragraph is a View that also paints text, so it gets no separate widget
130
+ // type: same box model, same frame, same children. The mounting manager builds
131
+ // the layout from the shadow view's AttributedString -- Pango is part of the
132
+ // GTK stack, so taking a PangoLayout here keeps this file free of React Native
133
+ // types the way the rest of it is.
134
+ //
135
+ // Takes its own reference. Pass NULL to clear. `color` is the colour to paint
136
+ // glyphs that carry no foreground attribute of their own.
137
+ void rn_view_set_text_layout(RnView *self, PangoLayout *layout, const GdkRGBA *color);
138
+
139
+ // How an image fills its frame. Mirrors React Native's ImageResizeMode, minus
140
+ // Repeat, which needs a repeating pattern node rather than one texture draw.
141
+ typedef enum {
142
+ RN_IMAGE_FIT_COVER,
143
+ RN_IMAGE_FIT_CONTAIN,
144
+ RN_IMAGE_FIT_STRETCH,
145
+ RN_IMAGE_FIT_CENTER,
146
+ // Tiled from the top left at the image's own size, which is what CSS
147
+ // `repeat` and React Native's `resizeMode: 'repeat'` mean. Every toolkit
148
+ // here has a primitive for it; centring, which is what this used to fall
149
+ // back to, is a different picture rather than a rougher one.
150
+ RN_IMAGE_FIT_REPEAT,
151
+ } RnImageFit;
152
+
153
+ // The decoded pixels of an <Image>. Takes its own reference; pass NULL to clear.
154
+ //
155
+ // Like the text layout above, this is a GTK type rather than a React Native
156
+ // one, so the widget layer stays free of RN headers. GtkImageLoader produces
157
+ // the texture and GtkMountingManager chooses the fit.
158
+ void rn_view_set_texture(RnView *self, GdkTexture *texture, RnImageFit fit);
159
+
160
+ // `tintColor`: recolours the image, keeping its alpha. An icon drawn as a
161
+ // silhouette is the usual reason -- one asset, any colour.
162
+ //
163
+ // Separate from the texture because it arrives from the props and the texture
164
+ // arrives from a loader callback, and either can land first.
165
+ void rn_view_set_image_tint(RnView *self, gboolean has_tint, const GdkRGBA *tint);
166
+
167
+ // Called on the GTK main thread when this widget's own allocation changes.
168
+ //
169
+ // The host attaches one to a surface root to drive
170
+ // ReactHost::setSurfaceConstraints. GTK4 removed GtkWidget::size-allocate, and
171
+ // a layout manager's allocate is the supported replacement: it is the one
172
+ // place a widget is told the size it actually got.
173
+ typedef void (*RnViewResizeFunc)(RnView *self, int width, int height, gpointer user_data);
174
+ void rn_view_set_resize_callback(RnView *self, RnViewResizeFunc callback, gpointer user_data);
175
+
176
+ // Clip children to this view's bounds.
177
+ //
178
+ // Off by default, because React Native's default is overflow: visible. A
179
+ // <ScrollView> needs it, and so does any view with overflow: 'hidden'.
180
+ void rn_view_set_clips_children(RnView *self, gboolean clips);
181
+
182
+ // Shift children by a scroll offset, in points.
183
+ //
184
+ // Applied in the layout manager rather than by moving frames, so the frames
185
+ // Fabric assigned stay untouched and GTK's own hit testing follows the shift
186
+ // for free: a child allocated at its scrolled position is picked there.
187
+ void rn_view_set_scroll_offset(RnView *self, double offset_x, double offset_y);
188
+
189
+ // The overlay scrollbars this view draws over its own content. Computed by the
190
+ // scroll manager from core/ScrollIndicator.h, because the geometry is the same
191
+ // on all three desktops and the drawing is not.
192
+ //
193
+ // Lengths of zero mean "no indicator on that axis", which is what content that
194
+ // fits produces.
195
+ void rn_view_set_scroll_indicators(RnView *self,
196
+ double vertical_offset,
197
+ double vertical_length,
198
+ double horizontal_offset,
199
+ double horizontal_length);
200
+ void rn_view_get_scroll_offset(RnView *self, double *offset_x, double *offset_y);
201
+
202
+ // A textual description of the widget tree rooted here, one indented line per
203
+ // view.
204
+ //
205
+ // This is what makes the full stack testable rather than merely watchable.
206
+ // Everything below GDK has unit tests, but the path from JavaScript through
207
+ // React, Fabric and the mounting manager can only be exercised by running the
208
+ // real host -- and until now the only way to check the result was to look at a
209
+ // screenshot. A dump can be asserted on.
210
+ //
211
+ // Returns a newly allocated string; free with g_free.
212
+ char *rn_view_describe_tree(RnView *self);
213
+
214
+ // Turns this view into a text field, or back.
215
+ //
216
+ // The editable is a real GtkText -- the widget behind GtkEntry -- parented
217
+ // inside this one and sized to its whole frame. Using GTK's own editable rather
218
+ // than drawing a cursor on a PangoLayout is what brings input methods,
219
+ // selection, the clipboard and every keybinding a Linux user expects, none of
220
+ // which is worth reimplementing.
221
+ //
222
+ // Returns the editable, or NULL after clearing.
223
+ GtkWidget *rn_view_set_editable(RnView *self, gboolean editable, gboolean multiline);
224
+
225
+ // The border and padding to hold the native peer inside. Yoga has already
226
+ // resolved these into React Native's content inset; without them a <TextInput>
227
+ // renders its text flush against its own border, ignoring paddingHorizontal.
228
+ void rn_view_set_peer_insets(RnView *self, const GtkBorder *insets);
229
+ void rn_view_get_peer_insets(RnView *self, GtkBorder *out);
230
+ GtkWidget *rn_view_get_editable(RnView *self);
231
+
232
+ // --- Controls ----------------------------------------------------------------
233
+ //
234
+ // The three components that are a toolkit control rather than a box:
235
+ // <ActivityIndicator> and <RefreshControl> are a GtkSpinner, <Switch> is a
236
+ // GtkSwitch. Parented and sized exactly the way the <TextInput> peer above is
237
+ // -- a non-RnView child fills the view's inner rect -- because it is the same
238
+ // arrangement for the same reason: GTK's own widget brings the theme, the
239
+ // animation and the accessibility with it, and none of that is worth drawing
240
+ // by hand.
241
+ typedef enum {
242
+ RN_CONTROL_NONE,
243
+ RN_CONTROL_SPINNER,
244
+ RN_CONTROL_SWITCH,
245
+ } RnControlKind;
246
+
247
+ // Installs the control this view stands for, replacing any other kind, or
248
+ // removes it for RN_CONTROL_NONE. Returns the control widget, or NULL.
249
+ GtkWidget *rn_view_set_control(RnView *self, RnControlKind kind);
250
+ GtkWidget *rn_view_get_control(RnView *self);
251
+ RnControlKind rn_view_get_control_kind(RnView *self);
252
+
253
+ // What `describe_tree` prints for this control, which core/DesktopControls.h
254
+ // writes so that three hosts cannot describe the same switch differently. NULL
255
+ // or empty prints nothing.
256
+ void rn_view_set_control_description(RnView *self, const char *description);
257
+
258
+ // Whether this control is disabled. Kept on the view rather than read back off
259
+ // the widget because `sensitive` is GTK's word for two different things, and
260
+ // only React Native's `disabled` should stop a press.
261
+ void rn_view_set_control_disabled(RnView *self, gboolean disabled);
262
+ gboolean rn_view_get_control_disabled(RnView *self);
263
+
264
+ // --- React DevTools' overlay --------------------------------------------------
265
+ //
266
+ // The rectangles a `DebuggingOverlay` draws: an inspected element's blue box,
267
+ // or an outline around everything that just re-rendered. Set from
268
+ // GtkMountingManager, which parses them; see core/DebuggingOverlay.h.
269
+ //
270
+ // Eight floats per rectangle -- x, y, width, height, then r, g, b, a -- and a
271
+ // flag for whether to fill. A plain array rather than the core struct because
272
+ // this file has no React Native in it and is not about to start.
273
+ void rn_view_set_highlights(RnView *self,
274
+ const float *rectangles,
275
+ const gboolean *filled,
276
+ int count);
277
+
278
+ // Accessibility, as a screen reader sees it.
279
+ //
280
+ // `label` is the accessible name and `description` the hint; either may be NULL
281
+ // or empty to leave it unset. GTK maps these onto AT-SPI, which is what Orca
282
+ // reads.
283
+ void rn_view_set_accessible_text(RnView *self, const char *label, const char *description);
284
+
285
+ // React Native's own name for the role -- "button", "image", "text" -- kept
286
+ // alongside the GtkAccessibleRole the widget was constructed with.
287
+ //
288
+ // Two vocabularies for one fact, and both are needed. GTK's is what a screen
289
+ // reader reads; React Native's is what `rn_view_describe_tree` reports, so that
290
+ // dump can be compared line by line with the AppKit one without either platform
291
+ // speaking the other's language. That the GTK role was really applied is
292
+ // asserted in tests/test_accessibility.cpp instead.
293
+ //
294
+ // Set from the same computation that picks the GtkAccessibleRole, so the two
295
+ // cannot disagree. Pass NULL or "" for no role.
296
+ void rn_view_set_role_name(RnView *self, const char *name);
297
+
298
+ // The view's `nativeID`, verbatim.
299
+ //
300
+ // Only the hidden title bar reads it, to find the regions an app marked with
301
+ // <TitleBar.DragRegion> and <TitleBar.NoDragRegion>; see
302
+ // core/TitleBarRegions.h for the two names and the rule. Nothing about layout,
303
+ // painting or accessibility looks at it.
304
+ //
305
+ // Kept as the app set it rather than parsed into a flag here, because a view
306
+ // can carry a nativeID for reasons that have nothing to do with the title bar
307
+ // -- an end-to-end test looking a view up by name, most of them -- and this
308
+ // layer has no business deciding which of those meanings was intended.
309
+ //
310
+ // Pass NULL or "" for none.
311
+ void rn_view_set_native_id(RnView *self, const char *native_id);
312
+ // NULL when unset, never the empty string.
313
+ const char *rn_view_get_native_id(RnView *self);
314
+
315
+ // Accessible states. Each is a tri-state: unset leaves GTK's default alone,
316
+ // which is not the same as setting it false.
317
+ typedef enum {
318
+ RN_A11Y_UNSET,
319
+ RN_A11Y_FALSE,
320
+ RN_A11Y_TRUE,
321
+ } RnAccessibleFlag;
322
+
323
+ void rn_view_set_accessible_state(RnView *self,
324
+ RnAccessibleFlag disabled,
325
+ RnAccessibleFlag checked,
326
+ RnAccessibleFlag selected,
327
+ RnAccessibleFlag expanded,
328
+ RnAccessibleFlag busy);
329
+
330
+ // Hidden from assistive technology, for accessible={false} and
331
+ // accessibilityElementsHidden.
332
+ void rn_view_set_accessible_hidden(RnView *self, gboolean hidden);
333
+
334
+ // Child management. Mirrors Insert/Remove mutations; index is the position
335
+ // within the parent's child list, as Fabric numbers it.
336
+ void rn_view_insert_child(RnView *self, RnView *child, int index);
337
+ void rn_view_remove_child(RnView *self, RnView *child);
338
+
339
+ G_END_DECLS
@@ -0,0 +1,57 @@
1
+ // Exercises the GTK view layer with hand-written frames, standing in for what
2
+ // Fabric would emit. Builds and runs without the React Native C++ core, so the
3
+ // paint and layout path can be verified on its own.
4
+
5
+ #include "RnView.h"
6
+
7
+ static GdkRGBA rgba(float r, float g, float b, float a) {
8
+ GdkRGBA c;
9
+ c.red = r; c.green = g; c.blue = b; c.alpha = a;
10
+ return c;
11
+ }
12
+
13
+ static RnView *make_box(int tag, float x, float y, float w, float h, GdkRGBA color) {
14
+ RnView *view = rn_view_new(tag);
15
+ rn_view_set_frame(view, x, y, w, h);
16
+ rn_view_set_background_color(view, TRUE, &color);
17
+ return view;
18
+ }
19
+
20
+ static void on_activate(GtkApplication *app, gpointer /*user_data*/) {
21
+ GtkWidget *window = gtk_application_window_new(app);
22
+ gtk_window_set_title(GTK_WINDOW(window), "basalt-core — mounting skeleton");
23
+ gtk_window_set_default_size(GTK_WINDOW(window), 640, 420);
24
+
25
+ // Root surface.
26
+ RnView *root = make_box(1, 0, 0, 640, 420, rgba(0.12f, 0.13f, 0.16f, 1.0f));
27
+
28
+ // A row of children, absolutely placed the way Yoga would have resolved them.
29
+ RnView *a = make_box(2, 32, 32, 240, 160, rgba(0.30f, 0.55f, 0.95f, 1.0f));
30
+ RnView *b = make_box(3, 296, 32, 240, 160, rgba(0.95f, 0.45f, 0.35f, 1.0f));
31
+ RnView *c = make_box(4, 32, 224, 504, 140, rgba(0.35f, 0.80f, 0.55f, 1.0f));
32
+
33
+ rn_view_insert_child(root, a, 0);
34
+ rn_view_insert_child(root, b, 1);
35
+ rn_view_insert_child(root, c, 2);
36
+
37
+ // Nested child, to prove coordinates are parent-relative.
38
+ RnView *nested = make_box(5, 24, 24, 120, 90, rgba(1.0f, 1.0f, 1.0f, 0.85f));
39
+ rn_view_insert_child(a, nested, 0);
40
+
41
+ // Half-opacity child, to prove the opacity layer.
42
+ RnView *faded = make_box(6, 24, 24, 160, 90, rgba(0.1f, 0.1f, 0.1f, 1.0f));
43
+ rn_view_set_opacity(faded, 0.4);
44
+ rn_view_insert_child(c, faded, 0);
45
+
46
+ gtk_window_set_child(GTK_WINDOW(window), GTK_WIDGET(root));
47
+ gtk_window_present(GTK_WINDOW(window));
48
+ }
49
+
50
+ int main(int argc, char **argv) {
51
+ GtkApplication *app =
52
+ gtk_application_new("dev.basalt.skeleton", G_APPLICATION_DEFAULT_FLAGS);
53
+ g_signal_connect(app, "activate", G_CALLBACK(on_activate), nullptr);
54
+ const int status = g_application_run(G_APPLICATION(app), argc, argv);
55
+ g_object_unref(app);
56
+ return status;
57
+ }