fix(canvas): align rust z-order with renderer
Some checks failed
Rust check (native) / macos-latest / 1.94 (push) Waiting to run
Rust check (native) / windows-latest / 1.94 (push) Waiting to run
Rust multi-platform build / linux-aarch64 (push) Waiting to run
Rust multi-platform build / macos-aarch64 (push) Waiting to run
Rust multi-platform build / windows-x86_64 (push) Waiting to run
Rust multi-platform build / macos-x86_64 (push) Waiting to run
Rust multi-platform build / windows-aarch64 (push) Waiting to run
Rust multi-platform build / ios-aarch64 (cargo check only) (push) Waiting to run
Rust multi-platform build / ios-aarch64-sim (cargo check only) (push) Waiting to run
Rust check (native) / ubuntu-latest / 1.94 (push) Failing after 2s
Rust check (native) / cargo-deny (native) (push) Failing after 2s
Rust check (native) / diagnostics golden drift (push) Failing after 2s
Rust multi-platform build / linux-x86_64 (push) Failing after 1s
Rust multi-platform build / wasm32-unknown-unknown / op-host-web (compile guard) (push) Failing after 2s
Rust multi-platform build / android-aarch64 (cargo check only) (push) Failing after 1s
Rust multi-platform build / android-x86_64 (cargo check only) (push) Failing after 1s
WASM bundle check (kickoff §1.2) / cargo check --target wasm32-unknown-unknown (push) Failing after 1s
WASM bundle check (kickoff §1.2) / cargo-deny --target wasm32-unknown-unknown check bans (push) Failing after 2s

This commit is contained in:
Fini 2026-05-26 07:02:32 +08:00
parent b0b52a7842
commit e1ba222e87
5 changed files with 27 additions and 27 deletions

View file

@ -238,28 +238,28 @@ fn root_ids(s: &crate::state::EditorState) -> Vec<String> {
} }
#[test] #[test]
fn reorder_selected_up_moves_to_higher_index() { fn reorder_selected_up_moves_toward_front_index() {
let mut s = three_rects(); let mut s = three_rects();
s.set_single_selection(NodeId::new("n2")); s.set_single_selection(NodeId::new("n2"));
assert!(s.reorder_selected(ReorderDirection::Up)); assert!(s.reorder_selected(ReorderDirection::Up));
assert_eq!(root_ids(&s), vec!["n1", "n3", "n2"]); assert_eq!(root_ids(&s), vec!["n2", "n1", "n3"]);
} }
#[test] #[test]
fn reorder_selected_down_moves_to_lower_index() { fn reorder_selected_down_moves_toward_back_index() {
let mut s = three_rects(); let mut s = three_rects();
s.set_single_selection(NodeId::new("n2")); s.set_single_selection(NodeId::new("n2"));
assert!(s.reorder_selected(ReorderDirection::Down)); assert!(s.reorder_selected(ReorderDirection::Down));
assert_eq!(root_ids(&s), vec!["n2", "n1", "n3"]); assert_eq!(root_ids(&s), vec!["n1", "n3", "n2"]);
} }
#[test] #[test]
fn reorder_selected_at_edges_is_noop() { fn reorder_selected_at_edges_is_noop() {
let mut s = three_rects(); let mut s = three_rects();
s.set_single_selection(NodeId::new("n1")); s.set_single_selection(NodeId::new("n1"));
assert!(!s.reorder_selected(ReorderDirection::Down));
s.set_single_selection(NodeId::new("n3"));
assert!(!s.reorder_selected(ReorderDirection::Up)); assert!(!s.reorder_selected(ReorderDirection::Up));
s.set_single_selection(NodeId::new("n3"));
assert!(!s.reorder_selected(ReorderDirection::Down));
} }
#[test] #[test]

View file

@ -15,10 +15,10 @@ use std::collections::HashSet;
/// selected node swaps with in its parent's children vec. /// selected node swaps with in its parent's children vec.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReorderDirection { pub enum ReorderDirection {
/// Towards the front of the paint order (higher index, drawn on /// Towards the front of the paint order (lower index,
/// top). Bound to `]`. /// `children[0]` is top). Bound to `]`.
Up, Up,
/// Towards the back of the paint order (lower index, drawn /// Towards the back of the paint order (higher index, drawn
/// underneath). Bound to `[`. /// underneath). Bound to `[`.
Down, Down,
} }
@ -193,12 +193,12 @@ pub fn reorder_in_children(
) -> bool { ) -> bool {
if let Some(idx) = children.iter().position(|n| n.id_str() == target.as_str()) { if let Some(idx) = children.iter().position(|n| n.id_str() == target.as_str()) {
match direction { match direction {
ReorderDirection::Up if idx + 1 < children.len() => { ReorderDirection::Up if idx > 0 => {
children.swap(idx, idx + 1); children.swap(idx, idx - 1);
return true; return true;
} }
ReorderDirection::Down if idx > 0 => { ReorderDirection::Down if idx + 1 < children.len() => {
children.swap(idx, idx - 1); children.swap(idx, idx + 1);
return true; return true;
} }
_ => return false, _ => return false,

View file

@ -8,7 +8,7 @@
//! reads the same resolved geometry the painter walks. //! reads the same resolved geometry the painter walks.
//! //!
//! Hit semantics are preserved bit-for-bit from `document/walkers.rs`: //! Hit semantics are preserved bit-for-bit from `document/walkers.rs`:
//! top-most-first z-order, per-node rotation inverse-transform, the //! top-most-first z-order (`children[0]` is frontmost), per-node rotation inverse-transform, the
//! tighter Ellipse / Polygon / Line geometry, the locked-node //! tighter Ellipse / Polygon / Line geometry, the locked-node
//! body-opts-out-children-stay rule, and the hidden-subtree skip. //! body-opts-out-children-stay rule, and the hidden-subtree skip.
@ -18,15 +18,15 @@ use crate::{Point2D, Rect};
impl LayoutScene { impl LayoutScene {
/// Topmost node id whose geometry contains `point` (doc space) /// Topmost node id whose geometry contains `point` (doc space)
/// on the active page. Walks children in reverse z-order so the /// on the active page. Walks children in front-to-back z-order
/// most-recently-painted node wins. `None` on dead space. /// (`children[0]` is frontmost). `None` on dead space.
/// ///
/// `zoom` is the live viewport zoom — it scales the Line stroke /// `zoom` is the live viewport zoom — it scales the Line stroke
/// hit slack so a thin line stays clickable at any zoom. /// hit slack so a thin line stays clickable at any zoom.
pub fn node_at_doc_point(&self, point: Point2D, zoom: f32) -> Option<String> { pub fn node_at_doc_point(&self, point: Point2D, zoom: f32) -> Option<String> {
let zoom = zoom.max(0.0001); let zoom = zoom.max(0.0001);
let page = self.active_page()?; let page = self.active_page()?;
for child in page.children.iter().rev() { for child in &page.children {
if let Some(hit) = hit_test_walk(child, point, zoom) { if let Some(hit) = hit_test_walk(child, point, zoom) {
return Some(hit); return Some(hit);
} }
@ -92,7 +92,7 @@ fn hit_test_walk(node: &SceneNode, point: Point2D, zoom: f32) -> Option<String>
} else { } else {
point point
}; };
for child in node.children.iter().rev() { for child in &node.children {
if let Some(hit) = hit_test_walk(child, local, zoom) { if let Some(hit) = hit_test_walk(child, local, zoom) {
return Some(hit); return Some(hit);
} }
@ -292,22 +292,22 @@ mod tests {
} }
#[test] #[test]
fn node_at_doc_point_picks_topmost_overlapping_node() { fn node_at_doc_point_treats_first_child_as_frontmost() {
let scene = one_page(vec![ let scene = one_page(vec![
leaf("under", NodeKind::Rect, Rect::xywh(0.0, 0.0, 50.0, 50.0)), leaf("over", NodeKind::Rect, Rect::xywh(0.0, 0.0, 50.0, 50.0)),
leaf("over", NodeKind::Rect, Rect::xywh(10.0, 10.0, 50.0, 50.0)), leaf("under", NodeKind::Rect, Rect::xywh(10.0, 10.0, 50.0, 50.0)),
]); ]);
// Overlap region → the later-painted "over" wins. // Overlap region -> children[0] is the top layer.
assert_eq!( assert_eq!(
scene scene
.node_at_doc_point(Point2D::new(20.0, 20.0), 1.0) .node_at_doc_point(Point2D::new(20.0, 20.0), 1.0)
.as_deref(), .as_deref(),
Some("over") Some("over")
); );
// Only "under" covers (5, 5). // Only "under" covers (55, 55).
assert_eq!( assert_eq!(
scene scene
.node_at_doc_point(Point2D::new(5.0, 5.0), 1.0) .node_at_doc_point(Point2D::new(55.0, 55.0), 1.0)
.as_deref(), .as_deref(),
Some("under") Some("under")
); );

View file

@ -415,7 +415,7 @@ impl<'a> Widget for CanvasViewport<'a> {
rect.size.y + CULL_MARGIN * 2.0, rect.size.y + CULL_MARGIN * 2.0,
), ),
}; };
for child in &page.children { for child in page.children.iter().rev() {
super::canvas_viewport_paint::paint_node( super::canvas_viewport_paint::paint_node(
cx, cx,
child, child,

View file

@ -263,7 +263,7 @@ pub fn paint_node(
} else { } else {
paint_fill_then_stroke(cx, node, world_rect, zoom, node.fill); paint_fill_then_stroke(cx, node, world_rect, zoom, node.fill);
} }
for child in &node.children { for child in node.children.iter().rev() {
paint_node(cx, child, viewport_origin, zoom, edit_caret.clone(), cull); paint_node(cx, child, viewport_origin, zoom, edit_caret.clone(), cull);
} }
} }
@ -275,7 +275,7 @@ pub fn paint_node(
node.fill, node.fill,
), ),
NodeKind::Group | NodeKind::Other(_) => { NodeKind::Group | NodeKind::Other(_) => {
for child in &node.children { for child in node.children.iter().rev() {
paint_node(cx, child, viewport_origin, zoom, edit_caret.clone(), cull); paint_node(cx, child, viewport_origin, zoom, edit_caret.clone(), cull);
} }
} }