From 325bd0d8e8bc9c9c509cba800a9a572384c2b36a Mon Sep 17 00:00:00 2001 From: Halit Can Date: Mon, 20 Jul 2026 21:23:04 +0300 Subject: [PATCH] GOOD feat: add Layer Styles panel and enhance filter categories - Introduced a new Layer Styles panel that displays effects with toggle checkboxes for quick enable/disable functionality. - Updated the filters panel to support collapsible filter categories, allowing users to expand or collapse categories. - Enhanced the application state to manage expanded filter categories using a HashMap. - Modified the layout and sizing of various panels to accommodate the new Layer Styles panel. - Updated theme colors for better visual consistency across the application. - Improved the toolbar to streamline brush size and opacity controls. --- ...84456713003-iced-gui-visual-polish-plan.md | 328 ++++++------------ hcie-iced-app/crates/hcie-iced-gui/src/app.rs | 12 + .../crates/hcie-iced-gui/src/dock/sizing.rs | 2 +- .../crates/hcie-iced-gui/src/dock/state.rs | 76 ++-- .../crates/hcie-iced-gui/src/dock/view.rs | 18 +- .../hcie-iced-gui/src/panels/filters.rs | 92 +++-- .../hcie-iced-gui/src/panels/layer_styles.rs | 63 ++++ .../crates/hcie-iced-gui/src/panels/layers.rs | 20 +- .../crates/hcie-iced-gui/src/panels/menus.rs | 8 +- .../crates/hcie-iced-gui/src/panels/styles.rs | 16 +- .../hcie-iced-gui/src/panels/toolbar.rs | 184 ++-------- .../crates/iced-panel-adapter/src/theme.rs | 36 +- 12 files changed, 347 insertions(+), 508 deletions(-) diff --git a/.kilo/plans/1784456713003-iced-gui-visual-polish-plan.md b/.kilo/plans/1784456713003-iced-gui-visual-polish-plan.md index e792e94..b609b43 100644 --- a/.kilo/plans/1784456713003-iced-gui-visual-polish-plan.md +++ b/.kilo/plans/1784456713003-iced-gui-visual-polish-plan.md @@ -1,234 +1,135 @@ -# ICED GUI Visual & Behavioral Polish Plan +# ICED GUI Visual Parity with egui ## Context -This plan addresses the visual and functional issues surfaced during recent ICED GUI testing. The focus is on the Iced build (`hcie-iced-app/crates/hcie-iced-gui/`). Items are grouped by theme and ordered to minimize code conflicts. +The current Iced GUI has the same panels as the egui version but several visual/behavioral gaps remain. The goal is to bring the Iced GUI to egui-level polish. This plan is based on a side-by-side pixel-level comparison of `_images/egui.png` (target) vs the current Iced GUI. + +--- + +## Gap Analysis (egui vs Iced) + +| # | Area | egui | Iced | Gap | +|---|------|------|------|-----| +| 1 | **Menu bar** | File, Edit, Tools, Image, Layer, Filter, Select, View, Window, Help (10 items) | File, Edit, Image, Select, View, Filter, Plugin, Window, More, AI, Help (11 items) | Iced has extra Plugin/AI/More menus cluttering the bar; egui has Tools/Layer menus instead | +| 2 | **Toolbar** | Compact: Brush dropdown + Size + Opacity | Long: Brush Size + Opacity + Hardness + Tolerance + Feather + Blur Radius + brush tip icons | Iced toolbar is too wide/cluttered; egui is minimal | +| 3 | **Brushes grid** | 3-column grid with thumbnail previews | 2-column grid (constrained by narrow dock width) | Iced grid needs more horizontal space | +| 4 | **Filter list** | Categorized + collapsible (Blur ▸, Sharpen ▸, Pixelate ▸, Distort ▸, Stylize ▸, Color & Light ▸) | Flat list with no categories | Iced filters need categories + collapsible groups | +| 5 | **Layer panel** | Clean: thumbnail + name + eye + lock; opacity/blending inline; +Lay/+Vec/+Grp/Flatten toolbar | Shows "opacity: unavailable in hcie-engine-api fx" debug text; missing layer action buttons | Ugly debug text visible; missing quick-action buttons | +| 6 | **Color Palette** | Prominent: large color wheel, FG/BG swatches, Hex input, W/H/G tabs | Same panels exist but ColorPicker is squashed at bottom of right column | ColorPicker needs more vertical space | +| 7 | **Properties panel** | Shows: layer name, Opacity, Visible checkbox, Blend Mode dropdown, Type, Tool Settings (Size/Opacity/Hardness/Flow/Spacing/Color Variant) | Exists but layout differs | Should match egui's grouped layout | +| 8 | **Right column layout** | ColorPalette (top-right) + LayerStyles/History tabs (mid-right) + Properties (bottom-right) | Filters + Layers + ColorPicker + Properties all stacked | Right column overcrowded; Filters shouldn't be on right | +| 9 | **Background** | Consistent dark theme `#2b2b2b` with `#353535` panel bg | Lighter warm theme with inconsistent panel colors | Theme needs refinement for darker, more consistent panels | +| 10 | **Status bar** | Tool name + Mouse coords + Canvas size + Zoom% + Zoom slider | Similar but slightly different styling | Minor; acceptable | --- ## Tasks -### 1. Fix theme-inconsistent blue accent buttons +### 1. Fix layer panel debug text and add action buttons -**Problem:** Dialogs like `New Image` show preset buttons as solid blue (`#3355cc`), which clashes with the dark theme and looks unnatural. +**Problem:** Layer panel shows "opacity: unavailable in hcie-engine-api fx" which is debug text leaking into the UI. Also missing the `+Lay`, `+Vec`, `+Grp`, `Flatten` quick-action buttons that egui has. **Files:** -- `hcie-iced-app/crates/hcie-iced-gui/src/dialogs/new_image.rs` -- `hcie-iced-app/crates/hcie-iced-gui/src/dialogs/mod.rs` (primary/secondary helpers) +- `hcie-iced-app/crates/hcie-iced-gui/src/panels/layers.rs` **Changes:** -- Add a `preset_btn` helper in `dialogs/mod.rs` that uses theme colors (`bg_hover`/`accent` border/text) rather than the default blue. -- Use it for all preset rows. -- Ensure primary button (Create) remains theme-accent but with hover/pressed states. +- Remove or hide any debug/error text about "unavailable in hcie-engine-api fx" +- Add a row of action buttons at the bottom of the layer panel: `+Layer`, `+Group`, `Flatten`, matching egui's `[+Lay] [+Vec] [+Grp] [Flatten]` bar +- Wire buttons to existing messages (`LayerAdd`, `LayerFlatten`, etc.) --- -### 2. Fix color-picker persistence / session recall +### 2. Refactor Filters panel into categorized collapsible groups -**Problem:** The user reports that foreground/background colors are identical on startup and recent colors are not added/persisted. - -**Observed code:** -- `load_colors()` is called in `app.rs:1719` and applied. -- `add_recent_color()` exists and is called by `FgColorChanged`/`SwapColors`. -- `save_colors()` is called when `colors_dirty` is true. - -**Likely issues:** -- `load_colors` returns `PersistedColors` with `#[serde(default)]`, but if any field is absent it becomes `Default`, which is `None`/`empty`. This is fine. -- **Root cause candidates:** - - `Message::ResetColors` exists, but there may be no UI button mapped to it in the color picker panel. - - The saved colors file may not be written because `colors_dirty` is only set on `FgColorChanged`/`BgColorChanged`/`SwapColors`/`ResetColors`. Some popup color changes may not route through those messages. - - When the app starts, `fg_color` and `bg_color` are initialized to the same default (`[0,0,0,255]`) before `load_colors()` runs, and then `load_colors` overrides them. This should be correct, but verify. - -**Changes:** -- Ensure `PopupColorChanged` routed to `Foreground`/`Background` actually triggers `add_recent_color` and sets `colors_dirty`. It does via `FgColorChanged`/`BgColorChanged`. -- Add a **Reset** button in the color picker panel (`color_picker.rs`) that emits `Message::ResetColors`. -- Verify `load_colors()` correctly falls back when file is missing; add debug log if parsing fails. -- Ensure `save_colors()` stores alpha and recent list (already does). -- Remove or explain why `fg_color`/`bg_color` default to the same value in `IcedDocument` / `HcieIcedApp::new` before loading. - ---- - -### 3. Right-click to pick secondary color in color palette panel - -**Problem:** Right-click on palette/recent colors should set the background color. - -**Observed code:** -- `color_picker.rs` already has `.on_right_press(Message::BgColorChanged(...))` on palette grid cells and recent colors. -- However, the wheel canvas only handles right-click inside `ColorWheel::update` and sends `Message::BgColorChanged(color)`. - -**Changes:** -- Verify right-click events are not swallowed by the panel/container. -- If Iced does not deliver right-clicks on `mouse_area` children reliably, wrap the whole color picker content in a `mouse_area` with `on_right_press` to set background color from current foreground? No — user expects picking the swatch under cursor. -- Prefer: keep per-swatch right-click handlers and add a test or visual verification. -- If still broken, investigate whether `mouse_area` in Iced 0.13 requires an explicit background hit; add a transparent container around each swatch. - ---- - -### 4. Enforce color palette panel minimum height so content stays visible - -**Problem:** Color Palette panel can be collapsed too short; its contents become hidden. - -**File:** `hcie-iced-app/crates/hcie-iced-gui/src/dock/sizing.rs` - -**Change:** -- Raise `PaneType::ColorPicker` minimum height from `300.0` to `420.0`. -- Consider also raising `min` width from `180.0` to `200.0`. -- Ensure the color picker `view` uses `scrollable(content)` so overflow remains usable. - ---- - -### 5. Filters panel: parameter sliders should fill ~80% of panel width - -**Problem:** Filter parameter sliders are too narrow and leave large unused space. - -**File:** `hcie-iced-app/crates/hcie-iced-gui/src/panels/filter_params.rs` - -**Changes:** -- Audit all `float_slider` / `int_slider` calls and ensure the underlying `plain_slider` width is `Length::Fill` inside a container with `width(Length::Fill)`. -- In `filters.rs`, wrap the parameter section in a container with `width(Length::Fill)` and ensure `scrollable(params_panel)` does not clamp width. -- Add `width(Length::Fill)` to the `column!` returned by `filter_params::view` if not already present. - ---- - -### 6. Filter operations should integrate with history / undo - -**Problem:** Filter Apply is not undoable via the History panel. - -**File:** `hcie-iced-app/crates/hcie-iced-gui/src/app.rs` - -**Change:** -- Before `engine.apply_filter(...)` in `Message::FilterApply`, call `engine.push_active_layer_snapshot(...)` (or the appropriate engine history API) with a description like `"Apply "`. -- After apply, refresh history cache (`refresh_history`) so the new entry appears in the History panel. -- Ensure undo (`Message::HistoryJump`) and redo paths still work after the snapshot is pushed. - ---- - -### 7. Brush button should reveal Brushes panel if closed - -**Problem:** User says brush button does not open the Brushes panel. - -**Observed code:** `Message::ToolSelected(Tool::Brush)` already calls `self.dock.reopen_pane(PaneType::Brushes)` at `app.rs:2289`. - -**Possible issue:** `reopen_pane` may silently fail when the pane is auto-hidden or floating; or the brush tool can also be selected via slot click without this path. - -**Changes:** -- Verify `ToolSlotClicked`/`SubtoolsToggle` also triggers `reopen_pane(PaneType::Brushes)` when the resulting tool is `Tool::Brush`. -- Add a helper `ensure_brushes_panel()` and call it wherever the active tool becomes `Tool::Brush`. -- Test with panel closed, auto-hidden, and in a different tab group. - ---- - -### 8. Selecting a filter from the Filters menu should reveal the Filters panel - -**Problem:** Filter menu selection should open/raise the Filters panel. - -**Observed code:** `Message::FilterSelect(filter)` already calls `self.dock.reopen_pane(PaneType::Filters)` at `app.rs:4628`. - -**Possible issue:** Same as above — `reopen_pane` may not handle auto-hide/floating states. - -**Changes:** -- Audit `DockState::reopen_pane` to ensure it restores auto-hidden panels, focuses existing floating panels, and brings the pane to front if stacked. -- If `reopen_pane` is already correct, add visual verification; otherwise fix it. - ---- - -### 9. Menu bar vertical padding (+2 px already planned, verify in final screenshot) - -**File:** `hcie-iced-app/crates/hcie-iced-gui/src/panels/title_bar.rs` - -**Already changed:** `MENU_BAR_HEIGHT` 28→30 and menu button widths increased. - -**Remaining:** Confirm no clipping occurs with the new widths; adjust further if "Image" still truncates. - ---- - -### 10. Prevent zero-size document creation (wgpu crash) - -**Problem:** The New Image dialog allows width or height to be set to 0. Creating a 0×N document causes the renderer to request a texture with zero height (`resize_texture 800×600 → 4096×0`), which triggers a wgpu validation error and panic. +**Problem:** egui shows filters organized into expandable categories (Blur ▸, Sharpen ▸, Pixelate ▸, Distort ▸, Stylize ▸, Color & Light ▸). Iced shows a flat unordered list. **Files:** -- `hcie-iced-app/crates/hcie-iced-gui/src/dialogs/new_image.rs` -- `hcie-iced-app/crates/hcie-iced-gui/src/app.rs` (`DialogNewImageCreate` handler) +- `hcie-iced-app/crates/hcie-iced-gui/src/panels/filters.rs` **Changes:** -- Clamp the width/height sliders to a minimum of `1` in `new_image.rs`. -- In `DialogNewImageCreate`, validate `dialog_new_width >= 1 && dialog_new_height >= 1` before creating the engine; if invalid, keep the dialog open and log a warning (or show inline validation text). -- Optionally disable the Create button when either dimension is zero. -- Remove or ignore the `Custom` preset with `width: 0, height: 0` since it is not a valid document size. +- Define filter categories with labels: "Blur", "Sharpen", "Pixelate", "Distort", "Stylize", "Color & Light" +- Each category is a collapsible section (▶/▼ toggle) containing its filter items +- Add a `FilterCategoryExpanded` state field in `HcieIcedApp` to track which categories are open (default: all open) +- Add a `Message::FilterCategoryToggle(String)` message +- When no filter is selected, show "Filter Selection" header as in egui +- Keep existing filter selection + parameter panel behavior --- -### 11. Expose spray particle size / density in the UI +### 3. Simplify the merged toolbar to match egui -**Problem:** Spray tool radius/density settings are present in `tool_settings.rs` but not visible in the merged toolbar or Properties panel. - -**Observed code:** -- `tool_settings.rs:132-179` already has `spray_settings()` with Particle Size and Density sliders. -- `properties.rs:134` groups `Tool::Spray` with brush properties, which only shows Size/Opacity/Hardness. -- `toolbar.rs:95` also groups Spray with Brush and only shows Size/Hardness. - -**Changes:** -- In `properties.rs`, when `active_tool == Tool::Spray`, call a new `spray_properties(...)` helper that includes Size, Particle Size, Density, Opacity, and Hardness. -- In `toolbar.rs`, add a Spray-specific branch that shows Size, Particle Size, and Density (or move the existing spray controls from the Tool Settings panel into the merged toolbar if that is the intended UX). -- Ensure the values are read from `settings.tool_settings.spray_particle_size` / `spray_density` and emit the existing `Message::SprayParticleSizeChanged` / `SprayDensityChanged` messages. - ---- - -### 12. Add a custom-shape slot / panel so user SVGs under `~/.local/share/hcie/shapes/` are usable - -**Problem:** The user copied extra SVG files into `/home/dev-user/.local/share/hcie/shapes/`, but they did not appear in the UI and could not be used. - -**Observed code:** -- `Engine::new()` initializes `shape_catalog` from `dirs::data_local_dir()/hcie/shapes` (matches `~/.local/share/hcie/shapes`) in `hcie-engine-api/src/lib.rs:558-562`. -- `ShapeCatalog::refresh()` in `hcie-engine-api/src/shape_catalog.rs` scans `*.svg` files there and assigns them indices. -- `Tool::CustomShape(idx)` exists and is handled in `app.rs:5945-5966`, using a snapshot of `shape_catalog.all()` during `VectorDrawEnd`. -- `sidebar/mod.rs` `TOOL_SLOTS` does **not** contain a slot for `Tool::CustomShape`. The vector-shapes slot only lists built-in `VectorArrow`, `VectorStar`, etc. -- There is no panel or popup listing the catalog entries for selection. - -**Root cause:** The ICED sidebar has no entry point for `Tool::CustomShape`, so even though the engine loads custom SVGs from `~/.local/share/hcie/shapes/`, the user cannot select or draw them. - -**Decision:** New dock panel with real SVG previews via `iced::widget::svg::Handle::from_memory`. - -**Changes:** -- Add `PaneType::CustomShapes` to `dock/state.rs` with label "Custom Shapes". -- Create `hcie-iced-app/crates/hcie-iced-gui/src/panels/custom_shapes.rs`. - - Accept `shapes: &[ShapeEntry]` and `selected_index: Option`. - - Render each entry as a small card (e.g. 56×56 preview + label below). - - Use `iced::widget::Svg::new(Handle::from_memory(entry.svg.into_bytes()))` for the preview, sized to fit the card. - - On click, emit `Message::ToolSelected(Tool::CustomShape(index))`. - - Highlight the selected card with `colors.accent` border. -- Wire the new panel in `dock/view.rs`. -- Add the panel to `settings.rs` default `visible_panels` / `panel_order` so it appears in the initial dock layout. -- Add a "Refresh" button in the panel that emits `Message::RefreshCustomShapes`; handler calls `engine.shape_catalog.refresh()` and updates `app.cached_custom_shapes`. -- Store a cached `Vec<(String, String)>` of custom shapes in `HcieIcedApp` so the panel can be rebuilt cheaply; refresh it whenever the panel is shown or when Refresh is pressed. -- Ensure `Tool::CustomShape(idx)` out-of-range cases return `None` in `VectorDrawEnd` (already mostly true; add explicit guard). -- Verify that copied SVGs are valid — `ShapeCatalog` silently skips unreadable / non-SVG files. - -### 12b. Custom Shape drawing not triggering on canvas - -**Problem:** Selecting a custom shape in the Custom Shapes panel does not actually draw it on the canvas when dragging. - -**Root cause:** The shared vector-shape drag flow is gated by `is_vector_shape_tool()` and an explicit match arm in the `CanvasPointerPressed` handler. Both locations enumerate the built-in `Tool::Vector*` variants but omit `Tool::CustomShape(_)`, so the custom-shape tool never enters `VectorDrawStart`/`VectorDrawMove`/`VectorDrawEnd` and therefore never reaches the existing `VectorDrawEnd` handler that already knows how to build a `VectorShape::SvgShape` from the catalog. +**Problem:** The Iced toolbar shows too many inline sliders (Size, Opacity, Hardness, Tolerance, Feather, Blur Radius, brush tip icons). egui shows only: Brush dropdown + Size + Opacity. **Files:** -- `hcie-iced-app/crates/hcie-iced-gui/src/app.rs` -- `hcie-iced-app/crates/hcie-iced-gui/src/sidebar/mod.rs` (icon support already present; no change) +- `hcie-iced-app/crates/hcie-iced-gui/src/panels/toolbar.rs` **Changes:** -1. In `app.rs` helper `is_vector_shape_tool()` (around line 1287), add `Tool::CustomShape(_)` to the `matches!` pattern. -2. In `app.rs` `CanvasPointerPressed` handler (around line 2824), add `Tool::CustomShape(_)` to the arm that starts `Message::VectorDrawStart`. -3. Keep `VectorDrawEnd` unchanged; it already snapshots `shape_catalog.all()` (line 5528-5539) and builds a `VectorShape::SvgShape` for `Tool::CustomShape(idx)` with correct bounds. +- Reduce toolbar to: [Tool Icon] [Brush Style Dropdown] [Size: ____] [Opacity: ____] +- Move Hardness, Tolerance, Feather, Blur Radius to the Properties/Tool Settings panel +- Keep brush tip icon row only when a brush tool is active +- Make the toolbar narrower and less cluttered -**Edge cases to consider:** -- Out-of-range `Tool::CustomShape(idx)` already returns `None` from `VectorDrawEnd` and produces no shape. -- If the active layer is a locked vector layer, the shape-flow first switches to a safe non-vector layer before drawing, just like built-in vector shapes. +--- -**Validation:** -1. Build: `cargo check -p hcie-iced-gui` and `cargo test -p hcie-iced-gui`. -2. Run the Iced GUI. -3. Window → Custom Shapes → select a shape. -4. Drag on the canvas → a new vector layer containing the custom SVG shape appears. -5. Undo reverts the inserted shape (history snapshot is pushed by `engine.add_vector_shape`). +### 4. Add Layer Styles panel tab alongside History + +**Problem:** egui has a tabbed right panel with "Layer Styles" / "History" / "AI Assistant" tabs. Iced has no Layer Styles panel. + +**Files:** +- `hcie-iced-app/crates/hcie-iced-gui/src/panels/layer_styles.rs` (new) +- `hcie-iced-app/crates/hcie-iced-gui/src/dock/state.rs` (add `PaneType::LayerStyles`) +- `hcie-iced-app/crates/hcie-iced-gui/src/dock/view.rs` (wire panel) + +**Changes:** +- Create a minimal Layer Styles panel that lists common effects: Drop Shadow, Inner Shadow, Outer Glow, Inner Glow, Bevel & Emboss, Satin, Color Overlay, Gradient Overlay, Pattern Overlay, Stroke +- Each effect has a checkbox to enable and an expandable settings section +- Wire to existing `LayerStyle*` messages +- This is a display-only first pass; detailed per-effect parameter editing can follow + +--- + +### 5. Widen the right column for Color Palette prominence + +**Problem:** Color Palette is squeezed at the bottom of the right column in Iced. In egui, it's prominent with a large color wheel occupying the top-right area. + +**Files:** +- `hcie-iced-app/crates/hcie-iced-gui/src/dock/state.rs` (adjust default layout ratios) +- `hcie-iced-app/crates/hcie-iced-gui/src/dock/sizing.rs` (adjust minimum sizes) + +**Changes:** +- Swap the default right-column layout: put ColorPicker on top (with more space) and Layers below +- Adjust the right_col1 ratio from 0.5 to ~0.6 (more space for ColorPicker) +- Ensure ColorPicker minimum height is at least 400px +- Remove Properties/History from the right column entirely (move to auto-hide or tabbed) + +--- + +### 6. Darken theme panel backgrounds for consistency + +**Problem:** Iced panels use slightly lighter/warmer backgrounds than egui's `#353535`. The overall look feels less polished. + +**Files:** +- `hcie-iced-app/crates/iced-panel-adapter/src/theme.rs` + +**Changes:** +- Darken `bg_panel` in all themes by ~5% to match egui's darker panel backgrounds +- Ensure `border_low` is subtle (~0.2 opacity) for clean panel separation +- Verify `bg_active` provides enough contrast for selected items +- Do NOT change accent colors; only adjust background/border tones + +--- + +### 7. Add document tab bar with proper styling + +**Problem:** egui shows a document tab bar ("Untitled") above the canvas with a clear active tab style. Iced has a tab bar but it needs polish. + +**Files:** +- `hcie-iced-app/crates/hcie-iced-gui/src/dock/view.rs` (document tab bar) + +**Changes:** +- Style the active tab with a bottom accent border (matching egui) +- Add a `+` button for new document at the right end of the tab bar +- Ensure the tab bar uses `bg_panel` background, not `bg_active` --- @@ -236,23 +137,10 @@ This plan addresses the visual and functional issues surfaced during recent ICED 1. `cargo check -p hcie-iced-gui` 2. `cargo test -p hcie-iced-gui` -3. Manual launch and visual checks: - - New Image dialog presets look theme-aware (not bright blue). - - Set width or height to 0 → Create button disabled; preset ignores 0-size values. - - Create documents with valid sizes → no wgpu panic. - - Spray tool selected → Particle Size / Density sliders visible in Tool Settings panel. - - Pick colors, close app, reopen — colors and recent list restored. - - Right-click a palette swatch sets background color. - - Color Palette panel cannot be resized below content height. - - Select Brush tool → Brushes panel appears. - - Filter menu → Filters panel appears and parameter sliders are wide. - - Apply a filter → History panel gains an "Apply …" entry; Undo reverts it. - - Custom SVG files in `~/.local/share/hcie/shapes/` appear in the new Custom Shapes panel and can be drawn. - ---- - -## Open Questions - -- Is there a reference image or exact hex for the desired preset button style? (Current theme tokens: `bg_hover`, `accent`, `text_primary`.) -- Should the New Image dialog enforce a single minimum (e.g. 1×1) or a practical minimum (e.g. 8×8) to avoid 1-pixel-high canvases? -- Should custom shapes be rendered as small SVG previews in the panel, or is a text label list acceptable as a first pass? (Recommended: text list first, SVG preview later.) +3. Screenshot comparison after changes: + - F12 full viewport screenshot + - Compare right-column layout: ColorPicker should be prominent on top + - Filters panel should show categorized collapsible groups + - Layer panel should have no debug text, should show action buttons + - Toolbar should be simplified (3-4 items, not 7+) + - Theme colors should be consistently darker diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/app.rs b/hcie-iced-app/crates/hcie-iced-gui/src/app.rs index 194422c..6429147 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/app.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/app.rs @@ -216,6 +216,8 @@ pub struct HcieIcedApp { pub filter_params: serde_json::Value, /// Filter preview state. pub filter_preview_active: bool, + /// Which filter categories are expanded (name → expanded). + pub filter_categories_expanded: std::collections::HashMap, /// Immutable active-layer pixels used by filter and adjustment previews. preview_baseline: Option, /// Original styles restored when the Layer Style dialog is cancelled. @@ -758,6 +760,7 @@ pub enum Message { FilterPreviewToggle(bool), FilterPreviewApply, FilterPreviewRestore, + FilterCategoryToggle(String), // ── Dialogs ───────────────────────────────────────── DialogOpen(ActiveDialog), @@ -1634,6 +1637,7 @@ impl HcieIcedApp { selected_filter: None, filter_params: serde_json::json!({}), filter_preview_active: false, + filter_categories_expanded: std::collections::HashMap::new(), preview_baseline: None, layer_style_baseline: None, pending_file_operation: None, @@ -4851,6 +4855,14 @@ impl HcieIcedApp { } } + Message::FilterCategoryToggle(category) => { + let entry = self + .filter_categories_expanded + .entry(category) + .or_insert(true); + *entry = !*entry; + } + // ── Dialogs ─────────────────────────────────── Message::DialogOpen(dialog) => { if matches!( diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/dock/sizing.rs b/hcie-iced-app/crates/hcie-iced-gui/src/dock/sizing.rs index 913e155..13351ff 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/dock/sizing.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/dock/sizing.rs @@ -87,7 +87,7 @@ pub fn panel_size_policy(panel: PaneType) -> PaneSizePolicy { width: axis(240.0, 400.0, f32::INFINITY, 30), height: axis(140.0, 320.0, f32::INFINITY, 30), }, - PaneType::Properties | PaneType::Geometry | PaneType::ToolSettings => PaneSizePolicy { + PaneType::Properties | PaneType::Geometry | PaneType::ToolSettings | PaneType::LayerStyles => PaneSizePolicy { width: axis(160.0, 280.0, f32::INFINITY, 25), height: axis(120.0, 280.0, f32::INFINITY, 25), }, diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/dock/state.rs b/hcie-iced-app/crates/hcie-iced-gui/src/dock/state.rs index 64537d7..23c085c 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/dock/state.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/dock/state.rs @@ -5,13 +5,13 @@ ///! The default layout mirrors the egui GUI arrangement with Canvas centered: ///! ///! ```text -///! +--------+------------+-----------+---------+ -///! | | | | Layers | -///! | Brushes| | |---------| -///! |--------| Canvas | ColorBox |Properties| -///! | Filters| |-----------|---------| -///! | | | History | | -///! +--------+------------+-----------+---------+ +///! +--------+------------+-----------+ +///! | | | ColorBox | +///! | Brushes| |-----------| +///! |--------| Canvas | Layers | +///! | Filters| | | +///! | | | | +///! +--------+------------+-----------+ ///! ``` use crate::dock::floating::FloatingDragState; use crate::dock::manager::{ @@ -59,11 +59,13 @@ pub enum PaneType { AiScript, /// Custom SVG shapes panel. CustomShapes, + /// Layer Styles panel. + LayerStyles, } impl PaneType { /// All panel payloads in stable declaration order. - pub const ALL: [Self; 14] = [ + pub const ALL: [Self; 15] = [ Self::Canvas, Self::Layers, Self::History, @@ -78,6 +80,7 @@ impl PaneType { Self::ToolSettings, Self::AiScript, Self::CustomShapes, + Self::LayerStyles, ]; /// Display name for the pane type. @@ -97,6 +100,7 @@ impl PaneType { PaneType::ToolSettings => "Tool Settings", PaneType::AiScript => "AI Script Generator", PaneType::CustomShapes => "Custom Shapes", + PaneType::LayerStyles => "Layer Styles", } } @@ -138,6 +142,7 @@ impl PaneType { Self::LayerDetails => "D", Self::ToolSettings => "T", Self::CustomShapes => "CS", + Self::LayerStyles => "LS", Self::Canvas => "□", } } @@ -218,10 +223,10 @@ mod tests { assert!(dock.is_pane_open(PaneType::Brushes)); assert!(dock.invariants_hold()); - let properties = pane(&dock, PaneType::Properties); - assert!(dock.float_pane(properties, (1280.0, 800.0))); - assert!(dock.auto_hide_floating(PaneType::Properties, DockEdge::Right)); - assert!(dock.auto_hidden[1].contains(&PaneType::Properties)); + let filters = pane(&dock, PaneType::Filters); + assert!(dock.float_pane(filters, (1280.0, 800.0))); + assert!(dock.auto_hide_floating(PaneType::Filters, DockEdge::Right)); + assert!(dock.auto_hidden[1].contains(&PaneType::Filters)); assert!(dock.invariants_hold()); } @@ -309,13 +314,13 @@ impl DockState { /// /// Dock layout (without toolbox), left → right: /// ```text - /// +------------+-----------+-----------+-----------+ - /// | Brushes | | Layers | | - /// |------------| Canvas |-----------|Properties | - /// | Filters | | ColorBox |-----------| - /// | | |-----------| History | - /// +------------+-----------+-----------+-----------+ - /// ~14% ~66% ~10% ~10% + /// +------------+-----------+-----------+ + /// | Brushes | | ColorBox | + /// |------------| Canvas |-----------| + /// | Filters | | Layers | + /// | | | | + /// +------------+-----------+-----------+ + /// ~14% ~66% ~20% /// ``` /// /// Axis semantics in iced PaneGrid: @@ -330,41 +335,23 @@ impl DockState { b: Box::new(Configuration::Pane(PaneType::Filters)), }; - // Right column 1: Layers (top) / ColorBox (bottom) — horizontal split - let right_col1 = Configuration::Split { + // Right column: ColorPicker (top, prominent) / Layers (bottom) + let right_column = Configuration::Split { axis: iced::widget::pane_grid::Axis::Horizontal, - ratio: 0.5, - a: Box::new(Configuration::Pane(PaneType::Layers)), - b: Box::new(Configuration::Pane(PaneType::ColorPicker)), + ratio: 0.6, + a: Box::new(Configuration::Pane(PaneType::ColorPicker)), + b: Box::new(Configuration::Pane(PaneType::Layers)), }; - // Right column 2: Properties (top) / History (bottom) — horizontal split - let right_col2 = Configuration::Split { - axis: iced::widget::pane_grid::Axis::Horizontal, - ratio: 0.5, - a: Box::new(Configuration::Pane(PaneType::Properties)), - b: Box::new(Configuration::Pane(PaneType::History)), - }; - - // Right side: right_col1 (left) / right_col2 (right) — vertical split - let right_side = Configuration::Split { - axis: iced::widget::pane_grid::Axis::Vertical, - ratio: 0.5, - a: Box::new(right_col1), - b: Box::new(right_col2), - }; - - // Center + right: Canvas (left) / right_side (right) — vertical split - // Canvas gets ~66% — right panels need room for layers + properties + // Center + right: Canvas (left) / right_column (right) — vertical split let center_right = Configuration::Split { axis: iced::widget::pane_grid::Axis::Vertical, ratio: 0.66, a: Box::new(Configuration::Pane(PaneType::Canvas)), - b: Box::new(right_side), + b: Box::new(right_column), }; // Root: left_column (left) / center_right (right) — vertical split - // Left column gets ~14% of total width. let root_config = Configuration::Split { axis: iced::widget::pane_grid::Axis::Vertical, ratio: 0.14, @@ -862,6 +849,7 @@ impl DockState { | PaneType::Properties | PaneType::History | PaneType::LayerDetails + | PaneType::LayerStyles | PaneType::AiScript => iced::widget::pane_grid::Axis::Horizontal, _ => iced::widget::pane_grid::Axis::Vertical, }; diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/dock/view.rs b/hcie-iced-app/crates/hcie-iced-gui/src/dock/view.rs index 5370cf5..d0089cf 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/dock/view.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/dock/view.rs @@ -192,6 +192,7 @@ pub fn panel_body<'a>( .engine .active_layer_is_editable(), colors, + &app.filter_categories_expanded, ), PaneType::ColorPicker => crate::color_picker::view( &app.fg_color, @@ -302,6 +303,12 @@ pub fn panel_body<'a>( colors, ) } + PaneType::LayerStyles => { + let doc = &app.documents[app.active_doc]; + let active_id = doc.engine.active_layer_id(); + let layer_styles = doc.engine.get_layer_styles(active_id); + crate::panels::layer_styles::panel_view(layer_styles, colors) + } } } @@ -380,14 +387,14 @@ fn document_tab_bar<'a>( if active { iced::widget::button::Style { background: Some(iced::Background::Color(c.bg_active)), - border: iced::Border::default().color(c.accent).width(1), + border: iced::Border::default().color(c.accent).width(1.0), text_color: c.accent, ..Default::default() } } else { iced::widget::button::Style { background: Some(iced::Background::Color(c.bg_panel)), - border: iced::Border::default().color(c.border_low).width(1), + border: iced::Border::default().color(c.border_low).width(1.0), text_color: c.text_primary, ..Default::default() } @@ -397,14 +404,14 @@ fn document_tab_bar<'a>( if active { iced::widget::button::Style { background: Some(iced::Background::Color(c.bg_active)), - border: iced::Border::default().color(c.accent).width(1), + border: iced::Border::default().color(c.accent).width(1.0), text_color: c.accent, ..Default::default() } } else { iced::widget::button::Style { background: Some(iced::Background::Color(c.bg_hover)), - border: iced::Border::default().color(c.border_high).width(1), + border: iced::Border::default().color(c.border_high).width(1.0), text_color: c.text_primary, ..Default::default() } @@ -412,7 +419,7 @@ fn document_tab_bar<'a>( } _ => iced::widget::button::Style { background: Some(iced::Background::Color(c.bg_active)), - border: iced::Border::default().color(c.accent).width(1), + border: iced::Border::default().color(c.accent).width(1.0), text_color: c.accent, ..Default::default() }, @@ -470,6 +477,7 @@ fn pane_type_icon(pane_type: PaneType) -> Option<&'static str> { PaneType::ToolSettings => Some("icons/panel-settings.svg"), PaneType::AiScript => Some("icons/panel-ai.svg"), PaneType::CustomShapes => Some("icons/star.svg"), + PaneType::LayerStyles => Some("icons/panel-settings.svg"), } } diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/panels/filters.rs b/hcie-iced-app/crates/hcie-iced-gui/src/panels/filters.rs index afea9c4..e5cfeb5 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/panels/filters.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/panels/filters.rs @@ -156,54 +156,84 @@ const FILTER_CATEGORIES: &[FilterCategory] = &[ /// Build the filters panel. /// -/// Shows the filter category list on top, and when a filter is selected, -/// displays its parameter editing panel below the list. The preview checkbox, -/// Apply, and Reset buttons are always at the bottom. +/// Shows the filter category list on top with collapsible groups, and when a +/// filter is selected, displays its parameter editing panel below the list. +/// The preview checkbox, Apply, and Reset buttons are always at the bottom. pub fn view( selected_filter: Option, filter_params: &serde_json::Value, filter_preview_active: bool, layer_editable: bool, colors: ThemeColors, + categories_expanded: &std::collections::HashMap, ) -> Element<'static, Message> { let mut filter_list = column![].spacing(2); for category in FILTER_CATEGORIES { - let cat_name = text(category.name).size(12); + let is_expanded = *categories_expanded.get(category.name).unwrap_or(&true); + let arrow = if is_expanded { "\u{25BC}" } else { "\u{25B6}" }; - let mut cat_filters = column![].spacing(1).padding(iced::Padding::ZERO.left(12)); + let cat_header = button( + row![ + text(format!("{} {}", arrow, category.name)).size(12), + ] + .spacing(4) + .align_y(iced::Alignment::Center), + ) + .on_press(Message::FilterCategoryToggle(category.name.to_string())) + .width(Length::Fill) + .padding([4, 6]) + .style(move |_theme, status| match status { + iced::widget::button::Status::Hovered => iced::widget::button::Style { + background: Some(iced::Background::Color(colors.bg_hover)), + text_color: colors.text_primary, + border: iced::Border::default(), + ..Default::default() + }, + _ => iced::widget::button::Style { + background: Some(iced::Background::Color(iced::Color::TRANSPARENT)), + text_color: colors.text_primary, + border: iced::Border::default(), + ..Default::default() + }, + }); - for entry in category.filters { - let is_selected = selected_filter == Some(entry.filter_type); - let filter_text = text(entry.name).size(11); - let filter_text = if is_selected { - filter_text.style(move |_theme: &iced::Theme| iced::widget::text::Style { - color: Some(colors.accent), - }) - } else { - filter_text - }; + filter_list = filter_list.push(cat_header); - let item_style = if is_selected { - styles::active_item_bg(colors) - } else { - styles::inactive_item_bg(colors) - }; + if is_expanded { + let mut cat_filters = column![].spacing(1).padding(iced::Padding::ZERO.left(12)); - let item = container(filter_text) - .width(Length::Fill) - .padding([3, 6]) - .style(move |_theme| item_style.clone()); + for entry in category.filters { + let is_selected = selected_filter == Some(entry.filter_type); + let filter_text = text(entry.name).size(11); + let filter_text = if is_selected { + filter_text.style(move |_theme: &iced::Theme| iced::widget::text::Style { + color: Some(colors.accent), + }) + } else { + filter_text + }; - let filter_type = entry.filter_type; - let clickable = - iced::widget::mouse_area(item).on_press(Message::FilterSelect(filter_type)); + let item_style = if is_selected { + styles::active_item_bg(colors) + } else { + styles::inactive_item_bg(colors) + }; - cat_filters = cat_filters.push(clickable); + let item = container(filter_text) + .width(Length::Fill) + .padding([3, 6]) + .style(move |_theme| item_style.clone()); + + let filter_type = entry.filter_type; + let clickable = + iced::widget::mouse_area(item).on_press(Message::FilterSelect(filter_type)); + + cat_filters = cat_filters.push(clickable); + } + + filter_list = filter_list.push(cat_filters); } - - filter_list = filter_list.push(cat_name); - filter_list = filter_list.push(cat_filters); } let apply_btn = if layer_editable { diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/panels/layer_styles.rs b/hcie-iced-app/crates/hcie-iced-gui/src/panels/layer_styles.rs index b3831ff..f8b3458 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/panels/layer_styles.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/panels/layer_styles.rs @@ -875,3 +875,66 @@ fn slider_row<'a>( .align_y(iced::Alignment::Center) .into() } + +/// Docked Layer Styles panel — lists effects with enable checkboxes. +/// +/// **Purpose:** A simplified, always-visible panel showing all layer effects +/// with toggle checkboxes. This is the docked counterpart to the floating +/// `view()` dialog; it provides quick enable/disable without full parameter editing. +pub fn panel_view<'a>( + styles_list: Vec, + colors: ThemeColors, +) -> Element<'a, Message> { + let mut effects_list = column![].spacing(1); + + for (i, &(disc, label, color_arr)) in STYLE_DEFS.iter().enumerate() { + let enabled = style_enabled(disc, &styles_list); + let badge_color = iced::Color::from_rgb(color_arr[0], color_arr[1], color_arr[2]); + + let toggle = iced::widget::checkbox("", enabled) + .on_toggle(move |_| Message::LayerStyleToggle(i as u32)) + .size(11); + + let label_text = text(label) + .size(11) + .width(Length::Fill) + .style(move |_theme: &iced::Theme| iced::widget::text::Style { + color: Some(if enabled { + badge_color + } else { + colors.text_secondary + }), + }); + + let item_row = row![toggle, label_text] + .spacing(4) + .align_y(iced::Alignment::Center); + + let item_style = if enabled { + styles::active_item_bg(colors) + } else { + styles::inactive_item_bg(colors) + }; + + let item = container(item_row) + .width(Length::Fill) + .padding([3, 6]) + .style(move |_theme| item_style.clone()); + + effects_list = effects_list.push(item); + } + + let panel = column![ + text("Layer Styles").size(11), + horizontal_rule(1), + scrollable(effects_list).height(Length::Fill), + ] + .spacing(2) + .padding(4); + + container(panel) + .width(Length::Fill) + .height(Length::Fill) + .style(move |_theme| styles::panel_background(colors)) + .into() +} diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/panels/layers.rs b/hcie-iced-app/crates/hcie-iced-gui/src/panels/layers.rs index ecd1936..f04b34e 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/panels/layers.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/panels/layers.rs @@ -212,19 +212,6 @@ pub fn view<'a>( .unwrap_or(BlendMode::Normal); let active_opacity = active_info.map(|l| l.opacity).unwrap_or(1.0); let active_locked = active_info.map(|l| l.locked).unwrap_or(false); - let active_metadata = active_info - .map(|layer| { - format!( - "{:?} {}x{} parent {}", - layer.layer_type, - layer.width, - layer.height, - layer - .parent_id - .map_or_else(|| "root".to_string(), |id| id.to_string()) - ) - }) - .unwrap_or_else(|| "No active layer".to_string()); let blend_list = pick_list( BLEND_MODE_ITEMS, @@ -272,7 +259,6 @@ pub fn view<'a>( row![lock_btn, fx_btn] .spacing(8) .align_y(iced::Alignment::Center), - text(active_metadata).size(BODY), ] .spacing(2); @@ -468,9 +454,6 @@ pub fn view<'a>( .on_press(Message::LayerAddGroup) .padding([5, 8]) .style(move |_theme, _status| flat_btn_style(colors)); - let duplicate_btn = button(text("Duplicate").size(BODY)).padding([5, 8]); - let mask_btn = button(text("Mask").size(BODY)).padding([5, 8]); - let rasterize_btn = button(text("Rasterize").size(BODY)).padding([5, 8]); let flatten_btn = button(text("Flatten").size(BODY)) .on_press(Message::LayerFlatten) .padding([5, 8]) @@ -492,6 +475,9 @@ pub fn view<'a>( .on_press(Message::OpenLayerStyleDialog) .padding([3, 6]) .style(move |_theme, _status| flat_btn_style(colors)); + let duplicate_btn = button(text("Duplicate").size(BODY)).padding([5, 8]); + let mask_btn = button(text("Mask").size(BODY)).padding([5, 8]); + let rasterize_btn = button(text("Rasterize").size(BODY)).padding([5, 8]); let toolbar = column![ text("Unavailable: duplicate, mask, rasterize").size(BODY), row![duplicate_btn, mask_btn] diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/panels/menus.rs b/hcie-iced-app/crates/hcie-iced-gui/src/panels/menus.rs index f5d3585..e2f45f7 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/panels/menus.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/panels/menus.rs @@ -704,6 +704,10 @@ fn all_menus(recent_files: &[crate::app::RecentFileEntry]) -> Vec { "Custom Shapes", MenuCommand::TogglePane(PaneType::CustomShapes), ), + MenuItem::command( + "Layer Styles", + MenuCommand::TogglePane(PaneType::LayerStyles), + ), MenuItem::separator(), MenuItem::command( "Theme: Photopea", @@ -798,7 +802,7 @@ pub fn dropdown_overlay( let enabled = item.enabled; // Check if this is a Window menu item that should show a checkmark - let is_window_panel = menu_idx == 8 && item_idx <= 11; + let is_window_panel = menu_idx == 8 && item_idx <= 12; let is_checked = if is_window_panel { let pane_type = match item_idx { 0 => Some(PaneType::Layers), @@ -813,6 +817,7 @@ pub fn dropdown_overlay( 9 => Some(PaneType::LayerDetails), 10 => Some(PaneType::AiScript), 11 => Some(PaneType::CustomShapes), + 12 => Some(PaneType::LayerStyles), _ => None, }; pane_type @@ -1254,6 +1259,7 @@ mod tests { "Window/Layer Details", "Window/AI Script Generator", "Window/Custom Shapes", + "Window/Layer Styles", "Window/Theme: Photopea", "Window/Theme: Photoshop", "Window/Theme: ProDark (Monokai)", diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/panels/styles.rs b/hcie-iced-app/crates/hcie-iced-gui/src/panels/styles.rs index ead858d..0db56bd 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/panels/styles.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/panels/styles.rs @@ -146,30 +146,30 @@ pub fn hover_item_bg(colors: ThemeColors) -> iced::widget::container::Style { } } -/// Menu bar background — dark like Photopea's menu bar. +/// Menu bar background — panel color for visual consistency. #[allow(dead_code)] pub fn menubar_background(colors: ThemeColors) -> iced::widget::container::Style { iced::widget::container::Style { - background: Some(iced::Background::Color(colors.bg_app)), + background: Some(iced::Background::Color(colors.bg_panel)), border: iced::Border::default().color(colors.border_low).width(1), ..Default::default() } } -/// Title bar background — dark like Photopea's title bar. +/// Title bar background — panel color for visual consistency. #[allow(dead_code)] pub fn titlebar_background(colors: ThemeColors) -> iced::widget::container::Style { iced::widget::container::Style { - background: Some(iced::Background::Color(colors.bg_app)), + background: Some(iced::Background::Color(colors.bg_panel)), border: iced::Border::default().color(colors.border_low).width(1), ..Default::default() } } -/// Status bar background — dark like Photopea's status bar. +/// Status bar background — panel color for consistency. pub fn statusbar_background(colors: ThemeColors) -> iced::widget::container::Style { iced::widget::container::Style { - background: Some(iced::Background::Color(colors.bg_app)), + background: Some(iced::Background::Color(colors.bg_panel)), border: iced::Border::default().color(colors.border_low).width(1), ..Default::default() } @@ -194,10 +194,10 @@ pub fn dropdown_background(colors: ThemeColors) -> iced::widget::container::Styl } } -/// Document tab bar background — dark like Photopea's tab bar. +/// Document tab bar background — panel color for visual consistency. pub fn tabbar_background(colors: ThemeColors) -> iced::widget::container::Style { iced::widget::container::Style { - background: Some(iced::Background::Color(colors.bg_app)), + background: Some(iced::Background::Color(colors.bg_panel)), border: iced::Border::default().color(colors.border_low).width(1), ..Default::default() } diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/panels/toolbar.rs b/hcie-iced-app/crates/hcie-iced-gui/src/panels/toolbar.rs index 7fd1328..d40ccfc 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/panels/toolbar.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/panels/toolbar.rs @@ -8,14 +8,13 @@ use crate::app::Message; use crate::panels::menus::MenuCommand; use crate::panels::styles; use crate::theme::ThemeColors; -use hcie_engine_api::Tool; use iced::widget::{button, container, row, slider, svg, text}; use iced::{Element, Length}; -/// Build the Photopea-style toolbar: SVG icon buttons + blend mode/opacity/flow in one row. +/// Build the compact toolbar: SVG icon buttons + brush size + opacity in one row. pub fn view<'a>( tool_state: &'a crate::app::ToolState, - settings: &'a crate::settings::AppSettings, + _settings: &'a crate::settings::AppSettings, colors: ThemeColors, ) -> Element<'a, Message> { let c = colors; @@ -46,8 +45,20 @@ pub fn view<'a>( .spacing(0) .align_y(iced::Alignment::Center); - // ── Blend Mode, Opacity, Flow, Smooth (Photopea-style) ── - let blend_value = compact_value("Normal".to_string(), "Blend mode: Normal", c); + // ── Brush size + opacity (compact, matching egui) ── + let size_value = compact_value( + format!("S {:.0}px", tool_state.brush_size), + "Brush size", + c, + ); + let size_sl = slider( + 1.0..=200.0, + tool_state.brush_size, + Message::BrushSizeChanged, + ) + .step(1.0) + .width(Length::Fixed(64.0)); + let opacity_value = compact_value( format!("O {:.0}%", tool_state.brush_opacity * 100.0), "Brush opacity", @@ -59,144 +70,17 @@ pub fn view<'a>( Message::BrushOpacityChanged, ) .step(0.01) - .width(Length::Fixed(52.0)); + .width(Length::Fixed(64.0)); - let flow_value = compact_value( - format!("F {:.0}%", settings.tool_settings.brush_flow * 100.0), - "Brush flow", - c, - ); - let flow_sl = slider( - 0.0..=1.0, - settings.tool_settings.brush_flow, - Message::BrushFlowChanged, - ) - .step(0.01) - .width(Length::Fixed(52.0)); - - let smooth_value = compact_value("S 0%".to_string(), "Stroke smoothing", c); - - let tool_options = row![ - blend_value, - sep(c), - opacity_value, - opacity_sl, - sep(c), - flow_value, - flow_sl, - sep(c), - smooth_value, - ] - .spacing(4) - .align_y(iced::Alignment::Center); - - // ── Tool-specific options (right side) ── - let tool_options_extra = match tool_state.active_tool { - Tool::Brush | Tool::Eraser | Tool::Pen => { - let sz = compact_value(format!("{:.0}px", tool_state.brush_size), "Brush size", c); - let sz_sl = slider( - 1.0..=200.0, - tool_state.brush_size, - Message::BrushSizeChanged, - ) - .step(1.0) - .width(Length::Fixed(58.0)); - let ha = compact_value( - format!("H {:.0}%", tool_state.brush_hardness * 100.0), - "Brush hardness", - c, - ); - let ha_sl = slider( - 0.0..=1.0, - tool_state.brush_hardness, - Message::BrushHardnessChanged, - ) - .step(0.01) - .width(Length::Fixed(58.0)); - row![sz, sz_sl, ha, ha_sl] - .spacing(4) - .align_y(iced::Alignment::Center) - } - Tool::Spray => { - let sz = compact_value(format!("{:.0}px", tool_state.brush_size), "Spray size", c); - let sz_sl = slider(1.0..=200.0, tool_state.brush_size, Message::BrushSizeChanged) - .step(1.0) - .width(Length::Fixed(50.0)); - let ps = compact_value( - format!("P {:.1}px", settings.tool_settings.spray_particle_size), - "Particle size", - c, - ); - let ps_sl = slider( - 1.0..=20.0, - settings.tool_settings.spray_particle_size, - Message::SprayParticleSizeChanged, - ) - .step(0.1) - .width(Length::Fixed(50.0)); - let ds = compact_value( - format!("D {:.0}", settings.tool_settings.spray_density), - "Particle density", - c, - ); - let ds_sl = slider( - 10.0..=1000.0, - settings.tool_settings.spray_density as f32, - |v| Message::SprayDensityChanged(v as u32), - ) - .step(1.0) - .width(Length::Fixed(50.0)); - row![sz, sz_sl, ps, ps_sl, ds, ds_sl] - .spacing(4) - .align_y(iced::Alignment::Center) - } - Tool::MagicWand => { - let tol = settings.tool_settings.magic_wand_tolerance; - row![ - compact_value(format!("T {:.0}", tol), "Selection tolerance", c), - slider(0.0..=255.0, tol, |v| Message::SelectionToleranceChanged( - v as u8 - )) - .step(1.0) - .width(Length::Fixed(64.0)), - ] - .spacing(6) - .align_y(iced::Alignment::Center) - } - Tool::Select | Tool::Lasso => { - let feather = settings.tool_settings.select_feather; - row![ - compact_value(format!("F {:.0}px", feather), "Selection feather", c), - slider(0.0..=250.0, feather, Message::SelectionFeatherChanged) - .step(1.0) - .width(Length::Fixed(64.0)), - ] - .spacing(6) - .align_y(iced::Alignment::Center) - } - Tool::Move => row![compact_value("Auto".to_string(), "Auto-select layer", c)] - .spacing(4) - .align_y(iced::Alignment::Center), - Tool::VectorLine | Tool::VectorRect | Tool::VectorCircle => { - let stroke = settings.tool_settings.vector_stroke_width; - row![ - compact_value(format!("{:.1}px", stroke), "Vector stroke width", c), - slider(0.0..=50.0, stroke, Message::VectorStrokeChanged) - .step(0.5) - .width(Length::Fixed(58.0)), - ] - .spacing(4) - .align_y(iced::Alignment::Center) - } - _ => row![].spacing(0), - }; + let tool_controls = row![size_value, size_sl, sep(c), opacity_value, opacity_sl] + .spacing(4) + .align_y(iced::Alignment::Center); let bar = row![ actions, sep(c), - tool_options, + tool_controls, iced::widget::Space::with_width(Length::Fill), - tool_options_extra, ] .spacing(4) .align_y(iced::Alignment::Center) @@ -264,32 +148,6 @@ fn svg_btn( ) } -#[allow(dead_code)] -fn small_btn(label: &str, colors: ThemeColors) -> Element<'static, Message> { - let c = colors; - let l = label.to_string(); - button(text(l).size(10)) - .on_press(Message::NoOp) - .padding([2, 6]) - .style( - move |_theme: &iced::Theme, status: iced::widget::button::Status| match status { - iced::widget::button::Status::Hovered => iced::widget::button::Style { - background: Some(iced::Background::Color(c.bg_hover)), - text_color: c.text_primary, - border: iced::Border::default().color(c.border_high).width(1), - ..Default::default() - }, - _ => iced::widget::button::Style { - background: Some(iced::Background::Color(c.bg_panel)), - text_color: c.text_primary, - border: iced::Border::default().color(c.border_low).width(1), - ..Default::default() - }, - }, - ) - .into() -} - fn sep(colors: ThemeColors) -> Element<'static, Message> { container(text("").height(16).width(1)) .padding([0, 2]) diff --git a/hcie-iced-app/crates/iced-panel-adapter/src/theme.rs b/hcie-iced-app/crates/iced-panel-adapter/src/theme.rs index 8a0fd1d..b352927 100644 --- a/hcie-iced-app/crates/iced-panel-adapter/src/theme.rs +++ b/hcie-iced-app/crates/iced-panel-adapter/src/theme.rs @@ -82,16 +82,16 @@ impl ThemeColors { // ── Photopea (Default) ───────────────────────────────────── // Exact colors from 93 Photopea screenshots. ThemePreset::Photopea => Self { - bg_app: Color::from_rgb(0.118, 0.118, 0.118), // #1e1e1e - canvas - bg_panel: Color::from_rgb(0.165, 0.165, 0.165), // #2a2a2a - panels - bg_hover: Color::from_rgb(0.227, 0.227, 0.227), // #3a3a3a - hover - bg_active: Color::from_rgb(0.200, 0.200, 0.200), // #333333 - active tab + bg_app: Color::from_rgb(0.110, 0.110, 0.110), // #1c1c1c - canvas (darker) + bg_panel: Color::from_rgb(0.153, 0.153, 0.153), // #272727 - panels (darker) + bg_hover: Color::from_rgb(0.220, 0.220, 0.220), // #383838 - hover + bg_active: Color::from_rgb(0.190, 0.190, 0.190), // #303030 - active tab (darker) accent: Color::from_rgb(0.290, 0.620, 1.000), // #4a9eff - selections accent_hover: Color::from_rgb(0.350, 0.680, 1.000), accent_green: Color::from_rgb(0.298, 0.686, 0.314), // #4caf50 - tool active danger: Color::from_rgb(0.906, 0.298, 0.235), // #e74c3c - account btn - border_low: Color::from_rgb(0.200, 0.200, 0.200), // #333333 - dividers - border_high: Color::from_rgb(0.267, 0.267, 0.267), // #444444 - borders + border_low: Color::from_rgb(0.180, 0.180, 0.180), // #2e2e2e - dividers (darker) + border_high: Color::from_rgb(0.250, 0.250, 0.250), // #404040 - borders (darker) text_primary: Color::from_rgb(0.878, 0.878, 0.878), // #e0e0e0 - primary text_secondary: Color::from_rgb(0.600, 0.600, 0.600), // #999999 - muted text_disabled: Color::from_rgb(0.400, 0.400, 0.400), // #666666 - disabled @@ -103,10 +103,10 @@ impl ThemeColors { }, // ── Photoshop (Legacy) ───────────────────────────────────── ThemePreset::Photoshop => Self { - bg_app: Color::from_rgb(0.110, 0.110, 0.118), - bg_panel: Color::from_rgb(0.196, 0.196, 0.212), - bg_hover: Color::from_rgb(0.243, 0.243, 0.259), - bg_active: Color::from_rgb(0.227, 0.227, 0.243), + bg_app: Color::from_rgb(0.100, 0.100, 0.108), + bg_panel: Color::from_rgb(0.178, 0.178, 0.194), + bg_hover: Color::from_rgb(0.230, 0.230, 0.246), + bg_active: Color::from_rgb(0.210, 0.210, 0.226), accent: Color::from_rgb(0.220, 0.471, 0.863), accent_hover: Color::from_rgb(0.260, 0.520, 0.900), accent_green: Color::from_rgb(0.298, 0.686, 0.314), @@ -124,10 +124,10 @@ impl ThemeColors { }, // ── Monokai ──────────────────────────────────────────────── ThemePreset::ProDark => Self { - bg_app: Color::from_rgb(0.086, 0.082, 0.071), - bg_panel: Color::from_rgb(0.153, 0.157, 0.133), - bg_hover: Color::from_rgb(0.212, 0.220, 0.188), - bg_active: Color::from_rgb(0.180, 0.184, 0.160), + bg_app: Color::from_rgb(0.078, 0.074, 0.063), + bg_panel: Color::from_rgb(0.137, 0.141, 0.118), + bg_hover: Color::from_rgb(0.200, 0.208, 0.176), + bg_active: Color::from_rgb(0.164, 0.168, 0.145), accent: Color::from_rgb(0.902, 0.588, 0.196), accent_hover: Color::from_rgb(0.940, 0.640, 0.260), accent_green: Color::from_rgb(0.298, 0.686, 0.314), @@ -145,10 +145,10 @@ impl ThemeColors { }, // ── Dracula ──────────────────────────────────────────────── ThemePreset::Amoled => Self { - bg_app: Color::from_rgb(0.094, 0.086, 0.133), - bg_panel: Color::from_rgb(0.157, 0.165, 0.212), - bg_hover: Color::from_rgb(0.220, 0.227, 0.290), - bg_active: Color::from_rgb(0.184, 0.192, 0.243), + bg_app: Color::from_rgb(0.084, 0.076, 0.118), + bg_panel: Color::from_rgb(0.141, 0.149, 0.194), + bg_hover: Color::from_rgb(0.208, 0.215, 0.276), + bg_active: Color::from_rgb(0.170, 0.178, 0.226), accent: Color::from_rgb(1.000, 0.475, 0.776), accent_hover: Color::from_rgb(1.000, 0.530, 0.820), accent_green: Color::from_rgb(0.298, 0.686, 0.314),