Fit modal dialogs to the phone screen on mobile

Add mobile::dialog_width(ctx, desired) — a min()-clamp that's a no-op on wide
desktop screens. Clamp the Export and Export Progress modals and the Sample
Import window to it. Render Preferences as an egui::Modal (dim backdrop, centered,
no title bar) with a screen-fit scroll height when mobile_active(); desktop keeps
its window. Extracted the preferences body into render_body() shared by both paths.
This commit is contained in:
Skyler Lehmkuhl 2026-07-01 06:27:59 -04:00
parent a0ac4f2d35
commit f13a127c9d
5 changed files with 102 additions and 64 deletions

View File

@ -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);

View File

@ -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");
}

View File

@ -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 {

View File

@ -151,6 +151,7 @@ impl PreferencesDialog {
ctx: &egui::Context,
config: &mut AppConfig,
theme: &mut Theme,
mobile: bool,
) -> Option<PreferencesSaveResult> {
if !self.open {
return None;
@ -160,13 +161,58 @@ impl PreferencesDialog {
let mut should_cancel = false;
let mut open = self.open;
// 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
};
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| {
ui.set_width(550.0);
self.render_body(ui, width, scroll_h, &mut should_save, &mut should_cancel);
});
}
// Update open state
self.open = open;
if should_cancel {
self.close();
return None;
}
if should_save {
return self.handle_save(config, theme);
}
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 {
@ -184,9 +230,7 @@ impl PreferencesDialog {
// Tab content
match self.tab {
PreferencesTab::General => {
egui::ScrollArea::vertical()
.max_height(400.0)
.show(ui, |ui| {
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);
@ -208,7 +252,7 @@ impl PreferencesDialog {
// Buttons
ui.horizontal(|ui| {
if ui.button("Cancel").clicked() {
should_cancel = true;
*should_cancel = true;
}
if ui.button("Reset to Defaults").clicked() {
@ -217,25 +261,10 @@ impl PreferencesDialog {
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.button("Save").clicked() {
should_save = true;
*should_save = true;
}
});
});
});
// Update open state
self.open = open;
if should_cancel {
self.close();
return None;
}
if should_save {
return self.handle_save(config, theme);
}
None
}
fn render_shortcuts_tab(&mut self, ui: &mut egui::Ui) {

View File

@ -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