mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-26 02:59:17 -06:00
Stack badges: filmstrip badge expands/collapses, badges work on unselected stacks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d999e382ba
commit
084cd3ddd3
@@ -2643,13 +2643,26 @@ pub(crate) fn filmstrip(app: &mut LightcraftApp, ui: &mut egui::Ui, r: Rect) {
|
||||
film_badges(p, &t, fr, ph);
|
||||
}
|
||||
if let Some(st) = app.session.catalog.stack_of(*id) {
|
||||
let collapsed = st.collapsed;
|
||||
let text =
|
||||
if st.collapsed { st.photos.len().to_string() } else { format!("{}/{}", st.position(*id).unwrap_or(0) + 1, st.photos.len()) };
|
||||
if collapsed { st.photos.len().to_string() } else { format!("{}/{}", st.position(*id).unwrap_or(0) + 1, st.photos.len()) };
|
||||
let g = p.layout_no_wrap(text, t.semibold(9.5), Color32::WHITE);
|
||||
let br = Rect::from_min_size(fr.min + vec2(3.0, 3.0), vec2(g.size().x + 22.0, 15.0));
|
||||
p.rect_filled(br, 7.5, Color32::from_black_alpha(170));
|
||||
// like the grid's badge: a click expands / collapses the stack
|
||||
let badge = ui.interact(br, egui::Id::new(("film-stack-badge", id.0)), Sense::click()).on_hover_text(if collapsed {
|
||||
"Stack — click to expand"
|
||||
} else {
|
||||
"Stack — click to collapse"
|
||||
});
|
||||
register(ui.ctx(), format!("filmstack:{}", id.0), br);
|
||||
let p = ui.painter();
|
||||
let fill = if badge.hovered() { Color32::from_black_alpha(230) } else { Color32::from_black_alpha(170) };
|
||||
p.rect_filled(br, 7.5, fill);
|
||||
crate::icons::paint(p, Rect::from_min_size(br.min + vec2(4.0, 2.0), vec2(11.0, 11.0)), crate::icons::Icon::Stack, Color32::WHITE);
|
||||
p.galley(pos2(br.min.x + 17.0, br.center().y - g.size().y / 2.0), g, Color32::WHITE);
|
||||
if badge.clicked() {
|
||||
super::grid::toggle_stack(app, ui.ctx(), *id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if resp.clicked() {
|
||||
|
||||
@@ -1035,7 +1035,18 @@ pub fn stack_badge(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId, sid:
|
||||
paint(p, Rect::from_min_size(br.min + vec2(6.0, 3.0), vec2(14.0, 14.0)), Icon::Stack, Color32::WHITE);
|
||||
p.galley(pos2(br.min.x + 24.0, br.center().y - g.size().y / 2.0), g, Color32::WHITE);
|
||||
if resp.clicked() {
|
||||
let _ = app.run("stack.toggle", json!({"ids": [id.0]}));
|
||||
toggle_stack(app, ui.ctx(), id);
|
||||
}
|
||||
}
|
||||
|
||||
/// A click on a stack badge (grid or filmstrip): expand / collapse that photo's stack. The
|
||||
/// photo is selected first when it isn't, as `stack.toggle` acts on a selected stack.
|
||||
pub fn toggle_stack(app: &mut LightcraftApp, ctx: &egui::Context, id: PhotoId) {
|
||||
if !app.session.selection.contains(id) {
|
||||
let _ = app.run("library.select", json!({"ids": [id.0]}));
|
||||
}
|
||||
if let Err(e) = app.run("stack.toggle", json!({"ids": [id.0]})) {
|
||||
app.toast(ctx, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//! Headless test of the filmstrip's file-name labels (#266): a name with multi-byte (CJK)
|
||||
//! characters is drawn without panicking.
|
||||
//! Headless tests of the filmstrip: file-name labels (#266) — a name with multi-byte (CJK)
|
||||
//! characters is drawn without panicking — and its stack badges, which expand / collapse a stack.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use lightcraft_catalog::Op;
|
||||
use lightcraft_catalog::{Op, Stack, StackId};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::headless::Headless;
|
||||
@@ -28,3 +28,32 @@ fn filmstrip_shows_a_cjk_file_name() {
|
||||
let film = format!("film:{}", first.0);
|
||||
assert!(h.app.widgets.iter().any(|(w, _)| *w == film), "{film} drawn in the filmstrip");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filmstrip_stack_badge_expands_and_collapses_the_stack() {
|
||||
let mut session = lightcraft_engine::Session::with_demo();
|
||||
let vis = session.visible_cloned();
|
||||
let (loose, top, member) = (vis[0], vis[1], vis[2]);
|
||||
let stack = Stack { id: StackId(1), photos: vec![top, member], collapsed: true };
|
||||
session.catalog.apply(Op::AddStack { stack }).unwrap();
|
||||
let app = LightcraftApp::new(session, Services { png: None, ..Default::default() });
|
||||
let mut h = Headless::new(app, [1200.0, 800.0], 1.0);
|
||||
// the loose photo is the one in the loupe: the stack isn't selected
|
||||
let r = h.request("engine.execute", json!({"command": "library.select", "params": {"ids": [loose.0]}}), T);
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
let r = h.request("ui.set", json!({"view": "detail"}), T);
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
h.settle(SETTLE);
|
||||
assert!(!h.app.session.visible_cloned().contains(&member), "collapsed: only the top shows");
|
||||
let badge = format!("filmstack:{}", top.0);
|
||||
let r = h.request("ui.clickWidget", json!({"id": badge}), T);
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
h.settle(SETTLE);
|
||||
assert!(!h.app.session.catalog.stack(StackId(1)).unwrap().collapsed, "a click on the badge expands the stack");
|
||||
assert!(h.app.session.visible_cloned().contains(&member));
|
||||
let r = h.request("ui.clickWidget", json!({"id": badge}), T);
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
h.settle(SETTLE);
|
||||
assert!(h.app.session.catalog.stack(StackId(1)).unwrap().collapsed, "a second click collapses it");
|
||||
assert!(!h.app.session.visible_cloned().contains(&member));
|
||||
}
|
||||
|
||||
@@ -172,3 +172,21 @@ fn an_unedited_raws_thumbnail_skips_the_sensor_decode() {
|
||||
assert!(!skips_the_real_render(false, true));
|
||||
assert!(!skips_the_real_render(false, false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grid_stack_badge_toggles_a_stack_that_is_not_selected() {
|
||||
let mut h = grid(ViewMode::PhotoGrid);
|
||||
// a stack on screen, and a loose photo as the active one
|
||||
let top = PhotoId(2001);
|
||||
select(&mut h, top);
|
||||
let vis = h.app.session.visible().to_vec();
|
||||
let i = vis.iter().position(|p| *p == top).unwrap();
|
||||
let loose = vis[i.saturating_sub(1)..].iter().copied().find(|p| h.app.session.catalog.stack_of(*p).is_none()).unwrap();
|
||||
select(&mut h, loose);
|
||||
assert!(!h.app.session.selection.contains(top));
|
||||
assert!(widget(&h, &format!("stack:{}", top.0)).is_some(), "badge drawn");
|
||||
let r = h.request("ui.clickWidget", json!({"id": format!("stack:{}", top.0)}), T);
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
h.settle(SETTLE);
|
||||
assert!(h.app.session.catalog.stack_of(top).unwrap().collapsed, "a click on an unselected stack's badge toggles it");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user