feat(gui): implement selection mask state and vector editing functionality

- Add `selection/state.rs` to manage selection masks, including combining masks and calculating bounds.
- Introduce `vector_edit.rs` for vector shape manipulation, handling drag sessions, and hit testing for handles.
- Create `feature_scorecard.rs` to ensure stable feature identifiers and regression gates for GUI components.
- Implement raster behavior tests in `raster_test.rs` to validate gradient application against masks.
This commit is contained in:
2026-07-16 18:41:05 +03:00
parent 026e268a10
commit 1240d25011
70 changed files with 11547 additions and 4357 deletions
@@ -1,6 +1,8 @@
//! Tests for selection types: SelectionTransform, TransformHandle, CropState.
use hcie_iced_gui::selection::{CropState, SelectionTransform, TransformHandle};
use hcie_iced_gui::selection::clipboard::centered_transform;
use hcie_iced_gui::selection::state::{combine_masks, mask_bounds, selected_spans};
use hcie_iced_gui::selection::{CropState, SelectionMode, SelectionTransform, TransformHandle};
#[test]
fn test_selection_transform_is_empty() {
@@ -70,3 +72,53 @@ fn test_transform_handle_cursor_hints() {
assert_eq!(TransformHandle::TopLeft.cursor_hint(), "nwse-resize");
assert_eq!(TransformHandle::Rotate.cursor_hint(), "crosshair");
}
#[test]
fn selection_modes_combine_alpha_without_losing_soft_edges() {
let old = [0, 64, 255, 255];
let new = [255, 128, 64, 0];
assert_eq!(combine_masks(Some(&old), &new, SelectionMode::Replace), new);
assert_eq!(
combine_masks(Some(&old), &new, SelectionMode::Add),
[255, 128, 255, 255]
);
assert_eq!(
combine_masks(Some(&old), &new, SelectionMode::Subtract),
[0, 0, 191, 255]
);
assert_eq!(
combine_masks(Some(&old), &new, SelectionMode::Intersect),
[0, 64, 64, 0]
);
}
#[test]
fn irregular_mask_bounds_and_fill_spans_are_exact() {
let mask = [0, 255, 0, 0, 255, 255, 0, 0, 0, 255, 0, 0];
assert_eq!(mask_bounds(&mask, 4, 3), Some((0, 0, 2, 3)));
assert_eq!(
selected_spans(&mask, 4, 3),
vec![(1, 0, 2), (0, 1, 2), (1, 2, 2)]
);
}
#[test]
fn crop_clamps_drag_and_cancel_restores_idle_state() {
let mut crop = CropState::default();
crop.start_drag_clamped(-20.0, 5.0, 100, 80);
crop.update_drag(140.0, 120.0);
assert_eq!(crop.rect, Some((0.0, 5.0, 100.0, 75.0)));
crop.cancel();
assert_eq!(crop.canvas_size, None);
assert!(!crop.active);
}
#[test]
fn clipboard_transform_is_centered_and_transform_geometry_is_safe() {
let mut transform = centered_transform(vec![255; 8 * 4 * 4], 8, 4, 100, 50).unwrap();
assert_eq!(transform.pos, iced::Vector::new(46.0, 23.0));
transform.size = iced::Vector::new(-20.0, f32::NAN);
transform.sanitize_geometry(true);
assert!(transform.size.x > 0.0 && transform.size.y > 0.0);
assert!((transform.size.x / transform.size.y - 2.0).abs() < 0.001);
}