From 56c987be6bf7b923a59a7dc73d58b217c73a056e Mon Sep 17 00:00:00 2001 From: Pascal Garber Date: Tue, 22 Sep 2026 13:01:49 +0200 Subject: [PATCH] Android: use shipped Gtk.Button/Adw.ButtonContent adw-main-button.ts hand-composed a Gtk.Image + Label in a StackLayout; that pair is Adw.ButtonContent inside Gtk.Button, both shipped by @gjsify/adwaita-nativescript. Keep the MainButtonState machine and the .adw-fab pill CSS, replace only the composition. gamepad.xml Builder.load'd plain @nativescript/core Buttons; swap them for gtk:Button via a new app/gtk.ts XML door (the app's own convention, mirrored from ~/widgets/index). class= becomes the widget's own styleClasses= door so "adw-button" survives, text= becomes label=. hex-monitor.ts is left alone: AdwDataGrid always renders a header row this dump never had, its default cell padding (9,12) is tuned for sparse financial-statement rows while BYTES_PER_ROW was already cut 8<-16 to fit phone width, its "first column absorbs slack" default inverts the desired tight equal-width byte columns, and it has no row shape for the single spanning "invalid range" message. --- packages/app-android/app/app.css | 15 ++++-- packages/app-android/app/gtk.ts | 6 +++ .../app/widgets/adw-main-button.ts | 52 +++++++++---------- .../app/widgets/game-console/gamepad.ts | 28 +++++----- .../app/widgets/game-console/gamepad.xml | 14 ++--- 5 files changed, 62 insertions(+), 53 deletions(-) create mode 100644 packages/app-android/app/gtk.ts diff --git a/packages/app-android/app/app.css b/packages/app-android/app/app.css index 4bedcba9..bc3127c9 100644 --- a/packages/app-android/app/app.css +++ b/packages/app-android/app/app.css @@ -139,20 +139,25 @@ TextView.no-underline { margin-right: var(--safeAreaInsetRight, 0) !important; } -/* Adwaita floating action button (the main Assemble / Run / … button). */ +/* Adwaita floating action button (the main Assemble / Run / … button): a + `Gtk.Button` carrying `Adw.ButtonContent`. `height: auto` overrides the fixed 38 + `.adw-button` sets — the FAB sizes to its icon+label plus this padding, as the old + hand-built StackLayout did. */ .adw-fab { background-color: #3584e4; border-radius: 9999; padding: 10 18 10 18; margin: 16; + height: auto; android-elevation: 0; } -.adw-fab-label { +/* `Adw.ButtonContent`'s label defaults to Adwaita's dark button text and only turns + white for `.suggested-action` or dark mode (`.adw-button-content-label` / + `.ns-dark .adw-button-content-label` in the theme) — the FAB's accent background + needs it white unconditionally, like the old `.adw-fab-label` did. */ +.adw-fab .adw-button-content-label { color: #ffffff; - font-weight: 700; - margin-left: 8; - vertical-align: middle; } .ns-dark .adw-fab { diff --git a/packages/app-android/app/gtk.ts b/packages/app-android/app/gtk.ts new file mode 100644 index 00000000..b8087863 --- /dev/null +++ b/packages/app-android/app/gtk.ts @@ -0,0 +1,6 @@ +// XML door for `Gtk.*` widgets — `` resolves through +// this barrel, the same way `~/widgets/index` resolves ``. A plain XML +// app (no Angular/Vue) has no `registerElement` global to register widgets through, +// so `@gjsify/adwaita-nativescript`'s own convention is an app-local barrel per +// namespace that re-exports the package's `/gtk` (and `/adw`) subpath. +export * from "@gjsify/adwaita-nativescript/gtk"; diff --git a/packages/app-android/app/widgets/adw-main-button.ts b/packages/app-android/app/widgets/adw-main-button.ts index 8626b6c9..a1c94962 100644 --- a/packages/app-android/app/widgets/adw-main-button.ts +++ b/packages/app-android/app/widgets/adw-main-button.ts @@ -1,6 +1,5 @@ -import { Label, StackLayout } from "@nativescript/core"; import { localize as _ } from "@nativescript/localize"; -import { Gtk, attachRowPressFeedback } from "@gjsify/adwaita-nativescript"; +import { Adw, Gtk, GTK_BUTTON_CLICKED } from "@gjsify/adwaita-nativescript"; import { systemRunSymbolic, mediaPlaybackStartSymbolic, @@ -31,39 +30,38 @@ const MODES: Partial> = { }; /** - * Adwaita-styled floating action button: a pill holding a white symbolic icon + - * label on the accent background. It is stateless w.r.t. the simulator — the shell - * computes the MainButtonState and calls setState(); a tap invokes - * onAction(currentAction). Replaces the Material `MainButton`/`Fab` widget. + * Adwaita-styled floating action button: a `Gtk.Button` holding an `Adw.ButtonContent` + * (white symbolic icon + label) on the accent `.adw-fab` pill background. It is + * stateless w.r.t. the simulator — the shell computes the MainButtonState and calls + * setState(); a tap invokes onAction(currentAction). Replaces the Material + * `MainButton`/`Fab` widget. */ -export class AdwMainButton extends StackLayout { +export class AdwMainButton extends Gtk.Button { /** Invoked on tap with the current mode's action. Wired by the shell. */ public onAction: ((action: MainButtonAction) => void) | null = null; - private readonly _icon: Gtk.Image; - private readonly _label: Label; + // Not `_content`: `Gtk.Button` already declares a private field of that name + // (its own single-child slot), and TypeScript refuses two private declarations + // of the same name across a base/subclass pair. + private readonly _buttonContent: Adw.ButtonContent; private _state: MainButtonState = MainButtonState.ASSEMBLE; constructor() { super(); - this.orientation = "horizontal"; - this.className = "adw-fab"; + // `add_css_class`, not a `className` assignment: the button's constructor already + // put `adw-button` there, and a raw overwrite would drop it — worse, it would come + // back on the next `styleClasses`/`add_css_class` call, which rebuilds `className` + // from the tracked list and has no idea `adw-fab` was ever there. + this.add_css_class("adw-fab"); - const icon = new Gtk.Image(); - icon.iconColor = "#ffffff"; // pinned white on the accent pill, both schemes - icon.verticalAlignment = "middle"; - this._icon = icon; - this.addChild(icon); + // Icon+label IS `Adw.ButtonContent`. `Gtk.Button` already wires the press-darken + // (`attachRowPressFeedback`, in its own constructor) — the only app-specific parts + // left are the MainButtonState machine below and the `.adw-fab` pill styling. + const buttonContent = new Adw.ButtonContent({ iconColor: "#ffffff" }); // pinned white on the accent pill, both schemes + this._buttonContent = buttonContent; + this.child = buttonContent; - const label = new Label(); - label.className = "adw-fab-label"; - label.verticalAlignment = "middle"; - this._label = label; - this.addChild(label); - - // Adwaita buttons darken on press; NS only auto-applies that to `Button`. - attachRowPressFeedback(this); - this.addEventListener("tap", () => { + this.addEventListener(GTK_BUTTON_CLICKED, () => { const mode = MODES[this._state]; if (mode && this.onAction) this.onAction(mode.action); }); @@ -83,7 +81,7 @@ export class AdwMainButton extends StackLayout { return; } this.visibility = "visible"; - this._icon.iconName = mode.icon; - this._label.text = _(mode.label); + this._buttonContent.iconName = mode.icon; + this._buttonContent.label = _(mode.label); } } diff --git a/packages/app-android/app/widgets/game-console/gamepad.ts b/packages/app-android/app/widgets/game-console/gamepad.ts index 580b9456..62089ba7 100644 --- a/packages/app-android/app/widgets/game-console/gamepad.ts +++ b/packages/app-android/app/widgets/game-console/gamepad.ts @@ -1,5 +1,5 @@ -import type { Button } from "@nativescript/core"; import { GridLayout, Builder } from "@nativescript/core"; +import { Gtk } from "@gjsify/adwaita-nativescript"; import type { GamepadKey, GamepadEventMap, GamepadWidget } from "@learn6502/common-ui"; import { getGamepadKeyCode, getGamepadKeyChar } from "@learn6502/common-ui"; import { EventDispatcher } from "@learn6502/core"; @@ -12,12 +12,12 @@ export class Gamepad extends GridLayout implements GamepadWidget { readonly events = new EventDispatcher(); // Button references - private buttonUp: Button | null = null; - private buttonDown: Button | null = null; - private buttonLeft: Button | null = null; - private buttonRight: Button | null = null; - private buttonA: Button | null = null; - private buttonB: Button | null = null; + private buttonUp: Gtk.Button | null = null; + private buttonDown: Gtk.Button | null = null; + private buttonLeft: Gtk.Button | null = null; + private buttonRight: Gtk.Button | null = null; + private buttonA: Gtk.Button | null = null; + private buttonB: Gtk.Button | null = null; constructor() { super(); @@ -30,12 +30,12 @@ export class Gamepad extends GridLayout implements GamepadWidget { this.addChild(componentView); - this.buttonUp = componentView.getViewById