feat(iced): add Apply/Cancel overlay and shape deletion
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user