openpencil/crates/openpencil-shell-native
Kayshen-X 8457f4d629 feat(shell): LayerPanel drag-to-reorder — cross-parent reparenting
Drag a layer row in the LayerPanel; a 2 px primary-tint drop
indicator paints between rows; release commits the move. Supports
both same-parent reordering and cross-parent reparenting.

Core (`document/`):
- `Document::reorder_before(source, anchor)` / `reorder_after`
  with editability + cycle + existence pre-checks. The mutation
  phase calls `extract_node` + `insert_before/after_in_children`
  via shared walkers — guarded by the pre-check pass so the
  Err(node) arm of the insert helpers stays unreachable.
- New walkers: `extract_node`, `insert_before/after_in_children`
  (`Result<(), Node>` so the node bounces back on miss),
  `children_contain_descendant`. All `pub(in crate::document)`
  so sibling submodules (`mutators`) can call them via
  `super::walkers::*`.

Widget (`widgets/layer_panel.rs`):
- `DropPosition::{Before, After}` + `DropTarget { anchor,
  position, indicator_y }` re-exported from `widgets::`.
- `LayerPanel::drop_target_at(rect, point) -> Option<DropTarget>`
  walks the layer rows; upper half → Before, lower half → After.
- `LayerPanel.drop_target` field + `from_document_with_drop`
  ctor so the host can thread the active drop target through
  to paint without touching the existing API. Drop-indicator
  paints last so it sits above row chrome.
- Layout constants promoted to `pub(crate)` and tests moved to
  a sibling `widgets/layer_panel_tests.rs` to honor the 800-line
  cap (mirrors the `document/` split pattern).

Host wiring (native + web parity):
- `LayerDragState { source, start_y, current_x/y, active }` on
  both `WidgetHostNative` and web `WidgetHost`.
- `apply_press` peeks for a Layer row hit and seeds the drag
  candidate (selection still happens immediately on press).
- `apply_cursor_move` promotes candidate → active once the
  cursor moves past a 4 px screen-space threshold (mirrors the
  marquee activation heuristic).
- `apply_release_with_viewport` calls `commit_layer_drag`,
  which runs `drop_target_at` against the live cursor pos and
  dispatches to `reorder_before` / `reorder_after`. Viewport-
  less `apply_release` drops the candidate silently (no rect
  to compute against).
- Paint paths build the panel via `from_document_with_drop`
  when `layer_drag.active` is true.

Tests (8 new, 137 total in shell-core):
- 6 mutator tests in `document/tests_mutators.rs`:
  same-parent before/after move, cross-parent reparenting,
  cycle rejection, locked/hidden source rejection,
  same-id/missing-id rejection.
- 1 drop-target hit-test in `widgets/layer_panel_tests.rs`
  covering both halves of a row (Before / After) and the
  `indicator_y` contract.

All checks clean: cargo fmt + cargo test + cargo check
--workspace + tools/check-widget-boundary.sh. Every file
under the 800-line ceiling.
2026-05-11 22:06:02 +08:00
..
examples refactor(shell): promote inspector_window to openpencil-desktop binary crate 2026-05-10 19:50:10 +08:00
notes feat(shell-native): Step 1a Task 4 — basic_window demo + acceptance + Phase C Gate 2026-05-05 22:39:00 +08:00
src feat(shell): LayerPanel drag-to-reorder — cross-parent reparenting 2026-05-11 22:06:02 +08:00
tests refactor(shell-native): finish v19.4 cleanup — drop orphan event/ + stale test 2026-05-08 22:03:34 +08:00
Cargo.toml docs(shell-native): correct stale Cargo.toml comment about Invariant 2 2026-05-10 10:17:10 +08:00