LR-PROF-BROWSER: long profile names are cut short in a narrow panel (fixes #1634)

This commit is contained in:
Marco Arnulfo
2026-10-14 12:58:40 +02:00
parent a4b53b28cc
commit 0cf062f508
3 changed files with 79 additions and 15 deletions
+2
View File
@@ -109,6 +109,8 @@ mod tests_preset_import;
#[cfg(test)]
mod tests_preview_limit;
#[cfg(test)]
mod tests_profiles_layout;
#[cfg(test)]
mod tests_quit_unsaved;
#[cfg(test)]
mod tests_sam_setup;
+7 -15
View File
@@ -12,7 +12,7 @@ use super::presets::cover_uv;
use crate::icons::{Icon, paint};
use crate::state::RightPanel;
use crate::theme::Tokens;
use crate::widgets::{divider, icon_button, register};
use crate::widgets::{divider, icon_button, one_line, register};
use crate::{HoverPreview, LightcraftApp};
/// Long edge of the variant thumbnails (px).
@@ -35,13 +35,9 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) {
let (cr, _) = ui.allocate_exact_size(vec2(ui.available_width(), 34.0), Sense::hover());
let name = app.session.profile_info(&d.profile.id).map(|(name, _)| name).unwrap_or("Color");
ui.painter().text(pos2(cr.left() + 24.0, cr.center().y), Align2::LEFT_CENTER, crate::i18n::tr("Profile"), t.font(13.0), t.text_dim);
ui.painter().text(
pos2(cr.left() + 84.0, cr.center().y),
Align2::LEFT_CENTER,
crate::i18n::profile_label(&d.profile.id, name),
t.font(14.0),
t.text_label,
);
// a long name is cut short (with …) at the panel's edge
let g = one_line(ui.painter(), crate::i18n::profile_label(&d.profile.id, name), t.font(14.0), t.text_label, cr.width() - 84.0 - 24.0);
ui.painter().galley(pos2(cr.left() + 84.0, cr.center().y - g.size().y / 2.0), g, t.text_label);
if d.profile.id != "lc.color" {
super::edit::control(app, ui, &d, "profile.amount", true);
ui.add_space(6.0);
@@ -174,13 +170,9 @@ fn cell_ui(
painter.rect_stroke(thumb, 3.0, Stroke::new(1.0, t.text_dim), egui::StrokeKind::Outside);
}
let name_color = if applied { t.text } else { t.text_label };
painter.text(
pos2(cell.left() + 2.0, thumb.bottom() + 12.0),
Align2::LEFT_CENTER,
crate::i18n::profile_label(p_id, p_name),
t.font(12.0),
name_color,
);
// cut short (with …) to the cell, so it never runs into its neighbour; the full name on hover
let g = one_line(painter, crate::i18n::profile_label(p_id, p_name), t.font(12.0), name_color, cell.width() - 2.0);
painter.galley(pos2(cell.left() + 2.0, thumb.bottom() + 12.0 - g.size().y / 2.0), g, name_color);
// favourite star (always shown when set, on hover otherwise)
let fav = app.session.profile_favorites.iter().any(|f| f == p_id);
let star = Rect::from_center_size(pos2(thumb.right() - 13.0, thumb.top() + 13.0), vec2(20.0, 20.0));
@@ -0,0 +1,70 @@
//! The profile browser in a narrow right panel: names stay inside their cells (#1634).
use egui::{Rect, pos2, vec2};
use serde_json::json;
use crate::{LightcraftApp, Services, headless::HeadlessView};
const LONG_ID: &str = "lut:kodak-portra-400-plus-one-stop-overexposed";
const LONG_NAME: &str = "Kodak Portra 400 Plus One Stop Overexposed";
fn text_shapes(shape: &egui::Shape, out: &mut Vec<egui::epaint::TextShape>) {
match shape {
egui::Shape::Text(text) => out.push(text.clone()),
egui::Shape::Vec(shapes) => shapes.iter().for_each(|s| text_shapes(s, out)),
_ => {}
}
}
fn panel(width: f32) -> (Vec<egui::epaint::TextShape>, Vec<(String, Rect)>) {
let mut app = LightcraftApp::new(lightcraft_engine::Session::with_demo(), Services::default());
app.session.lut_profiles.push(lightcraft_engine::cmd::lut_profiles::LutProfile {
id: LONG_ID.into(),
name: LONG_NAME.into(),
group: "Film Stocks".into(),
file: String::new(),
});
app.run("develop.profile", json!({"id": LONG_ID})).unwrap();
let photo = app.session.active().unwrap();
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
crate::theme::apply(&ctx);
let mut texts = Vec::new();
let mut widgets = Vec::new();
for frame in 0..3 {
let raw = HeadlessView::raw_input(vec2(width, 4000.0), 1.0, frame as f64 / 60.0, vec![]);
let mut out = ctx.run_ui(raw, |ui| {
egui::CentralPanel::default().frame(egui::Frame::NONE).show(ui, |ui| {
crate::panels::profiles::show(&mut app, ui, photo);
});
});
out.textures_delta.clear();
texts.clear();
for shape in &out.shapes {
text_shapes(&shape.shape, &mut texts);
}
widgets = crate::widgets::take_registry(&ctx);
}
(texts, widgets)
}
#[test]
fn profile_names_stay_inside_their_cells_in_a_narrow_panel() {
let width = 250.0;
let (texts, widgets) = panel(width);
let cell = widgets.iter().find(|(id, _)| id == &format!("profileCell:{LONG_ID}")).unwrap().1;
// the cell's name sits below its thumbnail; the header's copy is above every cell
let name = texts.iter().find(|t| t.galley.job.text == LONG_NAME && t.pos.y >= cell.top()).unwrap();
assert!(name.galley.elided, "a name wider than its cell is cut short");
let bounds = name.visual_bounding_rect();
assert!(bounds.left() >= cell.left() && bounds.right() <= cell.right(), "{bounds:?} spills out of {cell:?}");
let applied = texts.iter().find(|t| t.galley.job.text == LONG_NAME && t.pos.y < cell.top()).unwrap();
assert!(applied.visual_bounding_rect().right() <= width, "the applied profile's name leaves the panel");
// built-in names too: each one stays out of its neighbour's cell
for p in lightcraft_engine::presets::PROFILES {
let cell = widgets.iter().find(|(id, _)| id == &format!("profileCell:{}", p.id)).unwrap().1;
let name = texts.iter().find(|t| t.galley.job.text == p.name && cell.contains(pos2(cell.center().x, t.pos.y))).unwrap();
let bounds = name.visual_bounding_rect();
assert!(bounds.left() >= cell.left() && bounds.right() <= cell.right(), "{}: {bounds:?} spills out of {cell:?}", p.name);
}
}