diff --git a/lightningbeam-ui/lightningbeam-editor/src/export/dialog.rs b/lightningbeam-ui/lightningbeam-editor/src/export/dialog.rs index aa5dddb..7438836 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/export/dialog.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/export/dialog.rs @@ -208,7 +208,7 @@ impl ExportDialog { let modal_response = egui::Modal::new(egui::Id::new("export_dialog_modal")) .show(ctx, |ui| { - ui.set_width(500.0); + ui.set_width(crate::mobile::dialog_width(ctx, 500.0)); ui.heading(window_title); ui.add_space(8.0); @@ -801,7 +801,7 @@ impl ExportProgressDialog { egui::Modal::new(egui::Id::new("export_progress_modal")) .show(ctx, |ui| { - ui.set_width(400.0); + ui.set_width(crate::mobile::dialog_width(ctx, 400.0)); ui.heading("Exporting..."); ui.add_space(8.0); diff --git a/lightningbeam-ui/lightningbeam-editor/src/main.rs b/lightningbeam-ui/lightningbeam-editor/src/main.rs index f00990a..ec0f956 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/main.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/main.rs @@ -6329,7 +6329,8 @@ impl eframe::App for EditorApp { } // Render preferences dialog - if let Some(result) = self.preferences_dialog.render(ctx, &mut self.config, &mut self.theme) { + let mobile = self.mobile_active(); + if let Some(result) = self.preferences_dialog.render(ctx, &mut self.config, &mut self.theme, mobile) { if result.buffer_size_changed { println!("⚠️ Audio buffer size will be applied on next app restart"); } diff --git a/lightningbeam-ui/lightningbeam-editor/src/mobile/mod.rs b/lightningbeam-ui/lightningbeam-editor/src/mobile/mod.rs index 00d0806..9c722ca 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/mobile/mod.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/mobile/mod.rs @@ -34,6 +34,13 @@ pub const MOBILE_NS: usize = usize::MAX; const TRANSPORT_H: f32 = 60.0; const TOPBAR_H: f32 = 40.0; +/// Clamp a desktop dialog width to fit the current screen (with side margins). A no-op on wide +/// desktop screens (`min` keeps the desired width); on a phone-aspect window it shrinks to fit. +pub fn dialog_width(ctx: &egui::Context, desired: f32) -> f32 { + let avail = ctx.screen_rect().width() - 24.0; + desired.min(avail.max(200.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 { diff --git a/lightningbeam-ui/lightningbeam-editor/src/preferences/dialog.rs b/lightningbeam-ui/lightningbeam-editor/src/preferences/dialog.rs index e0f0989..16e379d 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/preferences/dialog.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/preferences/dialog.rs @@ -151,6 +151,7 @@ impl PreferencesDialog { ctx: &egui::Context, config: &mut AppConfig, theme: &mut Theme, + mobile: bool, ) -> Option { if !self.open { return None; @@ -160,68 +161,32 @@ impl PreferencesDialog { let mut should_cancel = false; let mut open = self.open; - egui::Window::new("Preferences") - .open(&mut open) - .resizable(false) - .collapsible(false) - .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO) - .show(ctx, |ui| { - ui.set_width(550.0); + // On mobile, render as a screen-fitting modal sheet (dim backdrop, centered) like the other + // mobile modals; on desktop, the familiar draggable window. + let width = crate::mobile::dialog_width(ctx, 550.0); + let scroll_h = if mobile { + (ctx.screen_rect().height() - 220.0).clamp(160.0, 400.0) + } else { + 400.0 + }; - // Error message - if let Some(error) = &self.error_message { - ui.colored_label(egui::Color32::from_rgb(255, 100, 100), error); - ui.add_space(8.0); - } - - // Tab bar - ui.horizontal(|ui| { - ui.selectable_value(&mut self.tab, PreferencesTab::General, "General"); - ui.selectable_value(&mut self.tab, PreferencesTab::Shortcuts, "Keyboard Shortcuts"); - }); - ui.separator(); - - // Tab content - match self.tab { - PreferencesTab::General => { - egui::ScrollArea::vertical() - .max_height(400.0) - .show(ui, |ui| { - self.render_general_section(ui); - ui.add_space(8.0); - self.render_audio_section(ui); - ui.add_space(8.0); - self.render_appearance_section(ui); - ui.add_space(8.0); - self.render_startup_section(ui); - ui.add_space(8.0); - self.render_advanced_section(ui); - }); - } - PreferencesTab::Shortcuts => { - self.render_shortcuts_tab(ui); - } - } - - ui.add_space(16.0); - - // Buttons - ui.horizontal(|ui| { - if ui.button("Cancel").clicked() { - should_cancel = true; - } - - if ui.button("Reset to Defaults").clicked() { - self.reset_to_defaults(); - } - - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - if ui.button("Save").clicked() { - should_save = true; - } - }); - }); + if mobile { + let resp = egui::Modal::new(egui::Id::new("preferences_modal")).show(ctx, |ui| { + self.render_body(ui, width, scroll_h, &mut should_save, &mut should_cancel); }); + if resp.backdrop_response.clicked() { + should_cancel = true; + } + } else { + egui::Window::new("Preferences") + .open(&mut open) + .resizable(false) + .collapsible(false) + .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO) + .show(ctx, |ui| { + self.render_body(ui, width, scroll_h, &mut should_save, &mut should_cancel); + }); + } // Update open state self.open = open; @@ -238,6 +203,70 @@ impl PreferencesDialog { None } + #[allow(clippy::too_many_arguments)] + fn render_body( + &mut self, + ui: &mut egui::Ui, + width: f32, + scroll_h: f32, + should_save: &mut bool, + should_cancel: &mut bool, + ) { + ui.set_width(width); + + // Error message + if let Some(error) = &self.error_message { + ui.colored_label(egui::Color32::from_rgb(255, 100, 100), error); + ui.add_space(8.0); + } + + // Tab bar + ui.horizontal(|ui| { + ui.selectable_value(&mut self.tab, PreferencesTab::General, "General"); + ui.selectable_value(&mut self.tab, PreferencesTab::Shortcuts, "Keyboard Shortcuts"); + }); + ui.separator(); + + // Tab content + match self.tab { + PreferencesTab::General => { + egui::ScrollArea::vertical().max_height(scroll_h).show(ui, |ui| { + self.render_general_section(ui); + ui.add_space(8.0); + self.render_audio_section(ui); + ui.add_space(8.0); + self.render_appearance_section(ui); + ui.add_space(8.0); + self.render_startup_section(ui); + ui.add_space(8.0); + self.render_advanced_section(ui); + }); + } + PreferencesTab::Shortcuts => { + self.render_shortcuts_tab(ui); + } + } + + ui.add_space(16.0); + + // Buttons + ui.horizontal(|ui| { + if ui.button("Cancel").clicked() { + *should_cancel = true; + } + + if ui.button("Reset to Defaults").clicked() { + self.reset_to_defaults(); + } + + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if ui.button("Save").clicked() { + *should_save = true; + } + }); + }); + } + fn render_shortcuts_tab(&mut self, ui: &mut egui::Ui) { // Capture key events for rebinding BEFORE rendering the rest if let Some(rebind_action) = self.rebinding { diff --git a/lightningbeam-ui/lightningbeam-editor/src/sample_import_dialog.rs b/lightningbeam-ui/lightningbeam-editor/src/sample_import_dialog.rs index 42b7150..10b5aac 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/sample_import_dialog.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/sample_import_dialog.rs @@ -58,7 +58,8 @@ impl SampleImportDialog { .resizable(true) .collapsible(false) .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO) - .default_width(700.0) + .default_width(crate::mobile::dialog_width(ctx, 700.0)) + .max_width(crate::mobile::dialog_width(ctx, 700.0)) .default_height(500.0) .show(ctx, |ui| { // Folder info