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:
2026-07-21 04:25:23 +03:00
parent 325bd0d8e8
commit b49106dc1d
60 changed files with 2716 additions and 1266 deletions
+139 -44
View File
@@ -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()
}
}