5632c916ee
- Added `SvgShape` variant to `VectorShape` in `hcie-protocol`, allowing for SVG rendering of complex shapes. - Updated serialization to skip old shape variants for backward compatibility. - Implemented SVG generation functions in `svg_templates.rs` for various shapes (arrow, star, rhombus, etc.). - Created `svg_render.rs` to parse SVG strings and tessellate paths using `usvg`. - Modified rendering logic in `hcie-vector` to handle `SvgShape` and integrate with existing shape rendering. - Updated shape creation API in `hcie-engine-api` to support SVG shapes. - Enhanced property editing and shape synchronization in both `hcie-egui-app` and `hcie-iced-app` to accommodate new SVG shapes. - Added comprehensive documentation for new features and changes.
155 lines
5.4 KiB
Rust
155 lines
5.4 KiB
Rust
#![allow(dead_code)]
|
|
//! Bidirectional vector shape ↔ tool state synchronization.
|
|
//!
|
|
//! Purpose: Keep `ToolState.tool_configs.vector` and the selected
|
|
//! VectorShape in the document engine in sync. When the user changes
|
|
//! tool properties (stroke, fill, color, opacity, etc.), push them
|
|
//! to the selected shape. When the user selects a different shape,
|
|
//! pull its properties back into the tool config.
|
|
//!
|
|
//! Logic & Workflow:
|
|
//! - `sync_shape_from_tool`: Reads ToolState vector config + colors,
|
|
//! writes all matching properties into the engine's selected shape.
|
|
//! - `sync_tool_from_shape`: Reads the engine's selected shape
|
|
//! properties, writes them back into ToolState vector config.
|
|
//!
|
|
//! Side Effects: Mutates both `AppDocument.engine` and `ToolState`.
|
|
|
|
use crate::app::{AppDocument, ToolState};
|
|
use hcie_engine_api::{Tool, VectorShape};
|
|
|
|
/// Push tool state properties (stroke, fill, color, opacity, hardness,
|
|
/// shape-specific fields) into the currently selected vector shape.
|
|
///
|
|
/// This is 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(doc: &mut AppDocument, state: &mut ToolState) {
|
|
if state.active_tool != Tool::VectorSelect {
|
|
return;
|
|
}
|
|
let shape_idx = match state.selected_vector_shape {
|
|
Some(i) => i,
|
|
None => return,
|
|
};
|
|
|
|
// Snapshot for undo (before first modification in this edit session)
|
|
if state.vector_shapes_snapshot.is_none() {
|
|
if let Some(shapes) = doc.engine.active_vector_shapes() {
|
|
state.vector_shapes_snapshot = Some(shapes);
|
|
}
|
|
}
|
|
|
|
let layer_id = doc.engine.active_layer_id();
|
|
let v = &state.tool_configs.vector;
|
|
|
|
// Stroke
|
|
doc.engine
|
|
.set_vector_shape_stroke(layer_id, shape_idx, v.stroke_size);
|
|
// Fill
|
|
if let Some(shapes) = doc.engine.active_vector_shapes() {
|
|
if let Some(shape) = shapes.get(shape_idx) {
|
|
if shape.fill_color().is_some() || shape.is_filled() != v.fill_shape {
|
|
doc.engine
|
|
.set_vector_shape_fill(layer_id, shape_idx, v.fill_shape);
|
|
}
|
|
}
|
|
}
|
|
// Opacity
|
|
doc.engine
|
|
.set_vector_shape_opacity(layer_id, shape_idx, v.opacity);
|
|
// Hardness
|
|
doc.engine
|
|
.set_vector_shape_hardness(layer_id, shape_idx, v.hardness);
|
|
// Primary color (stroke color)
|
|
doc.engine
|
|
.set_vector_shape_color(layer_id, shape_idx, state.primary_color);
|
|
// Fill color
|
|
if let Some(shapes) = doc.engine.active_vector_shapes() {
|
|
if let Some(shape) = shapes.get(shape_idx) {
|
|
if shape.fill_color().is_some() {
|
|
doc.engine
|
|
.set_vector_shape_fill_color(layer_id, shape_idx, state.secondary_color);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Shape-specific properties
|
|
if let Some(shapes) = 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
|
|
}
|
|
VectorShape::SvgShape { .. } => {
|
|
// shape-specific params are baked into the SVG
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Pull properties from the currently selected vector shape back into
|
|
/// `ToolState.tool_configs.vector` and color state.
|
|
///
|
|
/// Called when the user clicks/selects a shape in VectorSelect mode
|
|
/// so the properties panel reflects the shape's actual values.
|
|
pub fn sync_tool_from_shape(doc: &AppDocument, state: &mut ToolState) {
|
|
if state.active_tool != Tool::VectorSelect {
|
|
return;
|
|
}
|
|
let shape_idx = match state.selected_vector_shape {
|
|
Some(i) => i,
|
|
None => return,
|
|
};
|
|
|
|
if let Some(shapes) = doc.engine.active_vector_shapes() {
|
|
if let Some(shape) = shapes.get(shape_idx) {
|
|
let v = &mut state.tool_configs.vector;
|
|
v.stroke_size = shape.stroke();
|
|
v.opacity = shape.shape_opacity();
|
|
v.hardness = shape.hardness();
|
|
v.fill_shape = shape.is_filled();
|
|
|
|
match shape {
|
|
VectorShape::Rect { radius, .. } => {
|
|
v.round_rect_radius = *radius;
|
|
}
|
|
VectorShape::Star {
|
|
points,
|
|
inner_radius,
|
|
..
|
|
} => {
|
|
v.star_points = *points;
|
|
v.star_inner_radius = *inner_radius;
|
|
}
|
|
VectorShape::Polygon { sides, .. } => {
|
|
v.polygon_sides = *sides;
|
|
}
|
|
VectorShape::Line {
|
|
cap_start,
|
|
cap_end: _,
|
|
..
|
|
} => {
|
|
v.line_cap = *cap_start;
|
|
}
|
|
VectorShape::SvgShape { .. } => {
|
|
// params baked into SVG, nothing to sync
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
state.primary_color = shape.color();
|
|
if let Some(fc) = shape.fill_color() {
|
|
state.secondary_color = fc;
|
|
}
|
|
}
|
|
}
|
|
}
|