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:
@@ -100,7 +100,11 @@ fn load_colors() -> PersistedColors {
|
||||
.unwrap_or_default(),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => PersistedColors::default(),
|
||||
Err(e) => {
|
||||
log::warn!("Failed to read persisted colors at {}: {}", path.display(), e);
|
||||
log::warn!(
|
||||
"Failed to read persisted colors at {}: {}",
|
||||
path.display(),
|
||||
e
|
||||
);
|
||||
PersistedColors::default()
|
||||
}
|
||||
}
|
||||
@@ -171,8 +175,8 @@ pub struct HcieIcedApp {
|
||||
pub active_dialog: ActiveDialog,
|
||||
/// Index of the active tool slot in the sidebar (for sub-tools).
|
||||
pub active_tool_slot: usize,
|
||||
/// Whether the sub-tools popup is open for the active slot.
|
||||
pub sub_tools_open: bool,
|
||||
/// Slot whose expanded sub-tool chooser remains open until toggled again.
|
||||
pub open_subtool_slot: Option<usize>,
|
||||
/// Per-slot last-used sub-tool index (egui's `slot_last_used`). The sidebar
|
||||
/// shows the last-used tool's icon/label for each slot rather than always
|
||||
/// the primary tool.
|
||||
@@ -323,11 +327,18 @@ pub struct HcieIcedApp {
|
||||
pub show_dock_profile_dialog: bool,
|
||||
/// Current UI language for internationalization.
|
||||
pub _language: i18n::Language,
|
||||
/// Active Brushes & Tips category filter retained while the panel redraws.
|
||||
pub brush_category: crate::panels::brushes::BrushCategory,
|
||||
/// Context menu screen position (x, y). None means closed.
|
||||
pub canvas_context_menu: Option<(f32, f32)>,
|
||||
/// Dirty flag set whenever fg/bg/recent colors change, so colors can be
|
||||
/// persisted to disk (egui persists these under the `hcie_colors` key).
|
||||
pub colors_dirty: bool,
|
||||
/// True while the user is dragging on the color wheel; shape sync and
|
||||
/// composite refresh are deferred until the drag ends to avoid per-move lag.
|
||||
pub color_dragging: bool,
|
||||
/// Final color sampled during the active wheel drag; committed to Recent on mouse-up.
|
||||
pub pending_drag_color: Option<[u8; 4]>,
|
||||
/// Layer-bound snapshot of vector shapes before editing (for exact Cancel restoration).
|
||||
pub vector_shapes_snapshot: Option<crate::vector_edit::VectorEditSnapshot>,
|
||||
/// Cached `(kind, svg)` pairs from the engine's shape catalog for the active document.
|
||||
@@ -567,6 +578,8 @@ pub struct ToolState {
|
||||
pub last_update_instant: std::time::Instant,
|
||||
/// Imported brush presets from ABR/KPP files.
|
||||
pub imported_brushes: Vec<hcie_engine_api::BrushPreset>,
|
||||
/// Complete preset currently supplying non-style brush-tip parameters.
|
||||
pub active_brush_preset: Option<hcie_engine_api::BrushPreset>,
|
||||
/// True if currently holding shift to resize brush
|
||||
pub is_resizing_brush: bool,
|
||||
/// Starting brush size before resize drag
|
||||
@@ -596,6 +609,7 @@ impl Default for ToolState {
|
||||
last_composite_refresh: std::time::Instant::now(),
|
||||
last_update_instant: std::time::Instant::now(),
|
||||
imported_brushes: Vec::new(),
|
||||
active_brush_preset: None,
|
||||
is_resizing_brush: false,
|
||||
brush_resize_start_size: 20.0,
|
||||
brush_resize_start_pos: None,
|
||||
@@ -621,6 +635,10 @@ pub enum Message {
|
||||
// ── Color ───────────────────────────────────────────
|
||||
FgColorChanged([u8; 4]),
|
||||
BgColorChanged([u8; 4]),
|
||||
/// Signals the start of a color-picker drag with the initial color.
|
||||
ColorDragStarted([u8; 4]),
|
||||
/// Signals the end of a color-picker drag; triggers deferred shape sync.
|
||||
ColorDragEnded,
|
||||
SwapColors,
|
||||
ResetColors,
|
||||
ColorTabChanged(usize),
|
||||
@@ -719,6 +737,8 @@ pub enum Message {
|
||||
|
||||
// ── Brushes ─────────────────────────────────────────
|
||||
BrushStyleSelected(BrushStyle),
|
||||
BrushPresetSelected(hcie_engine_api::BrushPreset),
|
||||
BrushCategorySelected(crate::panels::brushes::BrushCategory),
|
||||
BrushSizeChanged(f32),
|
||||
BrushSizeRelative(f32),
|
||||
BrushOpacityChanged(f32),
|
||||
@@ -1258,6 +1278,79 @@ fn build_brush_tip(state: &ToolState) -> BrushTip {
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts an imported/default brush-engine preset tip into the protocol tip used by `Engine`.
|
||||
///
|
||||
/// **Arguments:** `source` is the preset-owned brush-engine tip. **Returns:** A protocol tip with
|
||||
/// all shared rendering fields copied and protocol-only time dynamics left at defaults.
|
||||
/// **Side Effects / Dependencies:** None.
|
||||
fn protocol_tip_from_preset(source: &hcie_engine_api::brush::BrushTip) -> BrushTip {
|
||||
let mut tip = BrushTip::default();
|
||||
tip.style = protocol_style_from_preset(source.style);
|
||||
tip.size = source.size;
|
||||
tip.opacity = source.opacity;
|
||||
tip.hardness = source.hardness;
|
||||
tip.spacing = source.spacing;
|
||||
tip.flow = source.flow;
|
||||
tip.jitter_amount = source.jitter_amount;
|
||||
tip.scatter_amount = source.scatter_amount;
|
||||
tip.angle = source.angle;
|
||||
tip.roundness = source.roundness;
|
||||
tip.spray_particle_size = source.spray_particle_size;
|
||||
tip.spray_density = source.spray_density;
|
||||
tip.bitmap_pixels = source.bitmap_pixels.clone();
|
||||
tip.bitmap_w = source.bitmap_w;
|
||||
tip.bitmap_h = source.bitmap_h;
|
||||
tip.color_variant = source.color_variant;
|
||||
tip.variant_amount = source.variant_amount;
|
||||
tip.density = source.density;
|
||||
tip.drawing_angle = source.drawing_angle;
|
||||
tip.rotation_random = source.rotation_random;
|
||||
tip
|
||||
}
|
||||
|
||||
/// Maps the brush-engine preset enum to the protocol enum used by GUI tool state.
|
||||
///
|
||||
/// **Arguments:** `style` is a serialized preset style. **Returns:** Its protocol equivalent.
|
||||
/// **Side Effects / Dependencies:** None.
|
||||
fn protocol_style_from_preset(style: hcie_engine_api::brush::BrushStyle) -> BrushStyle {
|
||||
use hcie_engine_api::brush::BrushStyle as PresetStyle;
|
||||
match style {
|
||||
PresetStyle::Round => BrushStyle::Round,
|
||||
PresetStyle::Square => BrushStyle::Square,
|
||||
PresetStyle::HardRound => BrushStyle::HardRound,
|
||||
PresetStyle::SoftRound => BrushStyle::SoftRound,
|
||||
PresetStyle::Star => BrushStyle::Star,
|
||||
PresetStyle::Noise => BrushStyle::Noise,
|
||||
PresetStyle::Texture => BrushStyle::Texture,
|
||||
PresetStyle::Spray => BrushStyle::Spray,
|
||||
PresetStyle::Pencil => BrushStyle::Pencil,
|
||||
PresetStyle::Pen => BrushStyle::Pen,
|
||||
PresetStyle::Calligraphy => BrushStyle::Calligraphy,
|
||||
PresetStyle::Oil => BrushStyle::Oil,
|
||||
PresetStyle::Charcoal => BrushStyle::Charcoal,
|
||||
PresetStyle::Leaf => BrushStyle::Leaf,
|
||||
PresetStyle::Rock => BrushStyle::Rock,
|
||||
PresetStyle::Meadow => BrushStyle::Meadow,
|
||||
PresetStyle::Wood => BrushStyle::Wood,
|
||||
PresetStyle::Watercolor => BrushStyle::Watercolor,
|
||||
PresetStyle::Marker => BrushStyle::Marker,
|
||||
PresetStyle::Sketch => BrushStyle::Sketch,
|
||||
PresetStyle::Hatch => BrushStyle::Hatch,
|
||||
PresetStyle::Glow => BrushStyle::Glow,
|
||||
PresetStyle::Airbrush => BrushStyle::Airbrush,
|
||||
PresetStyle::Crayon => BrushStyle::Crayon,
|
||||
PresetStyle::WetPaint => BrushStyle::WetPaint,
|
||||
PresetStyle::InkPen => BrushStyle::InkPen,
|
||||
PresetStyle::Clouds => BrushStyle::Clouds,
|
||||
PresetStyle::Dirt => BrushStyle::Dirt,
|
||||
PresetStyle::Tree => BrushStyle::Tree,
|
||||
PresetStyle::Bristle => BrushStyle::Bristle,
|
||||
PresetStyle::Mixer => BrushStyle::Mixer,
|
||||
PresetStyle::Blender => BrushStyle::Blender,
|
||||
PresetStyle::Bitmap => BrushStyle::Bitmap,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a display label back to a PaneType for dock profile loading.
|
||||
fn pane_type_from_label(label: &str) -> Option<crate::dock::state::PaneType> {
|
||||
use crate::dock::state::PaneType;
|
||||
@@ -1605,9 +1698,9 @@ impl HcieIcedApp {
|
||||
active_dialog: ActiveDialog::None,
|
||||
active_tool_slot: crate::sidebar::slot_for_tool(hcie_engine_api::Tool::Brush)
|
||||
.unwrap_or(0),
|
||||
sub_tools_open: false,
|
||||
open_subtool_slot: None,
|
||||
sidebar_expanded: settings.panel_layout.sidebar_expanded,
|
||||
slot_last_used: vec![0; 20],
|
||||
slot_last_used: vec![0; 21],
|
||||
_dialog_input: String::new(),
|
||||
_dialog_input2: String::new(),
|
||||
_show_performance: false,
|
||||
@@ -1692,7 +1785,10 @@ impl HcieIcedApp {
|
||||
dock_profile_rename_buf: String::new(),
|
||||
show_dock_profile_dialog: false,
|
||||
_language: i18n::Language::default(),
|
||||
brush_category: crate::panels::brushes::BrushCategory::All,
|
||||
colors_dirty: false,
|
||||
color_dragging: false,
|
||||
pending_drag_color: None,
|
||||
vector_shapes_snapshot: None,
|
||||
cached_custom_shapes: Vec::new(),
|
||||
vector_drag_last: None,
|
||||
@@ -2109,10 +2205,19 @@ impl HcieIcedApp {
|
||||
}
|
||||
|
||||
/// Add a color to the recent colors list (most recent first, max 40).
|
||||
///
|
||||
/// Immediately persists to disk so committed colors survive a crash or non-graceful
|
||||
/// shutdown. Color-wheel movement never calls this method; the final sampled color is
|
||||
/// committed once by `ColorDragEnded`.
|
||||
fn add_recent_color(&mut self, color: [u8; 4]) {
|
||||
self.recent_colors.retain(|c| *c != color);
|
||||
self.recent_colors.insert(0, color);
|
||||
self.recent_colors.truncate(40);
|
||||
// Write immediately unless we are in the middle of a colour-wheel
|
||||
// drag (where callers are responsible for the final flush).
|
||||
if !self.color_dragging {
|
||||
save_colors(self.fg_color, self.bg_color, &self.recent_colors);
|
||||
}
|
||||
}
|
||||
|
||||
/// Refresh the composite buffer using incremental dirty-region compositing.
|
||||
@@ -2287,7 +2392,17 @@ impl HcieIcedApp {
|
||||
/// Called before starting a stroke to ensure the engine uses
|
||||
/// the current brush size, opacity, hardness, and style.
|
||||
fn apply_brush_params(&mut self) {
|
||||
let mut tip = build_brush_tip(&self.tool_state);
|
||||
let mut tip = self
|
||||
.tool_state
|
||||
.active_brush_preset
|
||||
.as_ref()
|
||||
.map(|preset| protocol_tip_from_preset(&preset.tip))
|
||||
.unwrap_or_else(|| build_brush_tip(&self.tool_state));
|
||||
tip.style = self.tool_state.brush_style;
|
||||
tip.size = self.tool_state.brush_size;
|
||||
tip.opacity = self.tool_state.brush_opacity;
|
||||
tip.hardness = self.tool_state.brush_hardness;
|
||||
tip.spacing = self.tool_state.brush_spacing;
|
||||
match self.tool_state.active_tool {
|
||||
Tool::Pen => {
|
||||
tip.style = BrushStyle::Round;
|
||||
@@ -2354,7 +2469,6 @@ impl HcieIcedApp {
|
||||
Message::ToolSelected(tool) => {
|
||||
log::info!("Tool selected: {:?}", tool);
|
||||
self.tool_state.active_tool = tool;
|
||||
self.sub_tools_open = false;
|
||||
if tool == Tool::Brush {
|
||||
self.dock.reopen_pane(crate::dock::state::PaneType::Brushes);
|
||||
}
|
||||
@@ -2386,6 +2500,7 @@ impl HcieIcedApp {
|
||||
}
|
||||
|
||||
Message::ToolSlotClicked(slot_idx, primary_tool) => {
|
||||
let same_slot = self.active_tool_slot == slot_idx;
|
||||
self.active_tool_slot = slot_idx;
|
||||
let tool = crate::sidebar::last_used_tool(slot_idx, &self.slot_last_used);
|
||||
let selected_tool = if crate::sidebar::tool_slots().get(slot_idx).is_some() {
|
||||
@@ -2395,18 +2510,30 @@ impl HcieIcedApp {
|
||||
};
|
||||
self.tool_state.active_tool = selected_tool;
|
||||
if selected_tool == Tool::Brush {
|
||||
self.dock
|
||||
.reopen_pane(crate::dock::state::PaneType::Brushes);
|
||||
self.dock.reopen_pane(crate::dock::state::PaneType::Brushes);
|
||||
}
|
||||
let supports_popup = crate::sidebar::tool_slots()
|
||||
.get(slot_idx)
|
||||
.is_some_and(|slot| slot.tools.len() > 1);
|
||||
if same_slot && supports_popup {
|
||||
self.open_subtool_slot = if self.open_subtool_slot == Some(slot_idx) {
|
||||
None
|
||||
} else {
|
||||
Some(slot_idx)
|
||||
};
|
||||
}
|
||||
if matches!(selected_tool, Tool::CustomShape(_)) {
|
||||
self.dock
|
||||
.reopen_pane(crate::dock::state::PaneType::CustomShapes);
|
||||
}
|
||||
self.sub_tools_open = false;
|
||||
}
|
||||
Message::SubtoolsToggle(slot_idx) => {
|
||||
let supports_popup = crate::sidebar::tool_slots()
|
||||
.get(slot_idx)
|
||||
.is_some_and(|slot| slot.tools.len() > 1);
|
||||
if supports_popup {
|
||||
let opening = !(self.sub_tools_open && self.active_tool_slot == slot_idx);
|
||||
self.sub_tools_open = opening;
|
||||
let opening = self.open_subtool_slot != Some(slot_idx);
|
||||
self.open_subtool_slot = opening.then_some(slot_idx);
|
||||
self.active_tool_slot = slot_idx;
|
||||
if opening && !self.sidebar_expanded {
|
||||
self.sidebar_expanded = true;
|
||||
@@ -2414,13 +2541,14 @@ impl HcieIcedApp {
|
||||
self.settings.panel_layout.sidebar_width = 72.0;
|
||||
let _ = self.settings.save();
|
||||
}
|
||||
// If the user is opening the tool slot popup, also reveal the
|
||||
// Brushes panel when the slot's current tool is the brush.
|
||||
if opening {
|
||||
let tool = crate::sidebar::last_used_tool(slot_idx, &self.slot_last_used);
|
||||
if tool == Tool::Brush {
|
||||
self.dock.reopen_pane(crate::dock::state::PaneType::Brushes);
|
||||
}
|
||||
if matches!(tool, Tool::CustomShape(_)) {
|
||||
self.dock
|
||||
.reopen_pane(crate::dock::state::PaneType::Brushes);
|
||||
.reopen_pane(crate::dock::state::PaneType::CustomShapes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2429,25 +2557,45 @@ impl HcieIcedApp {
|
||||
if self.dock.active_auto_hide.take().is_some() {
|
||||
return Task::none();
|
||||
}
|
||||
if self.sub_tools_open {
|
||||
self.sub_tools_open = false;
|
||||
} else {
|
||||
self.active_menu = None;
|
||||
}
|
||||
self.active_menu = None;
|
||||
}
|
||||
|
||||
Message::FgColorChanged(color) => {
|
||||
self.fg_color = color;
|
||||
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);
|
||||
self.refresh_composite_if_needed();
|
||||
if self.color_dragging {
|
||||
self.pending_drag_color = Some(color);
|
||||
} else {
|
||||
self.add_recent_color(color);
|
||||
self.colors_dirty = true;
|
||||
crate::shape_sync::sync_shape_from_tool(self);
|
||||
self.refresh_composite_if_needed();
|
||||
}
|
||||
}
|
||||
|
||||
Message::BgColorChanged(color) => {
|
||||
self.bg_color = color;
|
||||
self.add_recent_color(color);
|
||||
self.colors_dirty = true;
|
||||
if !self.color_dragging {
|
||||
crate::shape_sync::sync_shape_from_tool(self);
|
||||
self.refresh_composite_if_needed();
|
||||
}
|
||||
}
|
||||
|
||||
Message::ColorDragStarted(initial_color) => {
|
||||
self.color_dragging = true;
|
||||
self.pending_drag_color = Some(initial_color);
|
||||
self.fg_color = initial_color;
|
||||
self.active_document_mut().engine.set_color(initial_color);
|
||||
}
|
||||
|
||||
Message::ColorDragEnded => {
|
||||
self.color_dragging = false;
|
||||
if let Some(final_color) = self.pending_drag_color.take() {
|
||||
self.add_recent_color(final_color);
|
||||
}
|
||||
self.colors_dirty = false;
|
||||
crate::shape_sync::sync_shape_from_tool(self);
|
||||
self.refresh_composite_if_needed();
|
||||
}
|
||||
@@ -2674,6 +2822,8 @@ impl HcieIcedApp {
|
||||
];
|
||||
self.fg_color = color;
|
||||
self.documents[self.active_doc].engine.set_color(color);
|
||||
self.add_recent_color(color);
|
||||
self.colors_dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2940,6 +3090,8 @@ impl HcieIcedApp {
|
||||
];
|
||||
self.fg_color = color;
|
||||
self.documents[self.active_doc].engine.set_color(color);
|
||||
self.add_recent_color(color);
|
||||
self.colors_dirty = true;
|
||||
}
|
||||
}
|
||||
} else if self.tool_state.is_resizing_brush {
|
||||
@@ -4479,6 +4631,21 @@ impl HcieIcedApp {
|
||||
Message::BrushStyleSelected(style) => {
|
||||
log::info!("Brush style selected: {:?}", style);
|
||||
self.tool_state.brush_style = style;
|
||||
self.tool_state.active_brush_preset = None;
|
||||
}
|
||||
Message::BrushPresetSelected(preset) => {
|
||||
log::info!("Brush preset selected: {}", preset.name);
|
||||
self.tool_state.brush_style = protocol_style_from_preset(preset.style);
|
||||
self.tool_state.brush_size = preset.tip.size;
|
||||
self.tool_state.brush_opacity = preset.tip.opacity;
|
||||
self.tool_state.brush_hardness = preset.tip.hardness;
|
||||
self.tool_state.brush_spacing = preset.tip.spacing;
|
||||
self.tool_state.active_brush_preset = Some(preset);
|
||||
self.settings.update_from_tool_state(&self.tool_state);
|
||||
let _ = self.settings.save();
|
||||
}
|
||||
Message::BrushCategorySelected(category) => {
|
||||
self.brush_category = category;
|
||||
}
|
||||
Message::BrushSizeChanged(size) => {
|
||||
self.tool_state.brush_size = size;
|
||||
@@ -5576,22 +5743,22 @@ impl HcieIcedApp {
|
||||
// Use cached catalog snapshot if available; otherwise read once from the
|
||||
// active document's shape catalog. `cached_custom_shapes` is populated/updated
|
||||
// by `RefreshCustomShapes` so drawing remains consistent with the panel.
|
||||
let custom_shapes: Vec<(String, String)> = if self.cached_custom_shapes.is_empty()
|
||||
{
|
||||
self.documents
|
||||
.get(self.active_doc)
|
||||
.map(|d| {
|
||||
d.engine
|
||||
.shape_catalog
|
||||
.all()
|
||||
.iter()
|
||||
.map(|e| (e.kind.clone(), e.svg.clone()))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
self.cached_custom_shapes.clone()
|
||||
};
|
||||
let custom_shapes: Vec<(String, String)> =
|
||||
if self.cached_custom_shapes.is_empty() {
|
||||
self.documents
|
||||
.get(self.active_doc)
|
||||
.map(|d| {
|
||||
d.engine
|
||||
.shape_catalog
|
||||
.all()
|
||||
.iter()
|
||||
.map(|e| (e.kind.clone(), e.svg.clone()))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
self.cached_custom_shapes.clone()
|
||||
};
|
||||
|
||||
let engine = &mut self.documents[self.active_doc].engine;
|
||||
let color = self.fg_color;
|
||||
@@ -6298,9 +6465,11 @@ impl HcieIcedApp {
|
||||
.active_vector_shapes()
|
||||
.map_or(0, |s| s.len());
|
||||
if idx + 1 < count {
|
||||
self.documents[self.active_doc]
|
||||
.engine
|
||||
.reorder_vector_shape(layer_id, idx, idx + 1);
|
||||
self.documents[self.active_doc].engine.reorder_vector_shape(
|
||||
layer_id,
|
||||
idx,
|
||||
idx + 1,
|
||||
);
|
||||
self.documents[self.active_doc].selected_vector_shape = Some(idx + 1);
|
||||
self.documents[self.active_doc].modified = true;
|
||||
self.documents[self.active_doc]
|
||||
@@ -6316,9 +6485,11 @@ impl HcieIcedApp {
|
||||
if let Some(idx) = self.documents[self.active_doc].selected_vector_shape {
|
||||
if idx > 0 {
|
||||
let layer_id = self.documents[self.active_doc].engine.active_layer_id();
|
||||
self.documents[self.active_doc]
|
||||
.engine
|
||||
.reorder_vector_shape(layer_id, idx, idx - 1);
|
||||
self.documents[self.active_doc].engine.reorder_vector_shape(
|
||||
layer_id,
|
||||
idx,
|
||||
idx - 1,
|
||||
);
|
||||
self.documents[self.active_doc].selected_vector_shape = Some(idx - 1);
|
||||
self.documents[self.active_doc].modified = true;
|
||||
self.documents[self.active_doc]
|
||||
@@ -6601,11 +6772,13 @@ impl HcieIcedApp {
|
||||
.engine
|
||||
.set_vector_shape_fill_color(layer_id, idx, c);
|
||||
self.documents[self.active_doc].modified = true;
|
||||
crate::shape_sync::sync_tool_from_shape(self);
|
||||
self.documents[self.active_doc]
|
||||
.engine
|
||||
.mark_composite_dirty();
|
||||
self.refresh_composite_if_needed();
|
||||
if !self.color_dragging {
|
||||
crate::shape_sync::sync_tool_from_shape(self);
|
||||
self.documents[self.active_doc]
|
||||
.engine
|
||||
.mark_composite_dirty();
|
||||
self.refresh_composite_if_needed();
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::GeometryStrokeColorChanged(c) => {
|
||||
@@ -6615,11 +6788,13 @@ impl HcieIcedApp {
|
||||
.engine
|
||||
.set_vector_shape_color(layer_id, idx, c);
|
||||
self.documents[self.active_doc].modified = true;
|
||||
crate::shape_sync::sync_tool_from_shape(self);
|
||||
self.documents[self.active_doc]
|
||||
.engine
|
||||
.mark_composite_dirty();
|
||||
self.refresh_composite_if_needed();
|
||||
if !self.color_dragging {
|
||||
crate::shape_sync::sync_tool_from_shape(self);
|
||||
self.documents[self.active_doc]
|
||||
.engine
|
||||
.mark_composite_dirty();
|
||||
self.refresh_composite_if_needed();
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::GeometryOpacityChanged(v) => {
|
||||
@@ -7962,7 +8137,6 @@ impl HcieIcedApp {
|
||||
|
||||
// ── Menu ─────────────────────────────────────
|
||||
Message::MenuOpen(idx) => {
|
||||
self.sub_tools_open = false;
|
||||
if self.active_menu == Some(idx) {
|
||||
self.active_menu = None;
|
||||
} else {
|
||||
@@ -8929,11 +9103,8 @@ impl HcieIcedApp {
|
||||
}
|
||||
// Mirrors egui's Escape handling. Priority: viewer > crop >
|
||||
// transform > vector selection > selection > dialog > viewer fallback.
|
||||
match overlay_escape_target(self.sub_tools_open, self.active_menu.is_some()) {
|
||||
Some(OverlayEscapeTarget::Subtools) => {
|
||||
self.sub_tools_open = false;
|
||||
return Task::none();
|
||||
}
|
||||
match overlay_escape_target(false, self.active_menu.is_some()) {
|
||||
Some(OverlayEscapeTarget::Subtools) => unreachable!(),
|
||||
Some(OverlayEscapeTarget::Menu) => {
|
||||
self.active_menu = None;
|
||||
return Task::none();
|
||||
@@ -8944,7 +9115,7 @@ impl HcieIcedApp {
|
||||
return self.update(Message::ViewerToggle);
|
||||
} else if matches!(self.active_dialog, ActiveDialog::SvgEditor) {
|
||||
return self.update(Message::SvgEditorCancel);
|
||||
} else if self.sub_tools_open || self.active_menu.is_some() {
|
||||
} else if self.active_menu.is_some() {
|
||||
return Task::none();
|
||||
} else if self.documents[self.active_doc].crop_state.active {
|
||||
return self.update(Message::CropCancel);
|
||||
@@ -9059,8 +9230,7 @@ impl HcieIcedApp {
|
||||
&self.fg_color,
|
||||
&self.bg_color,
|
||||
colors,
|
||||
self.active_tool_slot,
|
||||
self.sub_tools_open,
|
||||
self.open_subtool_slot,
|
||||
&self.slot_last_used,
|
||||
self.sidebar_expanded,
|
||||
);
|
||||
@@ -9226,6 +9396,7 @@ impl HcieIcedApp {
|
||||
self.theme_state.colors(),
|
||||
Some(&self.dock),
|
||||
self.settings.window.width,
|
||||
self.settings.window.height,
|
||||
) {
|
||||
stack = stack.push(menu_overlay);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user