feat(iced): add Apply/Cancel overlay and shape deletion

This commit is contained in:
2026-07-16 05:17:52 +03:00
parent 76da953a17
commit 4f05745255
2 changed files with 153 additions and 13 deletions
+85 -13
View File
@@ -628,6 +628,10 @@ pub enum Message {
VectorSelectDragEnd,
VectorShapeDelete,
VectorShapeSelect(Option<usize>),
VectorShapeApply,
VectorShapeCancel,
VectorShapeFlipH,
VectorShapeFlipV,
// ── Lasso / polygon selection ───────────────────────
LassoDragMove(f32, f32),
@@ -3520,17 +3524,14 @@ impl HcieIcedApp {
Message::VectorShapeDelete => {
if let Some(idx) = self.documents[self.active_doc].selected_vector_shape {
let layer_id = self.documents[self.active_doc].engine.active_layer_id();
// Remove the shape from the layer's shape list.
if let Some(shapes) = self.documents[self.active_doc].engine.get_layer_shapes_direct(layer_id) {
if idx < shapes.len() {
shapes.remove(idx);
}
}
self.documents[self.active_doc].engine.delete_vector_shape(layer_id, idx);
self.documents[self.active_doc].selected_vector_shape = None;
self.vector_shapes_snapshot = None;
self.documents[self.active_doc].engine.mark_composite_dirty();
self.refresh_composite_if_needed();
return Task::perform(async {}, |_| Message::CompositeRefresh);
} else {
return self.update(Message::Deselect);
}
}
Message::VectorShapeSelect(idx) => {
@@ -3542,6 +3543,78 @@ impl HcieIcedApp {
self.vector_shapes_snapshot = None;
}
}
// ── Vector shape apply / cancel / flip ──────
Message::VectorShapeApply => {
// Commit edits: clear snapshot and deselect.
let doc = &mut self.documents[self.active_doc];
doc.selected_vector_shape = None;
doc.vector_edit_handle = hcie_engine_api::VectorEditHandle::None;
self.vector_shapes_snapshot = None;
self.vector_drag_last = None;
}
Message::VectorShapeCancel => {
// Restore snapshot: put original shapes back, then deselect.
if let Some(snapshot) = self.vector_shapes_snapshot.take() {
let doc = &mut self.documents[self.active_doc];
let layer_id = doc.engine.active_layer_id();
if let Some(shapes) = doc.engine.get_layer_shapes_direct(layer_id) {
*shapes = snapshot;
}
doc.selected_vector_shape = None;
doc.vector_edit_handle = hcie_engine_api::VectorEditHandle::None;
self.vector_drag_last = None;
doc.engine.mark_composite_dirty();
self.refresh_composite_if_needed();
}
}
Message::VectorShapeFlipH => {
if let Some(idx) = self.documents[self.active_doc].selected_vector_shape {
let layer_id = self.documents[self.active_doc].engine.active_layer_id();
if let Some(shapes) = self.documents[self.active_doc].engine.get_layer_shapes_direct(layer_id) {
if let Some(shape) = shapes.get_mut(idx) {
match shape {
hcie_engine_api::VectorShape::FreePath { pts, .. } => {
let cx = pts.iter().map(|p| p[0]).sum::<f32>() / pts.len() as f32;
for p in pts.iter_mut() {
p[0] = 2.0 * cx - p[0];
}
}
_ => {
let (x1, y1, x2, y2) = shape.bounds();
// Swap x1/x2 to flip horizontally.
shape.set_bounds(x2, y1, x1, y2);
}
}
self.documents[self.active_doc].engine.mark_composite_dirty();
self.refresh_composite_if_needed();
}
}
}
}
Message::VectorShapeFlipV => {
if let Some(idx) = self.documents[self.active_doc].selected_vector_shape {
let layer_id = self.documents[self.active_doc].engine.active_layer_id();
if let Some(shapes) = self.documents[self.active_doc].engine.get_layer_shapes_direct(layer_id) {
if let Some(shape) = shapes.get_mut(idx) {
match shape {
hcie_engine_api::VectorShape::FreePath { pts, .. } => {
let cy = pts.iter().map(|p| p[1]).sum::<f32>() / pts.len() as f32;
for p in pts.iter_mut() {
p[1] = 2.0 * cy - p[1];
}
}
_ => {
let (x1, y1, x2, y2) = shape.bounds();
// Swap y1/y2 to flip vertically.
shape.set_bounds(x1, y2, x2, y1);
}
}
self.documents[self.active_doc].engine.mark_composite_dirty();
self.refresh_composite_if_needed();
}
}
}
}
// ── Lasso / polygon selection ─────────────────
Message::LassoDragMove(x, y) => {
@@ -4877,10 +4950,7 @@ impl HcieIcedApp {
} else if self.documents[self.active_doc].selection_transform.is_some() {
return self.update(Message::TransformCancel);
} else if self.documents[self.active_doc].selected_vector_shape.is_some() {
self.documents[self.active_doc].selected_vector_shape = None;
self.vector_shapes_snapshot = None;
self.documents[self.active_doc].vector_edit_handle = hcie_engine_api::VectorEditHandle::None;
self.vector_drag_last = None;
return self.update(Message::VectorShapeCancel);
} else if self.documents[self.active_doc].selection_bounds.is_some()
|| self.documents[self.active_doc].selection_rect.is_some()
{
@@ -4892,13 +4962,15 @@ impl HcieIcedApp {
}
}
Message::EnterPressed => {
// Mirrors egui's Enter: crop > transform > text-commit > viewer.
// Mirrors egui's Enter: crop > transform > text-commit > vector-apply > viewer.
if self.documents[self.active_doc].crop_state.active {
return self.update(Message::CropConfirm);
} else if self.documents[self.active_doc].selection_transform.is_some() {
return self.update(Message::TransformApply);
} else if self.active_document_mut().text_draft.is_some() {
return self.update(Message::TextCommit);
} else if self.documents[self.active_doc].selected_vector_shape.is_some() {
return self.update(Message::VectorShapeApply);
} else {
return self.update(Message::ViewerEnter);
}
@@ -5290,9 +5362,9 @@ impl HcieIcedApp {
// reserved — egui maps Enter to crop confirm).
// ── Named keys ───────────────────────────────
// Delete = Clear selection / deselect
// Delete = Delete selected vector shape, or clear selection
iced::keyboard::Key::Named(iced::keyboard::key::Named::Delete) => {
Some(Message::Deselect)
Some(Message::VectorShapeDelete)
}
// F11 = Fullscreen toggle
iced::keyboard::Key::Named(iced::keyboard::key::Named::F11) => {
@@ -1038,6 +1038,74 @@ pub fn view<'a>(
stacked = stacked.push(positioned_editor);
}
// ── Vector shape Apply / Cancel / Flip overlay ──────────────────────
// Floating buttons shown below the selected vector shape for quick
// commit, cancel, or flip operations.
if let Some((bx1, by1, bx2, by2)) = sel_vec_bounds {
let (pane_w, pane_h) = doc.pane_size;
let display_w = engine_w as f32 * zoom;
let display_h = engine_h as f32 * zoom;
let origin_x = (pane_w - display_w) / 2.0 + pan_offset.x;
let origin_y = (pane_h - display_h) / 2.0 + pan_offset.y;
let cx = (bx1 + bx2) / 2.0;
let cy = (by2).max(by1) + 10.0; // below shape
let screen_x = (origin_x + cx * zoom).max(4.0);
let screen_y = (origin_y + cy * zoom).max(4.0);
let btn_style = |_theme: &iced::Theme, _status: iced::widget::button::Status| iced::widget::button::Style {
background: Some(iced::Background::Color(iced::Color::from_rgba(0.15, 0.15, 0.18, 0.9))),
border: iced::Border::default()
.color(iced::Color::from_rgba(0.35, 0.35, 0.4, 1.0))
.width(1)
.rounded(4),
text_color: iced::Color::from_rgb(0.9, 0.9, 0.9),
..Default::default()
};
let apply_btn = iced::widget::button(
iced::widget::text("Apply").size(10),
)
.on_press(Message::VectorShapeApply)
.padding([3, 8])
.style(btn_style);
let cancel_btn = iced::widget::button(
iced::widget::text("Cancel").size(10),
)
.on_press(Message::VectorShapeCancel)
.padding([3, 8])
.style(btn_style);
let flip_h_btn = iced::widget::button(
iced::widget::text("Flip H").size(10),
)
.on_press(Message::VectorShapeFlipH)
.padding([3, 8])
.style(btn_style);
let flip_v_btn = iced::widget::button(
iced::widget::text("Flip V").size(10),
)
.on_press(Message::VectorShapeFlipV)
.padding([3, 8])
.style(btn_style);
let overlay_row = row![apply_btn, cancel_btn, flip_h_btn, flip_v_btn]
.spacing(4);
let overlay_container = container(overlay_row)
.width(Length::Shrink)
.padding(iced::Padding {
top: screen_y,
bottom: 0.0,
left: screen_x,
right: 0.0,
});
stacked = stacked.push(overlay_container);
}
// ── Tool info strip (bottom of canvas area) ─────────────────────────
let overlay_info = if let Some((x0, y0, x1, y1)) = doc.selection_rect {
let w = (x1 - x0).abs() as u32;