BIG REFACTOR GPT SOL
feat: Refactor Iced panel adapter and introduce build metadata management - Updated Cargo.toml files across multiple crates to use workspace versioning. - Enhanced the `iced-panel-adapter` to include a new `plain_slider` module and updated widget rendering to support theme colors. - Added new theme color utilities for recessed and elevated surfaces in `iced-panel-adapter`. - Introduced a new `hcie-build-info` crate to manage build metadata, including a build ID system. - Created a build script to synchronize build IDs across the workspace. - Added a Makefile for simplified build commands for the Iced application. - Implemented regression tests for vector shape creation history in the engine API. - Added a new script for managing Cargo commands with synchronized build ID increments. - Updated line count report to reflect recent changes in codebase. - Created a visual plan document for future improvements in the Iced history and panel systems.
This commit is contained in:
+139
-44
@@ -1,6 +1,9 @@
|
||||
#![allow(dead_code)]
|
||||
use hcie_protocol::{BlendMode, Layer, LayerData, LayerInfo, LayerStyle, LayerType, MAX_UNDO_STEPS_DEFAULT, VectorShape};
|
||||
use hcie_history::{HistoryManager, UndoableAction};
|
||||
use hcie_protocol::{
|
||||
BlendMode, Layer, LayerData, LayerInfo, LayerStyle, LayerType, VectorShape,
|
||||
MAX_UNDO_STEPS_DEFAULT,
|
||||
};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
static NEXT_LAYER_ID: AtomicU64 = AtomicU64::new(1);
|
||||
@@ -66,7 +69,8 @@ impl Document {
|
||||
|
||||
pub fn add_layer(&mut self, name: impl Into<String>) -> u64 {
|
||||
let name_str = name.into();
|
||||
let new_layer = Layer::new_transparent(name_str.clone(), self.canvas_width, self.canvas_height);
|
||||
let new_layer =
|
||||
Layer::new_transparent(name_str.clone(), self.canvas_width, self.canvas_height);
|
||||
let new_id = next_layer_id();
|
||||
let idx = self.layers.len();
|
||||
self.layers.push(new_layer);
|
||||
@@ -87,8 +91,53 @@ impl Document {
|
||||
new_id
|
||||
}
|
||||
|
||||
/// Creates a vector layer containing its first shape as one history transaction.
|
||||
///
|
||||
/// **Purpose:** Avoids recording the intermediate transparent raster layer that would be
|
||||
/// produced by calling [`Document::add_layer`] before converting the layer to vector data.
|
||||
/// **Logic & Workflow:** Builds the complete vector layer, assigns a stable ID, appends it,
|
||||
/// marks the document dirty, and records the final layer in a single `LayerAddAction` so Undo
|
||||
/// removes the layer and Redo restores both the layer type and its initial shape.
|
||||
/// **Arguments:** `name` is the generated layer label, `shape` is the first vector shape, and
|
||||
/// `description` is the user-facing history entry.
|
||||
/// **Returns:** The stable ID assigned to the new vector layer.
|
||||
/// **Side Effects / Dependencies:** Mutates layers, active-layer selection, dirty state, and
|
||||
/// the document history stack.
|
||||
pub fn add_vector_layer_with_shape(
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
shape: VectorShape,
|
||||
description: impl Into<String>,
|
||||
) -> u64 {
|
||||
let name = name.into();
|
||||
let mut layer = Layer::new_transparent(name, self.canvas_width, self.canvas_height);
|
||||
let layer_id = next_layer_id();
|
||||
layer.id = layer_id;
|
||||
layer.layer_type = LayerType::Vector;
|
||||
layer.data = LayerData::Vector {
|
||||
shapes: vec![shape],
|
||||
};
|
||||
layer.dirty = true;
|
||||
|
||||
let layer_index = self.layers.len();
|
||||
self.layers.push(layer);
|
||||
self.active_layer = layer_index;
|
||||
self.composite_dirty = true;
|
||||
self.modified = true;
|
||||
|
||||
self.history.push(Box::new(LayerAddAction {
|
||||
layer_index,
|
||||
layer: self.layers[layer_index].clone(),
|
||||
description: description.into(),
|
||||
}));
|
||||
|
||||
layer_id
|
||||
}
|
||||
|
||||
pub fn delete_layer(&mut self, idx: usize) {
|
||||
if idx >= self.layers.len() { return; }
|
||||
if idx >= self.layers.len() {
|
||||
return;
|
||||
}
|
||||
|
||||
let layer = &self.layers[idx];
|
||||
let action = LayerDeleteAction {
|
||||
@@ -128,9 +177,13 @@ impl Document {
|
||||
}
|
||||
|
||||
pub fn set_layer_visible(&mut self, idx: usize, visible: bool) {
|
||||
if idx >= self.layers.len() { return; }
|
||||
if idx >= self.layers.len() {
|
||||
return;
|
||||
}
|
||||
let old_visible = self.layers[idx].visible;
|
||||
if old_visible == visible { return; }
|
||||
if old_visible == visible {
|
||||
return;
|
||||
}
|
||||
|
||||
self.layers[idx].visible = visible;
|
||||
self.layers[idx].dirty = true;
|
||||
@@ -155,11 +208,13 @@ impl Document {
|
||||
|
||||
pub fn undo(&mut self) {
|
||||
self.history.undo(&mut self.layers);
|
||||
self.normalize_active_layer();
|
||||
self.composite_dirty = true;
|
||||
}
|
||||
|
||||
pub fn redo(&mut self) {
|
||||
self.history.redo(&mut self.layers);
|
||||
self.normalize_active_layer();
|
||||
self.composite_dirty = true;
|
||||
}
|
||||
|
||||
@@ -185,16 +240,29 @@ impl Document {
|
||||
|
||||
pub fn jump_to_history(&mut self, idx: i32) {
|
||||
self.history.jump_to(idx, &mut self.layers);
|
||||
self.normalize_active_layer();
|
||||
self.composite_dirty = true;
|
||||
}
|
||||
|
||||
/// Keeps active-layer selection valid after history actions add or remove layers.
|
||||
///
|
||||
/// **Purpose:** Layer history actions can invalidate the selected index when Undo removes the
|
||||
/// active layer. **Logic & Workflow:** Clamps the index to the final available layer, using
|
||||
/// zero when no layer exists. **Arguments/Returns:** None. **Side Effects:** Updates only
|
||||
/// `active_layer`.
|
||||
fn normalize_active_layer(&mut self) {
|
||||
self.active_layer = self.active_layer.min(self.layers.len().saturating_sub(1));
|
||||
}
|
||||
|
||||
pub fn push_vector_snapshot(
|
||||
&mut self,
|
||||
layer_idx: usize,
|
||||
before_shapes: Option<Vec<VectorShape>>,
|
||||
description: String,
|
||||
) {
|
||||
if layer_idx >= self.layers.len() { return; }
|
||||
if layer_idx >= self.layers.len() {
|
||||
return;
|
||||
}
|
||||
let layer = &self.layers[layer_idx];
|
||||
let after_shapes = if let LayerData::Vector { shapes } = &layer.data {
|
||||
Some(shapes.clone())
|
||||
@@ -343,7 +411,9 @@ impl Document {
|
||||
}
|
||||
|
||||
pub fn move_layer(&mut self, from: usize, to: usize) {
|
||||
if from >= self.layers.len() || to >= self.layers.len() || from == to { return; }
|
||||
if from >= self.layers.len() || to >= self.layers.len() || from == to {
|
||||
return;
|
||||
}
|
||||
let layer = self.layers.remove(from);
|
||||
let insert_idx = if to > from { to } else { to };
|
||||
self.layers.insert(insert_idx, layer);
|
||||
@@ -356,7 +426,9 @@ impl Document {
|
||||
let y1 = y.min(self.canvas_height);
|
||||
let w1 = w.min(self.canvas_width - x1);
|
||||
let h1 = h.min(self.canvas_height - y1);
|
||||
if w1 == 0 || h1 == 0 { return; }
|
||||
if w1 == 0 || h1 == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
for layer in &mut self.layers {
|
||||
let mut new_pixels = vec![0u8; (w1 * h1 * 4) as usize];
|
||||
@@ -367,7 +439,8 @@ impl Document {
|
||||
if src_x < layer.width && src_y < layer.height {
|
||||
let src_i = ((src_y * layer.width + src_x) * 4) as usize;
|
||||
let dst_i = ((ly * w1 + lx) * 4) as usize;
|
||||
new_pixels[dst_i..dst_i + 4].copy_from_slice(&layer.pixels[src_i..src_i + 4]);
|
||||
new_pixels[dst_i..dst_i + 4]
|
||||
.copy_from_slice(&layer.pixels[src_i..src_i + 4]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -383,7 +456,9 @@ impl Document {
|
||||
}
|
||||
|
||||
pub fn resize_canvas(&mut self, w: u32, h: u32) {
|
||||
if w == 0 || h == 0 { return; }
|
||||
if w == 0 || h == 0 {
|
||||
return;
|
||||
}
|
||||
for layer in &mut self.layers {
|
||||
let mut new_pixels = vec![0u8; (w * h * 4) as usize];
|
||||
let copy_w = w.min(layer.width);
|
||||
@@ -392,9 +467,8 @@ impl Document {
|
||||
let src_start = (y * layer.width) as usize * 4;
|
||||
let dst_start = (y * w) as usize * 4;
|
||||
let row_bytes = (copy_w * 4) as usize;
|
||||
new_pixels[dst_start..dst_start + row_bytes].copy_from_slice(
|
||||
&layer.pixels[src_start..src_start + row_bytes]
|
||||
);
|
||||
new_pixels[dst_start..dst_start + row_bytes]
|
||||
.copy_from_slice(&layer.pixels[src_start..src_start + row_bytes]);
|
||||
}
|
||||
layer.pixels = new_pixels;
|
||||
layer.width = w;
|
||||
@@ -441,7 +515,9 @@ impl Document {
|
||||
|
||||
pub fn invert_selection(&mut self) {
|
||||
if let Some(mask) = &mut self.selection_mask {
|
||||
for val in mask.iter_mut() { *val = 255 - *val; }
|
||||
for val in mask.iter_mut() {
|
||||
*val = 255 - *val;
|
||||
}
|
||||
} else {
|
||||
self.select_all();
|
||||
}
|
||||
@@ -449,7 +525,10 @@ impl Document {
|
||||
|
||||
pub fn get_mask_at(&self, x: u32, y: u32) -> u8 {
|
||||
match &self.selection_mask {
|
||||
Some(mask) => mask.get((y * self.canvas_width + x) as usize).copied().unwrap_or(255),
|
||||
Some(mask) => mask
|
||||
.get((y * self.canvas_width + x) as usize)
|
||||
.copied()
|
||||
.unwrap_or(255),
|
||||
None => 255,
|
||||
}
|
||||
}
|
||||
@@ -477,32 +556,34 @@ impl Document {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
pub fn all_layer_ids(&self) -> Vec<u64> {
|
||||
self.layers.iter().map(|l| l.id).collect()
|
||||
}
|
||||
|
||||
pub fn layer_infos(&self) -> Vec<LayerInfo> {
|
||||
self.layers.iter().map(|l| LayerInfo {
|
||||
id: l.id,
|
||||
name: l.name.clone(),
|
||||
layer_type: l.layer_type,
|
||||
width: l.width,
|
||||
height: l.height,
|
||||
visible: l.visible,
|
||||
opacity: l.opacity,
|
||||
blend_mode: l.blend_mode,
|
||||
locked: l.locked,
|
||||
parent_id: l.parent_id,
|
||||
clipping_mask: l.clipping_mask,
|
||||
collapsed: l.collapsed,
|
||||
has_vector_shapes: matches!(&l.data, LayerData::Vector { .. }),
|
||||
shape_count: match &l.data {
|
||||
LayerData::Vector { shapes } => shapes.len(),
|
||||
_ => 0,
|
||||
},
|
||||
has_effects: !l.effects.is_empty() || !l.styles.is_empty(),
|
||||
}).collect()
|
||||
self.layers
|
||||
.iter()
|
||||
.map(|l| LayerInfo {
|
||||
id: l.id,
|
||||
name: l.name.clone(),
|
||||
layer_type: l.layer_type,
|
||||
width: l.width,
|
||||
height: l.height,
|
||||
visible: l.visible,
|
||||
opacity: l.opacity,
|
||||
blend_mode: l.blend_mode,
|
||||
locked: l.locked,
|
||||
parent_id: l.parent_id,
|
||||
clipping_mask: l.clipping_mask,
|
||||
collapsed: l.collapsed,
|
||||
has_vector_shapes: matches!(&l.data, LayerData::Vector { .. }),
|
||||
shape_count: match &l.data {
|
||||
LayerData::Vector { shapes } => shapes.len(),
|
||||
_ => 0,
|
||||
},
|
||||
has_effects: !l.effects.is_empty() || !l.styles.is_empty(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,7 +632,12 @@ struct LayerDeleteAction {
|
||||
|
||||
impl UndoableAction<Vec<Layer>> for LayerDeleteAction {
|
||||
fn undo(&mut self, layers: &mut Vec<Layer>) {
|
||||
let mut restored = Layer::from_rgba(self.name.clone(), self.width, self.height, self.pixels.clone());
|
||||
let mut restored = Layer::from_rgba(
|
||||
self.name.clone(),
|
||||
self.width,
|
||||
self.height,
|
||||
self.pixels.clone(),
|
||||
);
|
||||
restored.layer_type = self.layer_type;
|
||||
restored.opacity = self.opacity;
|
||||
restored.blend_mode = self.blend_mode;
|
||||
@@ -599,8 +685,11 @@ impl UndoableAction<Vec<Layer>> for DrawAction {
|
||||
let src_start = (row * rw * 4) as usize;
|
||||
let dst_start = (((y0 + row) * dst_w + x0) * 4) as usize;
|
||||
let len = (rw * 4) as usize;
|
||||
if src_start + len <= self.before_pixels.len() && dst_start + len <= layer.pixels.len() {
|
||||
layer.pixels[dst_start..dst_start + len].copy_from_slice(&self.before_pixels[src_start..src_start + len]);
|
||||
if src_start + len <= self.before_pixels.len()
|
||||
&& dst_start + len <= layer.pixels.len()
|
||||
{
|
||||
layer.pixels[dst_start..dst_start + len]
|
||||
.copy_from_slice(&self.before_pixels[src_start..src_start + len]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -608,7 +697,9 @@ impl UndoableAction<Vec<Layer>> for DrawAction {
|
||||
}
|
||||
layer.dirty = true;
|
||||
if let Some(ref shapes) = self.before_shapes {
|
||||
layer.data = LayerData::Vector { shapes: shapes.clone() };
|
||||
layer.data = LayerData::Vector {
|
||||
shapes: shapes.clone(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -621,8 +712,11 @@ impl UndoableAction<Vec<Layer>> for DrawAction {
|
||||
let src_start = (row * rw * 4) as usize;
|
||||
let dst_start = (((y0 + row) * dst_w + x0) * 4) as usize;
|
||||
let len = (rw * 4) as usize;
|
||||
if src_start + len <= self.after_pixels.len() && dst_start + len <= layer.pixels.len() {
|
||||
layer.pixels[dst_start..dst_start + len].copy_from_slice(&self.after_pixels[src_start..src_start + len]);
|
||||
if src_start + len <= self.after_pixels.len()
|
||||
&& dst_start + len <= layer.pixels.len()
|
||||
{
|
||||
layer.pixels[dst_start..dst_start + len]
|
||||
.copy_from_slice(&self.after_pixels[src_start..src_start + len]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -630,7 +724,9 @@ impl UndoableAction<Vec<Layer>> for DrawAction {
|
||||
}
|
||||
layer.dirty = true;
|
||||
if let Some(ref shapes) = self.after_shapes {
|
||||
layer.data = LayerData::Vector { shapes: shapes.clone() };
|
||||
layer.data = LayerData::Vector {
|
||||
shapes: shapes.clone(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -663,4 +759,3 @@ impl UndoableAction<Vec<Layer>> for LayerVisibilityAction {
|
||||
self.description.clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user