mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-28 20:17:18 -06:00
Albums: the Albums header is the way back to the top level
It replaces the strip under the tree, which could be scrolled out of view and shifted the rows when it appeared; the header is always visible and nothing moves. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
922ea25d88
commit
24b80b4ee3
@@ -208,6 +208,7 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) {
|
||||
ui.add_space(10.0);
|
||||
// Albums header
|
||||
let (ar, albums_open) = sidebar_section_header(app, ui, "albums", "Albums");
|
||||
top_level_drop_target(app, ui, ar);
|
||||
// the + stays at the visible edge when the sidebar is scrolled sideways
|
||||
let plus_right = (ar.left() + viewport.max.x).min(ar.right());
|
||||
let mut hdr = ui.new_child(
|
||||
@@ -255,7 +256,6 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) {
|
||||
cur = cat.album(p).and_then(|a| a.parent);
|
||||
}
|
||||
albums_tree(app, ui, &kids, None, 0.0, &open_to);
|
||||
top_level_drop_zone(app, ui);
|
||||
}
|
||||
ui.add_space(10.0);
|
||||
local_section(app, ui);
|
||||
@@ -904,20 +904,22 @@ fn album_drag_over(app: &mut LightcraftApp, ui: &mut egui::Ui, resp: &egui::Resp
|
||||
opened
|
||||
}
|
||||
|
||||
/// While an album that sits in a folder is dragged: a row under the tree that takes it back to the
|
||||
/// top level.
|
||||
fn top_level_drop_zone(app: &mut LightcraftApp, ui: &mut egui::Ui) {
|
||||
/// While an album that sits in a folder is dragged: the "Albums" header takes it back to the top
|
||||
/// level (outlined, with a hint). The header stays in view however long the tree is, and nothing
|
||||
/// moves when it appears.
|
||||
fn top_level_drop_target(app: &mut LightcraftApp, ui: &mut egui::Ui, header: Rect) {
|
||||
let Some(dragged) = app.ui.dragging_album.map(AlbumId) else { return };
|
||||
if !can_drop_album(app, dragged, None) {
|
||||
return;
|
||||
}
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let (r, _) = ui.allocate_exact_size(vec2(ui.available_width(), 29.0), Sense::hover());
|
||||
register(ui.ctx(), "albumDrop:top", r);
|
||||
let over = pointer_over(ui, r);
|
||||
register(ui.ctx(), "albumDrop:top", header);
|
||||
let over = pointer_over(ui, header);
|
||||
let stroke = if over { egui::Stroke::new(1.5, t.accent) } else { egui::Stroke::new(1.0, t.text_dim.gamma_multiply(0.5)) };
|
||||
ui.painter().rect_stroke(r.shrink2(vec2(8.0, 1.0)), 4.0, stroke, egui::StrokeKind::Inside);
|
||||
ui.painter().text(pos2(r.left() + 18.0, r.center().y), Align2::LEFT_CENTER, crate::i18n::tr("Top Level"), t.font(13.5), t.text_dim);
|
||||
ui.painter().rect_stroke(header.shrink2(vec2(8.0, 3.0)), 4.0, stroke, egui::StrokeKind::Inside);
|
||||
// right-aligned, left of the + button
|
||||
let edge = header.right().min(ui.clip_rect().right());
|
||||
ui.painter().text(pos2(edge - 56.0, header.center().y), Align2::RIGHT_CENTER, crate::i18n::tr("Top Level"), t.font(12.5), t.text_dim);
|
||||
if over && ui.input(|i| i.pointer.primary_released()) {
|
||||
drop_album(app, ui, dragged, None);
|
||||
}
|
||||
|
||||
@@ -383,13 +383,9 @@ impl AlbumTree {
|
||||
let name = ["source:folder:", "source:album:"].iter().map(|p| format!("{p}{id}")).find(|n| h.app.widgets.iter().any(|(w, _)| w == n));
|
||||
widget(h, &name.unwrap_or_else(|| panic!("no row for album {id}")))
|
||||
}
|
||||
/// The middle of the row just under the last row of the Albums tree (where the top-level drop zone is).
|
||||
fn below_tree(&self) -> egui::Pos2 {
|
||||
let bottom = (self.h.app.widgets.iter())
|
||||
.filter(|(w, r)| (w.starts_with("source:album:") || w.starts_with("source:folder:")) && r.left() < 300.0)
|
||||
.map(|(_, r)| r.bottom())
|
||||
.fold(0.0, f32::max);
|
||||
egui::pos2(100.0, bottom + 14.5)
|
||||
/// The middle of the "Albums" header, where an album is dropped to take it to the top level.
|
||||
fn albums_header(&self) -> egui::Pos2 {
|
||||
widget(&self.h, "sidebarSection:albums").center()
|
||||
}
|
||||
fn parent(&self, id: u64) -> Option<u64> {
|
||||
self.h.app.session.catalog.album(lightcraft_catalog::AlbumId(id)).unwrap().parent.map(|p| p.0)
|
||||
@@ -600,22 +596,23 @@ fn folders_are_placed_by_their_edges_and_entered_by_their_middle() {
|
||||
assert!(!t.h.app.session.catalog.album_children_are_ordered(None), "Travel 2026 already sits right before Trips");
|
||||
}
|
||||
|
||||
/// Dragging out of a folder: the drop zone under the tree puts the album back at the top level;
|
||||
/// it only shows when there is somewhere to go, and the move is one undo step.
|
||||
/// Dragging out of a folder: the "Albums" header is the way back to the top level (it stays in
|
||||
/// view however long the tree is), only offered for an album that sits in a folder, and the move
|
||||
/// is one undo step. The drop does not fold the section.
|
||||
#[test]
|
||||
fn dragging_an_album_to_the_top_level() {
|
||||
fn dragging_an_album_to_the_albums_header_takes_it_to_the_top_level() {
|
||||
let mut t = album_tree();
|
||||
let has = |t: &AlbumTree, id: &str| t.h.app.widgets.iter().any(|(w, _)| w == id);
|
||||
assert!(!has(&t, "albumDrop:top"), "no zone unless an album is being dragged");
|
||||
// below the last row of the tree, where the zone appears once an album with a folder is dragged
|
||||
let below = t.below_tree();
|
||||
let from = t.row(t.best).center();
|
||||
t.drag(from, &[(below, 3)]);
|
||||
assert!(!has(&t, "albumDrop:top"), "no target unless an album is being dragged");
|
||||
let (from, header) = (t.row(t.best).center(), t.albums_header());
|
||||
t.drag(from, &[(header, 3)]);
|
||||
assert_eq!(t.parent(t.best), None, "moved out of Trips");
|
||||
// a top-level album has nowhere to go: dropping it there does nothing
|
||||
let (from, below) = (t.row(t.best).center(), t.below_tree());
|
||||
t.drag(from, &[(below, 3)]);
|
||||
assert!(!t.h.app.ui.sidebar_section_collapsed("albums"), "dropping on the header does not fold it");
|
||||
// a top-level album has nowhere to go: dropping it on the header does nothing
|
||||
let (from, header) = (t.row(t.best).center(), t.albums_header());
|
||||
t.drag(from, &[(header, 3)]);
|
||||
assert_eq!(t.parent(t.best), None);
|
||||
assert!(!t.h.app.ui.sidebar_section_collapsed("albums"));
|
||||
t.h.app.session.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(t.parent(t.best), Some(t.trips), "one undo puts it back");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user