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,273 @@
1
+ #include "GtkTitleBar.h"
2
+
3
+ #include <cstdio>
4
+
5
+ namespace basalt {
6
+
7
+ namespace {
8
+
9
+ // processColor's 0xAARRGGBB as a CSS colour. Alpha included: a header bar can
10
+ // be translucent where a Windows caption cannot.
11
+ std::string cssColor(uint32_t argb) {
12
+ char buffer[64];
13
+ std::snprintf(buffer,
14
+ sizeof(buffer),
15
+ "rgba(%u,%u,%u,%.3f)",
16
+ (argb >> 16) & 0xFFu,
17
+ (argb >> 8) & 0xFFu,
18
+ argb & 0xFFu,
19
+ static_cast<double>((argb >> 24) & 0xFFu) / 255.0);
20
+ return buffer;
21
+ }
22
+
23
+ } // namespace
24
+
25
+ void GtkTitleBar::attach(GtkWindow *window, GtkWidget *controls) {
26
+ // Weak, so these become NULL when the widgets go rather than dangling.
27
+ //
28
+ // This outlives the window: it is a process-wide singleton and the window is
29
+ // destroyed at quit, or by close() below, or by the user. Every method here
30
+ // is reachable from JavaScript, which is still running at that point -- so a
31
+ // raw pointer is a use-after-free waiting for a late setTitle. AppKit's half
32
+ // gets this for free from __weak; GTK has to ask.
33
+ if (window_ != nullptr) {
34
+ g_object_remove_weak_pointer(G_OBJECT(window_), reinterpret_cast<gpointer *>(&window_));
35
+ }
36
+ if (controls_ != nullptr) {
37
+ g_object_remove_weak_pointer(G_OBJECT(controls_), reinterpret_cast<gpointer *>(&controls_));
38
+ }
39
+
40
+ window_ = window;
41
+ controls_ = controls;
42
+ header_ = nullptr;
43
+
44
+ if (window_ != nullptr) {
45
+ g_object_add_weak_pointer(G_OBJECT(window_), reinterpret_cast<gpointer *>(&window_));
46
+ }
47
+ if (controls_ != nullptr) {
48
+ g_object_add_weak_pointer(G_OBJECT(controls_), reinterpret_cast<gpointer *>(&controls_));
49
+ }
50
+
51
+ apply();
52
+ }
53
+
54
+ void GtkTitleBar::setTitle(std::optional<std::string> title) {
55
+ title_ = std::move(title);
56
+ apply();
57
+ }
58
+
59
+ void GtkTitleBar::setColors(std::optional<uint32_t> background,
60
+ std::optional<uint32_t> text,
61
+ std::optional<uint32_t> border) {
62
+ background_ = background;
63
+ text_ = text;
64
+ border_ = border;
65
+ apply();
66
+ }
67
+
68
+ void GtkTitleBar::setStyle(TitleBarStyle style) {
69
+ if (style_ == style) {
70
+ return;
71
+ }
72
+ style_ = style;
73
+ apply();
74
+ notify();
75
+ }
76
+
77
+ void GtkTitleBar::minimize() {
78
+ if (window_ != nullptr) {
79
+ gtk_window_minimize(window_);
80
+ }
81
+ }
82
+
83
+ void GtkTitleBar::toggleMaximize() {
84
+ if (window_ == nullptr) {
85
+ return;
86
+ }
87
+ // GTK has a maximised state to read, unlike AppKit's zoom, so this really is
88
+ // a toggle rather than a request to swap frames.
89
+ if (gtk_window_is_maximized(window_)) {
90
+ gtk_window_unmaximize(window_);
91
+ } else {
92
+ gtk_window_maximize(window_);
93
+ }
94
+ }
95
+
96
+ void GtkTitleBar::close() {
97
+ if (window_ != nullptr) {
98
+ // close() rather than destroy(): it asks, so a close-request handler still
99
+ // gets its say, which is how the real button behaves.
100
+ gtk_window_close(window_);
101
+ }
102
+ }
103
+
104
+ void GtkTitleBar::startDrag() {
105
+ // Still nothing to do here, but not for the reason this used to give.
106
+ //
107
+ // It said there is no "begin a drag now" call. There is:
108
+ // gdk_toplevel_begin_move, which beginMoveDrag below uses. What there is no
109
+ // way to supply is its arguments -- the device, the button and the
110
+ // timestamp of the press GDK is being asked to take over. By the time
111
+ // JavaScript can call this, the press has been delivered, handled and
112
+ // forgotten, and a move begun without it is refused by the compositor or
113
+ // starts a drag the pointer is not in.
114
+ //
115
+ // So the conclusion the old comment reached was right even though its
116
+ // premise was wrong: this belongs in the host, at the press. That is where
117
+ // it now is -- main_gtk.cpp hit-tests the drag regions on a click gesture
118
+ // and calls beginMoveDrag. An app marking <TitleBar.DragRegion> needs
119
+ // nothing from this method.
120
+ }
121
+
122
+ void GtkTitleBar::beginMoveDrag(GdkDevice *device,
123
+ int button,
124
+ double x,
125
+ double y,
126
+ guint32 timestamp) {
127
+ // window_ is a weak pointer and the device comes from an event that has
128
+ // already been dispatched, so both can be gone by the time this runs.
129
+ if (window_ == nullptr || device == nullptr) {
130
+ return;
131
+ }
132
+
133
+ GdkSurface *const surface = gtk_native_get_surface(GTK_NATIVE(window_));
134
+ // Not every GdkSurface is a toplevel -- a popup is not -- and only a
135
+ // toplevel can be moved. A window that has not been mapped yet has no
136
+ // surface at all, which is what an app calling this during its first render
137
+ // would reach.
138
+ if (surface == nullptr || !GDK_IS_TOPLEVEL(surface)) {
139
+ return;
140
+ }
141
+
142
+ gdk_toplevel_begin_move(GDK_TOPLEVEL(surface), device, button, x, y, timestamp);
143
+ }
144
+
145
+ TitleBarMetrics GtkTitleBar::metrics() const {
146
+ TitleBarMetrics result;
147
+ result.style = style_;
148
+ if (style_ == TitleBarStyle::Native || controls_ == nullptr) {
149
+ // GTK's own decorations sit above the child rather than over it, so they
150
+ // take nothing from the app and there is nothing to report.
151
+ return result;
152
+ }
153
+
154
+ // Measured rather than assumed: how tall the buttons are and how much room
155
+ // they need is the theme's business, and a hard-coded 32 would be wrong on
156
+ // every desktop that disagrees.
157
+ int minimum = 0;
158
+ int natural = 0;
159
+ gtk_widget_measure(controls_, GTK_ORIENTATION_HORIZONTAL, -1, &minimum, &natural, nullptr, nullptr);
160
+ result.buttonsWidth = natural;
161
+ gtk_widget_measure(controls_, GTK_ORIENTATION_VERTICAL, -1, &minimum, &natural, nullptr, nullptr);
162
+ result.height = natural;
163
+ return result;
164
+ }
165
+
166
+ void GtkTitleBar::setMetricsListener(std::function<void(const TitleBarMetrics &)> listener) {
167
+ listener_ = std::move(listener);
168
+ }
169
+
170
+ void GtkTitleBar::apply() {
171
+ if (window_ == nullptr) {
172
+ return;
173
+ }
174
+
175
+ if (title_.has_value()) {
176
+ gtk_window_set_title(window_, title_->c_str());
177
+ }
178
+
179
+ const bool hidden = style_ == TitleBarStyle::Hidden;
180
+ // The header bar goes away with the decorations rather than being left
181
+ // behind empty, which is what `set_decorated(FALSE)` alone would do on a
182
+ // window that has one.
183
+ if (header_ != nullptr) {
184
+ gtk_widget_set_visible(header_, hidden ? FALSE : TRUE);
185
+ }
186
+ gtk_window_set_decorated(window_, hidden ? FALSE : TRUE);
187
+ if (controls_ != nullptr) {
188
+ // The host's stand-in for the caption buttons the other two systems keep
189
+ // drawing. Only when there are no real ones.
190
+ gtk_widget_set_visible(controls_, hidden ? TRUE : FALSE);
191
+ }
192
+
193
+ // A header bar of our own, and only once an app has asked for a colour.
194
+ //
195
+ // Without one there is nothing to style: GTK draws a `headerbar` node only
196
+ // when it is doing client-side decorations, and where it is not -- an X11
197
+ // session with server-side decorations, or the quartz backend, where macOS
198
+ // draws the frame -- the caption belongs to something CSS cannot reach.
199
+ //
200
+ // Installed lazily rather than at startup because `set_titlebar` takes
201
+ // vertical space from the child. A window that was never asked about its
202
+ // title bar must be exactly the window it was before this class existed --
203
+ // the first version installed one unconditionally and moved every app's
204
+ // layout down by the height of a header, which two end-to-end scenarios
205
+ // noticed by tapping where a button no longer was.
206
+ // Worth knowing what this costs: a GtkHeaderBar draws GTK's own window
207
+ // controls, so from here the buttons are Adwaita's rather than whatever the
208
+ // window frame had. On the target desktop that is the same thing -- GNOME
209
+ // does client-side decorations, and these *are* the system's buttons. On a
210
+ // backend where something else draws the frame, the quartz one used for
211
+ // development among them, it is visibly GTK's buttons on someone else's
212
+ // window. Unavoidable: CSS is the only handle GTK offers for a caption
213
+ // colour, and it reaches nothing the system drew.
214
+ const bool wantsColour = background_.has_value() || text_.has_value() || border_.has_value();
215
+ if (wantsColour && header_ == nullptr) {
216
+ header_ = gtk_header_bar_new();
217
+ gtk_header_bar_set_show_title_buttons(GTK_HEADER_BAR(header_), TRUE);
218
+ gtk_window_set_titlebar(window_, header_);
219
+ g_object_add_weak_pointer(G_OBJECT(header_), reinterpret_cast<gpointer *>(&header_));
220
+ gtk_widget_set_visible(header_, hidden ? FALSE : TRUE);
221
+ }
222
+
223
+ // Colours, as CSS on the header bar. There is no property for any of this:
224
+ // a GtkWindow's decorations are styled or they are the theme's.
225
+ if (provider_ == nullptr) {
226
+ GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(window_));
227
+ if (display == nullptr) {
228
+ // No display yet, so nothing to style and nothing drawn. The next call
229
+ // installs it.
230
+ return;
231
+ }
232
+ provider_ = gtk_css_provider_new();
233
+ gtk_style_context_add_provider_for_display(
234
+ display, GTK_STYLE_PROVIDER(provider_), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
235
+ }
236
+
237
+ std::string css;
238
+ if (background_.has_value() || text_.has_value() || border_.has_value()) {
239
+ css += "headerbar, headerbar > windowhandle {";
240
+ if (background_.has_value()) {
241
+ // `background-image: none` as well: the theme's header bar is usually a
242
+ // gradient, and a background-color alone leaves it on top.
243
+ css += "background-image: none; background-color: " + cssColor(*background_) + ";";
244
+ }
245
+ if (text_.has_value()) {
246
+ css += "color: " + cssColor(*text_) + ";";
247
+ }
248
+ if (border_.has_value()) {
249
+ // Unlike AppKit, GTK does have somewhere to put this.
250
+ css += "border-bottom: 1px solid " + cssColor(*border_) + ";";
251
+ }
252
+ css += "}";
253
+ // The title label inherits colour from the header bar on most themes and
254
+ // not on all, so it is named too.
255
+ if (text_.has_value()) {
256
+ css += "headerbar label { color: " + cssColor(*text_) + "; }";
257
+ }
258
+ }
259
+ gtk_css_provider_load_from_string(provider_, css.c_str());
260
+ }
261
+
262
+ void GtkTitleBar::notify() {
263
+ if (listener_) {
264
+ listener_(metrics());
265
+ }
266
+ }
267
+
268
+ GtkTitleBar &titleBar() {
269
+ static GtkTitleBar instance;
270
+ return instance;
271
+ }
272
+
273
+ } // namespace basalt
@@ -0,0 +1,96 @@
1
+ // The window's title bar on Linux: its title, its colours, and whether GTK
2
+ // draws it or the app does.
3
+ //
4
+ // The same two styles Win32TitleBar.h and AppKitTitleBar.h describe, but GTK's
5
+ // window model is neither of theirs and the hidden style had to be built
6
+ // rather than switched on.
7
+ //
8
+ // - Native style. The window keeps its decorations. GTK draws them as part of
9
+ // the window (client-side), but always *above* the child rather than over
10
+ // it, so they cost the app nothing and every metric is zero -- the same
11
+ // zeroes the other two report, arrived at differently. Colours are CSS on
12
+ // the `headerbar` node, which is the only handle GTK offers.
13
+ //
14
+ // - Hidden style. `gtk_window_set_decorated(FALSE)` takes the whole titlebar
15
+ // away, buttons included -- which is where GTK parts company with the other
16
+ // two, whose systems keep drawing the caption buttons over the app's
17
+ // content. So the host draws them: a GtkWindowControls in a GtkOverlay above
18
+ // the surface root, which is the same picture by different means, and the
19
+ // metrics describe it.
20
+ //
21
+ // GTK main thread only, like the window. The BasaltWindow module posts here.
22
+
23
+ #pragma once
24
+
25
+ #include <gtk/gtk.h>
26
+
27
+ #include <cstdint>
28
+ #include <functional>
29
+ #include <optional>
30
+ #include <string>
31
+
32
+ namespace basalt {
33
+
34
+ enum class TitleBarStyle { Native, Hidden };
35
+
36
+ // What JavaScript is told, in logical pixels -- the surface root's units.
37
+ struct TitleBarMetrics {
38
+ double height{0};
39
+ double buttonsWidth{0};
40
+ TitleBarStyle style{TitleBarStyle::Native};
41
+ };
42
+
43
+ class GtkTitleBar {
44
+ public:
45
+ // The window, and the controls the host overlays when the style is hidden.
46
+ // Both borrowed; the host owns them for the life of the process.
47
+ void attach(GtkWindow *window, GtkWidget *controls);
48
+
49
+ void setTitle(std::optional<std::string> title);
50
+ // 0xAARRGGBB as processColor produces, or nullopt for GTK's own.
51
+ void setColors(std::optional<uint32_t> background,
52
+ std::optional<uint32_t> text,
53
+ std::optional<uint32_t> border);
54
+ void setStyle(TitleBarStyle style);
55
+
56
+ // What the caption buttons do, for an app that draws its own. The system's
57
+ // buttons work without any of this; a header the app drew does not.
58
+ void minimize();
59
+ void toggleMaximize();
60
+ void close();
61
+ void startDrag();
62
+
63
+ // Hands the window to the window manager's own move loop, from the press
64
+ // that started it.
65
+ //
66
+ // `x` and `y` are in surface coordinates, and the device, button and
67
+ // timestamp are the press's own: GDK will not begin a move without them,
68
+ // which is the whole reason this takes arguments where startDrag() above
69
+ // cannot. The host calls it from the gesture; see main_gtk.cpp.
70
+ void beginMoveDrag(GdkDevice *device, int button, double x, double y, guint32 timestamp);
71
+
72
+ TitleBarMetrics metrics() const;
73
+
74
+ void setMetricsListener(std::function<void(const TitleBarMetrics &)> listener);
75
+
76
+ private:
77
+ void apply();
78
+ void notify();
79
+
80
+ GtkWindow *window_{nullptr};
81
+ GtkWidget *controls_{nullptr};
82
+ // The header bar this class installs, so there is something to style. See
83
+ // attach().
84
+ GtkWidget *header_{nullptr};
85
+ GtkCssProvider *provider_{nullptr};
86
+ std::optional<std::string> title_;
87
+ std::optional<uint32_t> background_;
88
+ std::optional<uint32_t> text_;
89
+ std::optional<uint32_t> border_;
90
+ TitleBarStyle style_{TitleBarStyle::Native};
91
+ std::function<void(const TitleBarMetrics &)> listener_;
92
+ };
93
+
94
+ GtkTitleBar &titleBar();
95
+
96
+ } // namespace basalt
@@ -0,0 +1,45 @@
1
+ #include "GtkTitleBarLayout.h"
2
+
3
+ #include "TitleBarRegions.h"
4
+
5
+ namespace basalt {
6
+
7
+ bool isTitleBarDragRegionAt(RnView *root, double x, double y) {
8
+ if (root == nullptr || !RN_IS_VIEW(root)) {
9
+ return false;
10
+ }
11
+
12
+ GtkWidget *const rootWidget = GTK_WIDGET(root);
13
+ // The toolkit's own hit test, in the root's coordinates. NULL when the point
14
+ // is outside the root or lands on nothing targetable, and the loop below
15
+ // then simply does not run -- which is the same answer Windows gives for a
16
+ // miss: not a drag.
17
+ GtkWidget *widget = gtk_widget_pick(rootWidget, x, y, GTK_PICK_DEFAULT);
18
+
19
+ for (; widget != nullptr; widget = gtk_widget_get_parent(widget)) {
20
+ // The chain from a picked widget is not all RnViews. A <TextInput>'s peer
21
+ // is a GtkText, a <Switch> is a GtkSwitch, and neither carries a nativeID
22
+ // -- so they are stepped over rather than stopping the walk, and the
23
+ // marked ancestor above them still decides.
24
+ if (RN_IS_VIEW(widget)) {
25
+ const char *id = rn_view_get_native_id(RN_VIEW(widget));
26
+ if (g_strcmp0(id, kTitleBarDragRegionId) == 0) {
27
+ return true;
28
+ }
29
+ if (g_strcmp0(id, kTitleBarNoDragRegionId) == 0) {
30
+ return false;
31
+ }
32
+ }
33
+
34
+ // The root is the last thing asked, not the first thing skipped: an app
35
+ // that marks its whole surface as a drag region is answered here. Above it
36
+ // lies the host's own overlay, which is not the app's to mark.
37
+ if (widget == rootWidget) {
38
+ break;
39
+ }
40
+ }
41
+
42
+ return false;
43
+ }
44
+
45
+ } // namespace basalt
@@ -0,0 +1,36 @@
1
+ // Whether a point in an app-drawn header drags the window.
2
+ //
3
+ // The geometry half of GtkTitleBar, kept apart from it for the same reason
4
+ // Win32TitleBarLayout is kept apart from Win32TitleBar: it needs a view tree
5
+ // and nothing else -- no window, no toplevel, no React Native -- so it can be
6
+ // tested with none of them. What a custom caption gets wrong is exactly this
7
+ // -- a label that will not drag, a button that drags instead of pressing --
8
+ // and it is invisible to every other test.
9
+ //
10
+ // There is no GTK equivalent of the caption-button arithmetic that fills out
11
+ // the Windows file. GTK takes the whole titlebar away with the decorations and
12
+ // the host puts a real GtkWindowControls back, so the buttons are widgets that
13
+ // place and hit-test themselves. Only the drag question is left.
14
+
15
+ #pragma once
16
+
17
+ #include "RnView.h"
18
+
19
+ namespace basalt {
20
+
21
+ // Whether a point, in the root's coordinates, drags the window.
22
+ //
23
+ // The rule is core/TitleBarRegions.h's, and it is the one Win32 applies: the
24
+ // deepest view under the point and then each ancestor in turn, with the
25
+ // nearest one marked either way deciding. A label inside a drag region drags
26
+ // without being marked, a button inside one stays pressable once it is marked
27
+ // no-drag, and a point under nothing marked is ordinary client area.
28
+ //
29
+ // Unlike the Windows host this does not walk the tree itself -- gtk_widget_pick
30
+ // is the toolkit's own answer to "what is under this point", and it already
31
+ // honours can-target, so a view with pointerEvents="none" is skipped here for
32
+ // the same reason a click would miss it. Writing a second walk would be a
33
+ // second chance to disagree with the toolkit about what was clicked.
34
+ bool isTitleBarDragRegionAt(RnView *root, double x, double y);
35
+
36
+ } // namespace basalt