Profiles: imported LUT names shown as named, own ids for their folders (#333)
#333 passed imported LUT names and folder names through tr(), so a LUT named like a message ("Vivid") was shown translated; they are now shown as the file names them (builtin_label / new profile_label, as for user presets). A LUT folder named like a built-in group ("Film") shared that group's menu widget id and the browser section's open state; both are keyed apart now. Restores the "Amount 0 = the photo as it was" assertion #333 replaced, and adds a What's new line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01912WaK6Rmew3wxZuPyX7E8
This commit is contained in:
parent
ccdd9e45d1
commit
4eb9b565e0
|
|
@ -544,6 +544,7 @@ fn cube_luts_become_profiles() {
|
|||
mean(&warm, 2)
|
||||
);
|
||||
s.execute("develop.profile", &json!({"id": id, "amount": 0})).unwrap();
|
||||
assert_eq!(s.render_now(photo, 48, 32).unwrap().image.data, base.data, "Amount 0 = the photo as it was");
|
||||
assert_eq!(s.profile_info(&id), Some(("Warm Test", "Film Looks")));
|
||||
s.execute("profile.favorite", &json!({"id": id})).unwrap();
|
||||
assert!(s.profile_favorites.contains(&id));
|
||||
|
|
|
|||
|
|
@ -191,6 +191,12 @@ pub fn builtin_label(source: &str, builtin: bool) -> &str {
|
|||
if builtin { tr(source) } else { source }
|
||||
}
|
||||
|
||||
/// A profile's name for display: a built-in profile's is translated, an imported LUT's is shown as
|
||||
/// its file named it (it may happen to match a message, like "Vivid").
|
||||
pub fn profile_label<'a>(id: &str, name: &'a str) -> &'a str {
|
||||
builtin_label(name, lightcraft_engine::presets::profile(id).is_some())
|
||||
}
|
||||
|
||||
/// An Activity (history) step for display. The step's stored label stays English; generated steps
|
||||
/// are translated around the names they carry, and a user preset's name is kept verbatim.
|
||||
pub fn history_label(source: &str, presets: &[lightcraft_develop::Preset]) -> String {
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) {
|
|||
ui.horizontal(|ui| {
|
||||
ui.label(egui::RichText::new(crate::i18n::tr("Profile")).font(t.font(13.0)).color(t.text_dim));
|
||||
let name = app.session.profile_info(&d.profile.id).map(|(name, _)| name).unwrap_or("Color");
|
||||
let r = crate::widgets::dropdown(ui, "profile", crate::i18n::tr(name), t.font(15.0), t.text_label);
|
||||
let r = crate::widgets::dropdown(ui, "profile", crate::i18n::profile_label(&d.profile.id, name), t.font(15.0), t.text_label);
|
||||
egui::Popup::menu(&r).show(|ui| profile_menu(app, ui, &d));
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if crate::widgets::icon_button(ui, "profileBrowser", Icon::ProfileGrid, vec2(28.0, 28.0), false, true, "Browse Profiles").clicked() {
|
||||
|
|
@ -356,7 +356,7 @@ fn profile_menu(app: &mut LightcraftApp, ui: &mut egui::Ui, d: &DevelopSettings)
|
|||
id: &str,
|
||||
name: &str,
|
||||
pick: &mut (Option<String>, Option<String>)| {
|
||||
let r = ui.selectable_label(id == cur, crate::i18n::tr(name));
|
||||
let r = ui.selectable_label(id == cur, crate::i18n::profile_label(id, name));
|
||||
register(ui.ctx(), format!("profileMenu:{key}:{id}"), r.rect);
|
||||
if r.clicked() {
|
||||
pick.0 = Some(id.to_string());
|
||||
|
|
@ -393,21 +393,22 @@ fn profile_menu(app: &mut LightcraftApp, ui: &mut egui::Ui, d: &DevelopSettings)
|
|||
lut_groups.sort_unstable();
|
||||
lut_groups.dedup();
|
||||
for g in &lut_groups {
|
||||
let r = ui.menu_button(crate::i18n::tr(g), |ui| {
|
||||
// a folder's name as it is, and its own widget id even when it matches a built-in group
|
||||
let r = ui.menu_button(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);
|
||||
}
|
||||
});
|
||||
register(ui.ctx(), format!("profileMenu:groupMenu:{g}"), r.response.rect);
|
||||
register(ui.ctx(), format!("profileMenu:lutGroupMenu:{g}"), r.response.rect);
|
||||
}
|
||||
ui.separator();
|
||||
if let Some((name, _)) = app.session.profile_info(cur) {
|
||||
let fav = app.session.profile_favorites.iter().any(|f| f == cur);
|
||||
let label = if fav {
|
||||
crate::i18n::tr_format!("Remove “{}” from Favorites", crate::i18n::tr(name))
|
||||
crate::i18n::tr_format!("Remove “{}” from Favorites", crate::i18n::profile_label(cur, name))
|
||||
} else {
|
||||
crate::i18n::tr_format!("Add “{}” to Favorites", crate::i18n::tr(name))
|
||||
crate::i18n::tr_format!("Add “{}” to Favorites", crate::i18n::profile_label(cur, name))
|
||||
};
|
||||
let r = ui.button(label);
|
||||
register(ui.ctx(), "profileMenu:toggleFavorite", r.rect);
|
||||
|
|
@ -428,7 +429,7 @@ fn profile_menu(app: &mut LightcraftApp, ui: &mut egui::Ui, d: &DevelopSettings)
|
|||
let mut s = d.clone();
|
||||
s.profile.id = hovered_id.clone();
|
||||
s.profile.amount = 100.0;
|
||||
app.hover_preview = Some(crate::HoverPreview { label: crate::i18n::tr_format!("Profile: {}", crate::i18n::tr(name)), settings: s });
|
||||
app.hover_preview = Some(crate::HoverPreview { label: crate::i18n::tr_format!("Profile: {}", crate::i18n::profile_label(hovered_id, name)), settings: s });
|
||||
}
|
||||
if let Some(id) = pick.0 {
|
||||
let _ = app.run("develop.profile", json!({"id": id}));
|
||||
|
|
|
|||
|
|
@ -35,13 +35,14 @@ 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::tr(name), t.font(14.0), t.text_label);
|
||||
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);
|
||||
if d.profile.id != "lc.color" {
|
||||
super::edit::control(app, ui, &d, "profile.amount", true);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
divider(ui);
|
||||
let mut sections: Vec<(&str, &str, Vec<(String, String, String)>)> = Vec::new();
|
||||
// (title, cell tag, imported LUT folder, profiles)
|
||||
let mut sections: Vec<(&str, &str, bool, Vec<(String, String, String)>)> = Vec::new();
|
||||
let favs: Vec<(String, String, String)> = app
|
||||
.session
|
||||
.profile_favorites
|
||||
|
|
@ -49,10 +50,10 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) {
|
|||
.filter_map(|f| app.session.profile_info(f).map(|(name, group)| (f.clone(), name.to_string(), group.to_string())))
|
||||
.collect();
|
||||
if !favs.is_empty() {
|
||||
sections.push(("Favorites", "fav", favs));
|
||||
sections.push(("Favorites", "fav", false, favs));
|
||||
}
|
||||
for g in profile_groups() {
|
||||
sections.push((g, "", PROFILES.iter().filter(|p| p.group == g).map(|p| (p.id.to_string(), p.name.to_string(), p.group.to_string())).collect()));
|
||||
sections.push((g, "", false, PROFILES.iter().filter(|p| p.group == g).map(|p| (p.id.to_string(), p.name.to_string(), p.group.to_string())).collect()));
|
||||
}
|
||||
// imported LUT profile groups
|
||||
let mut lut_groups: Vec<String> = app.session.lut_profiles.iter().map(|p| p.group.clone()).collect();
|
||||
|
|
@ -66,20 +67,21 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) {
|
|||
.filter(|p| &p.group == g)
|
||||
.map(|p| (p.id.clone(), p.name.clone(), p.group.clone()))
|
||||
.collect();
|
||||
sections.push((g.as_str(), "", profiles));
|
||||
sections.push((g.as_str(), "", true, profiles));
|
||||
}
|
||||
for (title, tag, list) in sections {
|
||||
let open_id = egui::Id::new(("profile-group", title));
|
||||
for (title, tag, imported, list) in sections {
|
||||
// an imported folder named like a built-in group still opens and closes on its own
|
||||
let open_id = egui::Id::new(("profile-group", imported, title));
|
||||
let open: bool = ui.data(|m| m.get_temp(open_id)).unwrap_or(true);
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 32.0), Sense::click());
|
||||
register(ui.ctx(), format!("profileGroup:{title}"), r);
|
||||
register(ui.ctx(), if imported { format!("profileGroup:lut:{title}") } else { format!("profileGroup:{title}") }, r);
|
||||
paint(
|
||||
ui.painter(),
|
||||
Rect::from_center_size(pos2(r.left() + 22.0, r.center().y), vec2(12.0, 12.0)),
|
||||
if open { Icon::ChevronDown } else { Icon::ChevronRight },
|
||||
t.text_label,
|
||||
);
|
||||
ui.painter().text(pos2(r.left() + 36.0, r.center().y), Align2::LEFT_CENTER, crate::i18n::tr(title), t.semibold(13.0), t.text_label);
|
||||
ui.painter().text(pos2(r.left() + 36.0, r.center().y), Align2::LEFT_CENTER, crate::i18n::builtin_label(title, !imported), t.semibold(13.0), t.text_label);
|
||||
ui.painter().text(pos2(r.right() - 22.0, r.center().y), Align2::RIGHT_CENTER, list.len().to_string(), t.font(12.0), t.text_dim);
|
||||
if resp.clicked() {
|
||||
ui.data_mut(|m| m.insert_temp(open_id, !open));
|
||||
|
|
@ -109,7 +111,7 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) {
|
|||
&& let Some((p_name, _)) = app.session.profile_info(&saved_profile_id)
|
||||
{
|
||||
let s = with_profile_id(&d, &saved_profile_id);
|
||||
app.hover_preview = Some(HoverPreview { label: crate::i18n::tr_format!("Profile: {}", crate::i18n::tr(p_name)), settings: s });
|
||||
app.hover_preview = Some(HoverPreview { label: crate::i18n::tr_format!("Profile: {}", crate::i18n::profile_label(p_id, p_name)), settings: s });
|
||||
}
|
||||
} else {
|
||||
ui.data_mut(|m| m.remove::<(PhotoId, String)>(last_hover_id));
|
||||
|
|
@ -158,7 +160,7 @@ 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::tr(p_name), t.font(12.0), name_color);
|
||||
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);
|
||||
// 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));
|
||||
|
|
@ -169,10 +171,10 @@ fn cell_ui(
|
|||
paint(painter, star.shrink(4.0), if fav { Icon::StarFilled } else { Icon::Star }, if fav || over_star { t.star } else { Color32::WHITE });
|
||||
}
|
||||
if resp.hovered() && !applied && !over_star {
|
||||
app.hover_preview = Some(HoverPreview { label: crate::i18n::tr_format!("Profile: {}", crate::i18n::tr(p_name)), settings: s.clone() });
|
||||
app.hover_preview = Some(HoverPreview { label: crate::i18n::tr_format!("Profile: {}", crate::i18n::profile_label(p_id, p_name)), settings: s.clone() });
|
||||
ui.data_mut(|m| m.insert_temp(egui::Id::new("last-profile-hover"), (id, p_id.to_string())));
|
||||
}
|
||||
let resp = resp.on_hover_text(format!("{} ({})", crate::i18n::tr(p_name), crate::i18n::tr(p_group)));
|
||||
let resp = resp.on_hover_text(format!("{} ({})", crate::i18n::profile_label(p_id, p_name), crate::i18n::builtin_label(p_group, lightcraft_engine::presets::profile(p_id).is_some())));
|
||||
if resp.clicked() {
|
||||
if resp.interact_pointer_pos().is_some_and(|q| star.contains(q)) {
|
||||
let _ = app.run("profile.favorite", json!({"id": p_id}));
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@
|
|||
- Luminar looks: `.lmp` files and `.mplumpack` collections import as presets (grouped by collection); the sliders
|
||||
with a counterpart here come along, the rest is listed.
|
||||
- 23 new built-in presets: Portrait, Landscape, Urban, Food, Seasons, Vintage and B&W toners.
|
||||
- Imported `.cube` LUT profiles appear in the Profile menu and the profile browser, grouped by their folder, and stay
|
||||
favourites across restarts (issue #328).
|
||||
|
||||
### Editing
|
||||
- The Tint slider works the right way round (issues #188, #321): left adds green, right adds magenta, as its track
|
||||
|
|
|
|||
Loading…
Reference in New Issue