mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-25 18:47:42 -06:00
Keep the profile dropdown and its submenus inside the window (#2059)
This commit is contained in:
@@ -140,6 +140,8 @@ mod tests_preview_limit;
|
||||
#[cfg(test)]
|
||||
mod tests_print;
|
||||
#[cfg(test)]
|
||||
mod tests_profile_menu;
|
||||
#[cfg(test)]
|
||||
mod tests_profiles_layout;
|
||||
#[cfg(test)]
|
||||
mod tests_quit_unsaved;
|
||||
|
||||
@@ -169,7 +169,8 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) {
|
||||
// content, and the sliders below would run past its edge
|
||||
let max = ui.available_width() - 28.0 - ui.spacing().item_spacing.x;
|
||||
let r = crate::widgets::dropdown_within(ui, "profile", crate::i18n::profile_label(&d.profile.id, name), t.font(15.0), t.text_label, max);
|
||||
egui::Popup::menu(&r).show(|ui| profile_menu(app, ui, &d));
|
||||
let below = r.rect.bottom();
|
||||
egui::Popup::menu(&r).show(|ui| profile_menu(app, ui, &d, below));
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if crate::widgets::icon_button(
|
||||
ui,
|
||||
@@ -429,8 +430,14 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) {
|
||||
}
|
||||
|
||||
/// The profile dropdown: Favorites, Recent, one submenu per group, then favourite toggle and
|
||||
/// Browse….
|
||||
fn profile_menu(app: &mut LightcraftApp, ui: &mut egui::Ui, d: &DevelopSettings) {
|
||||
/// Browse…. Like the menu bar's menus, the dropdown and each submenu stay inside the window and
|
||||
/// scroll when their rows don't fit (issue #2059: a folder of many imported profiles ran past the
|
||||
/// window's bottom edge). `below` is the dropdown's bottom edge, which the menu hangs from.
|
||||
fn profile_menu(app: &mut LightcraftApp, ui: &mut egui::Ui, d: &DevelopSettings, below: f32) {
|
||||
crate::menu_level::level(ui, 1, Some(below), |ui| profile_menu_rows(app, ui, d, below));
|
||||
}
|
||||
|
||||
fn profile_menu_rows(app: &mut LightcraftApp, ui: &mut egui::Ui, d: &DevelopSettings, below: f32) {
|
||||
use lightcraft_engine::presets::{PROFILES, profile_groups};
|
||||
let t = Tokens::get(ui.ctx());
|
||||
ui.set_min_width(200.0);
|
||||
@@ -463,12 +470,17 @@ fn profile_menu(app: &mut LightcraftApp, ui: &mut egui::Ui, d: &DevelopSettings)
|
||||
}
|
||||
for g in profile_groups() {
|
||||
let r = crate::widgets::menu_button(ui, crate::i18n::tr(g), |ui| {
|
||||
ui.set_min_width(170.0);
|
||||
for p in PROFILES.iter().filter(|p| p.group == g) {
|
||||
item(ui, "group", p.id, p.name, &mut pick);
|
||||
}
|
||||
crate::menu_level::level(ui, 2, Some(below), |ui| {
|
||||
ui.set_min_width(170.0);
|
||||
for p in PROFILES.iter().filter(|p| p.group == g) {
|
||||
item(ui, "group", p.id, p.name, &mut pick);
|
||||
}
|
||||
});
|
||||
});
|
||||
register(ui.ctx(), format!("profileMenu:groupMenu:{g}"), r.response.rect);
|
||||
if r.inner.is_some() {
|
||||
crate::menu_level::set_anchor(ui.ctx(), 2, r.response.rect);
|
||||
}
|
||||
}
|
||||
// imported LUT profile groups
|
||||
let mut lut_groups: Vec<String> = app.session.lut_profiles.iter().map(|p| p.group.clone()).collect();
|
||||
@@ -477,12 +489,17 @@ fn profile_menu(app: &mut LightcraftApp, ui: &mut egui::Ui, d: &DevelopSettings)
|
||||
for g in &lut_groups {
|
||||
// a folder's name as it is, and its own widget id even when it matches a built-in group
|
||||
let r = crate::widgets::menu_button(ui, g.as_str(), |ui| {
|
||||
ui.set_min_width(170.0);
|
||||
for p in app.session.lut_profiles.iter().filter(|p| &p.group == g) {
|
||||
item(ui, "group", &p.id, &p.name, &mut pick);
|
||||
}
|
||||
crate::menu_level::level(ui, 2, Some(below), |ui| {
|
||||
ui.set_min_width(170.0);
|
||||
for p in app.session.lut_profiles.iter().filter(|p| &p.group == g) {
|
||||
item(ui, "group", &p.id, &p.name, &mut pick);
|
||||
}
|
||||
});
|
||||
});
|
||||
register(ui.ctx(), format!("profileMenu:lutGroupMenu:{g}"), r.response.rect);
|
||||
if r.inner.is_some() {
|
||||
crate::menu_level::set_anchor(ui.ctx(), 2, r.response.rect);
|
||||
}
|
||||
}
|
||||
ui.separator();
|
||||
if let Some((name, _)) = app.session.profile_info(cur) {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
//! #2059: the profile dropdown and its submenus stay inside the window. A folder of many imported
|
||||
//! profiles used to make its submenu run past the window's bottom edge, and the profiles below it
|
||||
//! could not be reached.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use serde_json::json;
|
||||
|
||||
use crate::headless::Headless;
|
||||
use crate::{LightcraftApp, Services};
|
||||
|
||||
const T: Duration = Duration::from_secs(5);
|
||||
const SETTLE: Duration = Duration::from_secs(10);
|
||||
const FOLDER: &str = "Imported";
|
||||
|
||||
fn widget(h: &Headless, id: &str) -> Option<egui::Rect> {
|
||||
h.app.widgets.iter().find(|(w, _)| w == id).map(|(_, r)| *r)
|
||||
}
|
||||
|
||||
fn go(h: &mut Headless, method: &str, id: &str) {
|
||||
let r = h.request(method, json!({"id": id}), T);
|
||||
assert_eq!(r["ok"], true, "{method} {id}: {r}");
|
||||
h.settle(SETTLE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_folder_of_many_imported_profiles_scrolls_inside_the_window() {
|
||||
let mut app = LightcraftApp::new(lightcraft_engine::Session::with_demo(), Services { png: None, ..Default::default() });
|
||||
for i in 0..80 {
|
||||
app.session.lut_profiles.push(lightcraft_engine::cmd::lut_profiles::LutProfile {
|
||||
id: format!("lut:imported-{i}"),
|
||||
name: format!("Imported {i}"),
|
||||
group: FOLDER.into(),
|
||||
file: String::new(),
|
||||
});
|
||||
}
|
||||
let mut h = Headless::new(app, [1200.0, 900.0], 1.0);
|
||||
let r = h.request("ui.set", json!({"view": "detail"}), T);
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
h.app.ui.right = crate::state::RightPanel::Edit;
|
||||
h.settle(SETTLE);
|
||||
go(&mut h, "ui.clickWidget", "dropdown:profile");
|
||||
let menu = widget(&h, "menu-level:1").expect("the profile menu is open");
|
||||
assert!(menu.bottom() <= h.size.y + 0.5, "the profile menu {menu:?} runs past the window's bottom {}", h.size.y);
|
||||
go(&mut h, "ui.hoverWidget", &format!("profileMenu:lutGroupMenu:{FOLDER}"));
|
||||
let sub = widget(&h, "menu-level:2").expect("the imported folder's submenu is open");
|
||||
assert!(sub.bottom() <= h.size.y + 0.5, "the submenu {sub:?} runs past the window's bottom {}", h.size.y);
|
||||
// Its 80 rows don't all fit: some are scrolled out of view rather than drawn off screen.
|
||||
let rows: Vec<egui::Rect> = (0..80).filter_map(|i| widget(&h, &format!("profileMenu:group:lut:imported-{i}"))).collect();
|
||||
assert!(!rows.is_empty(), "no imported profile rows drawn");
|
||||
assert!(rows.iter().any(|r| !sub.contains_rect(*r)), "every row fits a 900 pt window");
|
||||
}
|
||||
Reference in New Issue
Block a user