fix(editor): align starter frame and layer hover

This commit is contained in:
Fini 2026-05-30 00:49:10 +08:00
parent 8d0e3b04ba
commit c0dada1a59
3 changed files with 92 additions and 9 deletions

View file

@ -56,17 +56,16 @@ impl EditorState {
}
/// Build the document a fresh launch opens with — a single empty
/// starter Frame `n10` (white fill, 1-px black stroke), selected
/// so the user can immediately resize / move it or drop nodes
/// inside. No demo decoration.
/// starter Frame `n10` matching the TypeScript app's blank
/// document geometry, selected so the user can immediately resize
/// / move it or drop nodes inside. No demo decoration.
pub fn starter() -> Self {
let src = r##"{
"version": "0.8.0",
"children": [
{"type":"frame","id":"n10","name":"Frame",
"x":40,"y":40,"width":360,"height":240,
"x":0,"y":0,"width":1200,"height":800,
"fill":[{"type":"solid","color":"#FFFFFF"}],
"stroke":{"thickness":1,"fill":[{"type":"solid","color":"#000000"}]},
"children":[]}
]
}"##;
@ -432,6 +431,21 @@ mod tests {
assert_eq!(s.doc.children.len(), 1);
assert_eq!(s.max_node_id(), 10);
assert_eq!(s.selection.anchor, NodeId::new("n10"));
let frame = match &s.doc.children[0] {
jian_ops_schema::node::PenNode::Frame(frame) => frame,
other => panic!("starter should be a frame, got {:?}", other),
};
assert_eq!(frame.base.x, Some(0.0));
assert_eq!(frame.base.y, Some(0.0));
assert!(matches!(
frame.container.width,
Some(jian_ops_schema::sizing::SizingBehavior::Number(1200.0))
));
assert!(matches!(
frame.container.height,
Some(jian_ops_schema::sizing::SizingBehavior::Number(800.0))
));
assert!(frame.container.stroke.is_none());
}
#[test]

View file

@ -364,14 +364,18 @@ impl LayerPanel {
let eye_x = lock_x - 22.0;
let icon_y = inner.origin.y + 6.0;
let slop = 4.0;
if point.x >= lock_x - slop
let show_eye = item.hovered || item.hidden;
let show_lock = item.hovered || item.locked;
if show_lock
&& point.x >= lock_x - slop
&& point.x <= lock_x + 14.0 + slop
&& point.y >= icon_y - slop
&& point.y <= icon_y + 14.0 + slop
{
return Some(LayerPanelHit::ToggleLocked(item.node_id.clone()));
}
if point.x >= eye_x - slop
if show_eye
&& point.x >= eye_x - slop
&& point.x <= eye_x + 14.0 + slop
&& point.y >= icon_y - slop
&& point.y <= icon_y + 14.0 + slop
@ -733,8 +737,8 @@ impl Widget for LayerPanel {
let trailing_size = 12.0;
let trailing_stroke = 1.2;
let trailing_y = row.origin.y + 7.0;
let show_eye = item.hovered || item.selected || item.hidden;
let show_lock = item.hovered || item.selected || item.locked;
let show_eye = item.hovered || item.hidden;
let show_lock = item.hovered || item.locked;
if show_eye {
draw_icon(
cx.backend,

View file

@ -108,6 +108,71 @@ fn hit_test_resolves_first_layer_row() {
}
}
fn first_layer_trailing_points(panel: &LayerPanel, rect: Rect) -> (Point2D, Point2D) {
let y = 8.0
+ SECTION_HEADER_HEIGHT
+ panel.pages.len() as f32 * PAGE_ROW_HEIGHT
+ SECTION_GAP
+ SECTION_HEADER_HEIGHT;
let row = Rect {
origin: Point2D::new(rect.origin.x + 6.0, y + 2.0),
size: Point2D::new(rect.size.x - 12.0, LAYER_ROW_HEIGHT - 4.0),
};
let trailing_right = row.origin.x + row.size.x - 8.0;
let lock_x = trailing_right - 14.0;
let eye_x = lock_x - 22.0;
let icon_y = row.origin.y + 6.0;
(
Point2D::new(eye_x + 6.0, icon_y + 6.0),
Point2D::new(lock_x + 6.0, icon_y + 6.0),
)
}
#[test]
fn selected_visible_unlocked_layer_does_not_expose_trailing_actions_without_hover() {
let state = EditorState::starter();
let panel = LayerPanel::from_editor(&state);
assert!(panel.items[0].selected);
assert!(!panel.items[0].hovered);
assert!(!panel.items[0].hidden);
assert!(!panel.items[0].locked);
let rect = Rect {
origin: Point2D::new(0.0, 0.0),
size: Point2D::new(LAYER_PANEL_WIDTH, panel.intrinsic_height()),
};
let (eye, lock) = first_layer_trailing_points(&panel, rect);
assert!(matches!(
panel.hit_test(rect, eye),
Some(LayerPanelHit::Layer(_))
));
assert!(matches!(
panel.hit_test(rect, lock),
Some(LayerPanelHit::Layer(_))
));
}
#[test]
fn hovered_layer_exposes_trailing_actions() {
let mut state = EditorState::starter();
state.editor_ui.hovered_layer_id = Some(NodeId::new("n10"));
let panel = LayerPanel::from_editor(&state);
let rect = Rect {
origin: Point2D::new(0.0, 0.0),
size: Point2D::new(LAYER_PANEL_WIDTH, panel.intrinsic_height()),
};
let (eye, lock) = first_layer_trailing_points(&panel, rect);
assert_eq!(
panel.hit_test(rect, eye),
Some(LayerPanelHit::ToggleHidden(NodeId::new("n10")))
);
assert_eq!(
panel.hit_test(rect, lock),
Some(LayerPanelHit::ToggleLocked(NodeId::new("n10")))
);
}
#[test]
fn hit_test_resolves_add_page_plus_icon() {
let state = EditorState::sample();