From 4b2802076b03f54c14a68120696bc573f4d0387e Mon Sep 17 00:00:00 2001 From: Skyler Lehmkuhl Date: Wed, 1 Jul 2026 07:28:08 -0400 Subject: [PATCH] Touch-friendly egui widgets on mobile; egui-based inspector header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add mobile::apply_touch_style(ctx) — applied every frame when mobile is active — enlarging egui's global spacing/sizing (interact_size, button padding, slider width/rail, combo width/height, icon sizes, scrollbar, body/button text) so the standard widgets in panes and dialogs are finger-sized. Desktop is untouched. Rework the mobile inspector header into a single row using real egui widgets: title (left, truncating) + Timeline/Nodes jump buttons + ✕ close, laid out with egui layouts so they inherit the touch sizing and theme visuals instead of being hand-painted. --- .../lightningbeam-editor/src/main.rs | 4 + .../src/mobile/inspector.rs | 80 +++++++++---------- .../lightningbeam-editor/src/mobile/mod.rs | 30 +++++++ 3 files changed, 70 insertions(+), 44 deletions(-) diff --git a/lightningbeam-ui/lightningbeam-editor/src/main.rs b/lightningbeam-ui/lightningbeam-editor/src/main.rs index 63e0c6a..00772b2 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/main.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/main.rs @@ -5346,6 +5346,10 @@ impl eframe::App for EditorApp { // Apply the theme's palette to egui's global visuals so the standard widgets (dialogs, pane // chrome, text) share the same colors as the mobile UI and respond to light/dark/user themes. self.theme.apply_to_egui(ctx); + // On mobile, enlarge egui spacing/sizing so widgets in panes/dialogs are touch-friendly. + if self.mobile_active() { + mobile::apply_touch_style(ctx); + } // === Raster fault-in (Phase 3 paging) === // The canvas records raster keyframe ids whose `raw_pixels` weren't resident diff --git a/lightningbeam-ui/lightningbeam-editor/src/mobile/inspector.rs b/lightningbeam-ui/lightningbeam-editor/src/mobile/inspector.rs index e75ab69..5c23319 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/mobile/inspector.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/mobile/inspector.rs @@ -11,8 +11,7 @@ use crate::panes::{NodePath, SharedPaneState}; use crate::RenderContext; const GRAB_H: f32 = 16.0; -const HEAD_H: f32 = 30.0; -const CHIP_H: f32 = 30.0; +const HEAD_H: f32 = 44.0; fn inspector_path() -> NodePath { vec![MOBILE_NS, 200] @@ -123,61 +122,54 @@ pub fn render( ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeVertical); } - // Header row: title + close. + // Single header row (egui widgets — they inherit the mobile touch sizing + theme visuals): + // title on the left, [Timeline] [Nodes] jump buttons + ✕ close on the right. let head_y = rect.top() + GRAB_H; let head = egui::Rect::from_min_max( egui::pos2(rect.left(), head_y), egui::pos2(rect.right(), head_y + HEAD_H), ); - ui.painter().text( - egui::pos2(head.left() + 14.0, head.center().y), - egui::Align2::LEFT_CENTER, - title(&rc.shared), - egui::FontId::proportional(13.0), - pal.text, + let title_text = title(&rc.shared); + let mut jump: Option<(usize, usize)> = None; + let mut do_close = false; + let mut hui = ui.new_child( + egui::UiBuilder::new() + .max_rect(head.shrink2(egui::vec2(12.0, 5.0))) + .layout(egui::Layout::left_to_right(egui::Align::Center)), ); - let close = egui::Rect::from_min_size(egui::pos2(head.right() - 36.0, head.top()), egui::vec2(36.0, HEAD_H)); - let cresp = ui.interact(close, ui.id().with("mobile_inspector_close"), egui::Sense::click()); - ui.painter().text( - close.center(), - egui::Align2::CENTER_CENTER, - super::icons::X, - super::icons::font(16.0), - if cresp.hovered() { pal.text } else { pal.text_dim }, - ); - if cresp.clicked() { + hui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if ui + .add(egui::Button::new(egui::RichText::new(super::icons::X).font(super::icons::font(18.0))).frame(false)) + .clicked() + { + do_close = true; + } + ui.add_space(4.0); + // Reversed so the visual order stays Timeline, then Nodes. + for chip in CHIPS.iter().rev() { + if ui.button(chip.label).clicked() { + jump = Some(chip.window); + } + } + // Title fills the remaining space on the left. + ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { + ui.add(egui::Label::new(egui::RichText::new(title_text).color(pal.text)).truncate()); + }); + }); + if do_close { rc.shared.selection.clear(); *rc.shared.focus = FocusSelection::None; } - - // Jump-to chips (reframe to a related surface, keeping the selection). - let chip_y = head.bottom(); - let mut cx = rect.left() + 12.0; - for (i, chip) in CHIPS.iter().enumerate() { - let galley = ui.painter().layout_no_wrap( - chip.label.to_string(), - egui::FontId::proportional(11.0), - pal.text, - ); - let w = galley.size().x + 18.0; - let chip_rect = egui::Rect::from_min_size(egui::pos2(cx, chip_y + 4.0), egui::vec2(w, CHIP_H - 8.0)); - let resp = ui.interact(chip_rect, ui.id().with(("mobile_inspector_chip", i)), egui::Sense::click()); - ui.painter().rect_filled(chip_rect, 11.0, if resp.hovered() { pal.line } else { pal.surface_alt }); - ui.painter().rect_stroke(chip_rect, 11.0, egui::Stroke::new(1.0, pal.line), egui::StrokeKind::Inside); - ui.painter().galley(chip_rect.center() - galley.size() * 0.5, galley, pal.text); - if resp.clicked() { - let (top, count) = chip.window; - state.window_top = top; - state.window_count = count; - state.weights = [1.0, 1.0, 1.0]; - state.anim = None; - } - cx += w + 6.0; + if let Some((top, count)) = jump { + state.window_top = top; + state.window_count = count; + state.weights = [1.0, 1.0, 1.0]; + state.anim = None; } // Properties content — reuse the Infopanel full-bleed. let content = egui::Rect::from_min_max( - egui::pos2(rect.left(), chip_y + CHIP_H), + egui::pos2(rect.left(), head.bottom()), rect.max, ); if content.height() > 1.0 { diff --git a/lightningbeam-ui/lightningbeam-editor/src/mobile/mod.rs b/lightningbeam-ui/lightningbeam-editor/src/mobile/mod.rs index 0baacd3..b49863c 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/mobile/mod.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/mobile/mod.rs @@ -44,6 +44,36 @@ pub fn dialog_width(ctx: &egui::Context, desired: f32) -> f32 { desired.min(avail.max(200.0)) } +/// Enlarge egui's spacing/sizing so the standard widgets (buttons, dropdowns, sliders, text fields) +/// in panes and dialogs are touch-friendly. Applied every frame after the theme visuals when the +/// mobile shell is active. The mobile chrome (transport, omnibutton, headers) is hand-sized already; +/// this targets the egui-widget content inside panes. +pub fn apply_touch_style(ctx: &egui::Context) { + use egui::{FontId, TextStyle}; + ctx.style_mut(|s| { + let sp = &mut s.spacing; + // Minimum touch target height for buttons/sliders/checkboxes. + sp.interact_size = egui::vec2(sp.interact_size.x.max(44.0), 38.0); + sp.button_padding = egui::vec2(12.0, 9.0); + sp.item_spacing = egui::vec2(10.0, 10.0); + sp.slider_width = 200.0; + sp.slider_rail_height = 10.0; + sp.combo_width = 200.0; + sp.combo_height = 360.0; + sp.text_edit_width = 220.0; + sp.icon_width = 24.0; + sp.icon_width_inner = 14.0; + sp.icon_spacing = 8.0; + sp.scroll.bar_width = 16.0; + sp.menu_margin = egui::Margin::same(8); + // Larger text for touch legibility. + s.text_styles.insert(TextStyle::Body, FontId::proportional(15.0)); + s.text_styles.insert(TextStyle::Button, FontId::proportional(15.0)); + s.text_styles.insert(TextStyle::Monospace, FontId::monospace(13.0)); + s.text_styles.insert(TextStyle::Small, FontId::proportional(12.0)); + }); +} + /// Returns true if the mobile UI is requested via the `LB_MOBILE_UI` env var. /// Any non-empty value other than "0" enables it. pub fn is_mobile_env() -> bool {