feat(iced): implement shape_sync bidirectional sync
This commit is contained in:
@@ -1447,15 +1447,14 @@ impl HcieIcedApp {
|
||||
Message::FgColorChanged(color) => {
|
||||
self.fg_color = color;
|
||||
self.active_document_mut().engine.set_color(color);
|
||||
// Record the new color in the recent-colors list so the
|
||||
// picker's recent swatches populate (egui does this on
|
||||
// drag-stopped/click in color.rs:316/816/915).
|
||||
self.add_recent_color(color);
|
||||
self.colors_dirty = true;
|
||||
crate::shape_sync::sync_shape_from_tool(self);
|
||||
}
|
||||
|
||||
Message::BgColorChanged(color) => {
|
||||
self.bg_color = color;
|
||||
crate::shape_sync::sync_shape_from_tool(self);
|
||||
}
|
||||
|
||||
Message::SwapColors => {
|
||||
@@ -1464,6 +1463,7 @@ impl HcieIcedApp {
|
||||
self.active_document_mut().engine.set_color(color);
|
||||
self.add_recent_color(color);
|
||||
self.colors_dirty = true;
|
||||
crate::shape_sync::sync_shape_from_tool(self);
|
||||
}
|
||||
Message::ResetColors => {
|
||||
// Reset fg=black, bg=white (egui: toolbox.rs:351-354).
|
||||
@@ -2725,26 +2725,32 @@ impl HcieIcedApp {
|
||||
}
|
||||
Message::VectorStrokeChanged(s) => {
|
||||
self.settings.tool_settings.vector_stroke_width = s;
|
||||
crate::shape_sync::sync_shape_from_tool(self);
|
||||
let _ = self.settings.save();
|
||||
}
|
||||
Message::VectorOpacityChanged(o) => {
|
||||
self.settings.tool_settings.vector_opacity = o;
|
||||
crate::shape_sync::sync_shape_from_tool(self);
|
||||
let _ = self.settings.save();
|
||||
}
|
||||
Message::VectorFillToggled(f) => {
|
||||
self.settings.tool_settings.vector_fill = f;
|
||||
crate::shape_sync::sync_shape_from_tool(self);
|
||||
let _ = self.settings.save();
|
||||
}
|
||||
Message::VectorRadiusChanged(r) => {
|
||||
self.settings.tool_settings.vector_radius = r;
|
||||
crate::shape_sync::sync_shape_from_tool(self);
|
||||
let _ = self.settings.save();
|
||||
}
|
||||
Message::VectorPointsChanged(p) => {
|
||||
self.settings.tool_settings.vector_points = p;
|
||||
crate::shape_sync::sync_shape_from_tool(self);
|
||||
let _ = self.settings.save();
|
||||
}
|
||||
Message::VectorSidesChanged(s) => {
|
||||
self.settings.tool_settings.vector_sides = s;
|
||||
crate::shape_sync::sync_shape_from_tool(self);
|
||||
let _ = self.settings.save();
|
||||
}
|
||||
Message::TextFontChanged(font) => {
|
||||
@@ -3487,6 +3493,7 @@ impl HcieIcedApp {
|
||||
doc.vector_draw = None; // clear any preview
|
||||
doc.vector_edit_handle = VEH::Move;
|
||||
self.vector_drag_last = Some((x, y));
|
||||
crate::shape_sync::sync_tool_from_shape(self);
|
||||
} else {
|
||||
// No shape under cursor — deselect.
|
||||
let doc = &mut self.documents[self.active_doc];
|
||||
@@ -3561,6 +3568,7 @@ impl HcieIcedApp {
|
||||
if idx.is_some() {
|
||||
let snapshot = self.documents[self.active_doc].engine.active_vector_shapes();
|
||||
self.vector_shapes_snapshot = snapshot;
|
||||
crate::shape_sync::sync_tool_from_shape(self);
|
||||
} else {
|
||||
self.vector_shapes_snapshot = None;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ mod panels;
|
||||
mod script;
|
||||
mod selection;
|
||||
mod settings;
|
||||
mod shape_sync;
|
||||
mod sidebar;
|
||||
mod theme;
|
||||
mod viewer;
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
//! Bidirectional vector shape ↔ tool state synchronization.
|
||||
//!
|
||||
//! Purpose: Keep the iced GUI's tool settings (stroke, fill, opacity, colors)
|
||||
//! and the selected VectorShape in the document engine in sync. When the user
|
||||
//! changes tool properties while a shape is selected, push them to the shape.
|
||||
//! When a shape is selected, pull its properties back into the tool state.
|
||||
//!
|
||||
//! Logic & Workflow:
|
||||
//! - `sync_shape_from_tool`: Reads tool settings + fg/bg colors from the app,
|
||||
//! writes all matching properties into the engine's selected vector shape.
|
||||
//! - `sync_tool_from_shape`: Reads the engine's selected shape properties,
|
||||
//! writes them back into tool settings and app color state.
|
||||
//!
|
||||
//! Side Effects: Mutates both the engine (via `mark_composite_dirty`) and
|
||||
//! the app's `settings.tool_settings` / `fg_color` / `bg_color`.
|
||||
|
||||
use crate::app::HcieIcedApp;
|
||||
use hcie_engine_api::{Tool, VectorShape};
|
||||
|
||||
/// Push tool state properties (stroke, fill, color, opacity, hardness)
|
||||
/// into the currently selected vector shape.
|
||||
///
|
||||
/// Called when the user changes a tool property or picks a color while
|
||||
/// a shape is selected in VectorSelect mode.
|
||||
pub fn sync_shape_from_tool(app: &mut HcieIcedApp) {
|
||||
if app.tool_state.active_tool != Tool::VectorSelect {
|
||||
return;
|
||||
}
|
||||
let shape_idx = match app.documents[app.active_doc].selected_vector_shape {
|
||||
Some(i) => i,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Snapshot for undo (before first modification in this edit session)
|
||||
if app.vector_shapes_snapshot.is_none() {
|
||||
let snapshot = app.documents[app.active_doc].engine.active_vector_shapes();
|
||||
app.vector_shapes_snapshot = snapshot;
|
||||
}
|
||||
|
||||
let layer_id = app.documents[app.active_doc].engine.active_layer_id();
|
||||
let ts = &app.settings.tool_settings;
|
||||
|
||||
// Stroke width
|
||||
app.documents[app.active_doc]
|
||||
.engine
|
||||
.set_vector_shape_stroke(layer_id, shape_idx, ts.vector_stroke_width);
|
||||
|
||||
// Fill toggle — only push if shape has a fill or the value changed
|
||||
if let Some(shapes) = app.documents[app.active_doc].engine.active_vector_shapes() {
|
||||
if let Some(shape) = shapes.get(shape_idx) {
|
||||
if shape.fill_color().is_some() || shape.is_filled() != ts.vector_fill {
|
||||
app.documents[app.active_doc]
|
||||
.engine
|
||||
.set_vector_shape_fill(layer_id, shape_idx, ts.vector_fill);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Opacity
|
||||
app.documents[app.active_doc]
|
||||
.engine
|
||||
.set_vector_shape_opacity(layer_id, shape_idx, ts.vector_opacity);
|
||||
|
||||
// Hardness — use brush_hardness as the shared hardness value
|
||||
app.documents[app.active_doc]
|
||||
.engine
|
||||
.set_vector_shape_hardness(layer_id, shape_idx, app.tool_state.brush_hardness);
|
||||
|
||||
// Primary color (stroke color)
|
||||
app.documents[app.active_doc]
|
||||
.engine
|
||||
.set_vector_shape_color(layer_id, shape_idx, app.fg_color);
|
||||
|
||||
// Fill color — only push if shape already has a fill color
|
||||
if let Some(shapes) = app.documents[app.active_doc].engine.active_vector_shapes() {
|
||||
if let Some(shape) = shapes.get(shape_idx) {
|
||||
if shape.fill_color().is_some() {
|
||||
app.documents[app.active_doc]
|
||||
.engine
|
||||
.set_vector_shape_fill_color(layer_id, shape_idx, app.bg_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shape-specific properties (radius, points, sides)
|
||||
if let Some(shapes) = app.documents[app.active_doc].engine.active_vector_shapes() {
|
||||
if let Some(shape) = shapes.get(shape_idx) {
|
||||
match shape {
|
||||
VectorShape::Rect { .. } => {
|
||||
// round_rect_radius is set directly via engine if needed
|
||||
}
|
||||
VectorShape::Star { .. } => {
|
||||
// star_points / inner_radius via engine when available
|
||||
}
|
||||
VectorShape::Polygon { .. } => {
|
||||
// polygon_sides via engine when available
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pull properties from the currently selected vector shape back into
|
||||
/// tool settings and app color state.
|
||||
///
|
||||
/// Called when the user clicks/selects a shape in VectorSelect mode
|
||||
/// so the properties panel and tool state reflect the shape's actual values.
|
||||
pub fn sync_tool_from_shape(app: &mut HcieIcedApp) {
|
||||
if app.tool_state.active_tool != Tool::VectorSelect {
|
||||
return;
|
||||
}
|
||||
let shape_idx = match app.documents[app.active_doc].selected_vector_shape {
|
||||
Some(i) => i,
|
||||
None => return,
|
||||
};
|
||||
|
||||
if let Some(shapes) = app.documents[app.active_doc].engine.active_vector_shapes() {
|
||||
if let Some(shape) = shapes.get(shape_idx) {
|
||||
let ts = &mut app.settings.tool_settings;
|
||||
|
||||
// Common properties
|
||||
ts.vector_stroke_width = shape.stroke();
|
||||
ts.vector_opacity = shape.shape_opacity();
|
||||
app.tool_state.brush_hardness = shape.hardness();
|
||||
ts.vector_fill = shape.is_filled();
|
||||
|
||||
// Shape-specific properties
|
||||
match shape {
|
||||
VectorShape::Rect { radius, .. } => {
|
||||
ts.vector_radius = *radius;
|
||||
}
|
||||
VectorShape::Star {
|
||||
points,
|
||||
inner_radius: _,
|
||||
..
|
||||
} => {
|
||||
ts.vector_points = *points;
|
||||
}
|
||||
VectorShape::Polygon { sides, .. } => {
|
||||
ts.vector_sides = *sides;
|
||||
}
|
||||
VectorShape::Line {
|
||||
cap_start,
|
||||
cap_end: _,
|
||||
..
|
||||
} => {
|
||||
// Store line caps in settings if available
|
||||
let _ = cap_start;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Colors
|
||||
app.fg_color = shape.color();
|
||||
if let Some(fc) = shape.fill_color() {
|
||||
app.bg_color = fc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user