- Improved layout and styling in filter_params.rs for better visual consistency. - Enhanced filters.rs to ensure scrollable parameters fill the available width. - Added a new Custom Shapes panel to display user-defined SVG shapes. - Updated menus.rs to include a new Custom Shapes menu item and adjusted related logic. - Modified properties.rs to support additional spray tool properties (particle size and density). - Adjusted title_bar.rs for improved menu button dimensions and layout. - Enhanced toolbar.rs to include spray tool options in the toolbar. - Fixed color persistence issues in settings.rs to ensure colors are saved and loaded correctly. - Updated plain_slider.rs to ensure sliders utilize the full width of their containers. - Created a visual polish plan to address various UI/UX issues and improve overall usability.
15 KiB
ICED GUI Visual & Behavioral Polish Plan
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.
Tasks
1. Fix theme-inconsistent blue accent buttons
Problem: Dialogs like New Image show preset buttons as solid blue (#3355cc), which clashes with the dark theme and looks unnatural.
Files:
hcie-iced-app/crates/hcie-iced-gui/src/dialogs/new_image.rshcie-iced-app/crates/hcie-iced-gui/src/dialogs/mod.rs(primary/secondary helpers)
Changes:
- Add a
preset_btnhelper indialogs/mod.rsthat uses theme colors (bg_hover/accentborder/text) rather than the default blue. - Use it for all preset rows.
- Ensure primary button (Create) remains theme-accent but with hover/pressed states.
2. Fix color-picker persistence / session recall
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 inapp.rs:1719and applied.add_recent_color()exists and is called byFgColorChanged/SwapColors.save_colors()is called whencolors_dirtyis true.
Likely issues:
load_colorsreturnsPersistedColorswith#[serde(default)], but if any field is absent it becomesDefault, which isNone/empty. This is fine.- Root cause candidates:
Message::ResetColorsexists, 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_dirtyis only set onFgColorChanged/BgColorChanged/SwapColors/ResetColors. Some popup color changes may not route through those messages. - When the app starts,
fg_colorandbg_colorare initialized to the same default ([0,0,0,255]) beforeload_colors()runs, and thenload_colorsoverrides them. This should be correct, but verify.
Changes:
- Ensure
PopupColorChangedrouted toForeground/Backgroundactually triggersadd_recent_colorand setscolors_dirty. It does viaFgColorChanged/BgColorChanged. - Add a Reset button in the color picker panel (
color_picker.rs) that emitsMessage::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_colordefault to the same value inIcedDocument/HcieIcedApp::newbefore 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.rsalready has.on_right_press(Message::BgColorChanged(...))on palette grid cells and recent colors.- However, the wheel canvas only handles right-click inside
ColorWheel::updateand sendsMessage::BgColorChanged(color).
Changes:
- Verify right-click events are not swallowed by the panel/container.
- If Iced does not deliver right-clicks on
mouse_areachildren reliably, wrap the whole color picker content in amouse_areawithon_right_pressto 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_areain 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::ColorPickerminimum height from300.0to420.0. - Consider also raising
minwidth from180.0to200.0. - Ensure the color picker
viewusesscrollable(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_slidercalls and ensure the underlyingplain_sliderwidth isLength::Fillinside a container withwidth(Length::Fill). - In
filters.rs, wrap the parameter section in a container withwidth(Length::Fill)and ensurescrollable(params_panel)does not clamp width. - Add
width(Length::Fill)to thecolumn!returned byfilter_params::viewif 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(...)inMessage::FilterApply, callengine.push_active_layer_snapshot(...)(or the appropriate engine history API) with a description like"Apply <filter_name>". - 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/SubtoolsTogglealso triggersreopen_pane(PaneType::Brushes)when the resulting tool isTool::Brush. - Add a helper
ensure_brushes_panel()and call it wherever the active tool becomesTool::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_paneto ensure it restores auto-hidden panels, focuses existing floating panels, and brings the pane to front if stacked. - If
reopen_paneis 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.
Files:
hcie-iced-app/crates/hcie-iced-gui/src/dialogs/new_image.rshcie-iced-app/crates/hcie-iced-gui/src/app.rs(DialogNewImageCreatehandler)
Changes:
- Clamp the width/height sliders to a minimum of
1innew_image.rs. - In
DialogNewImageCreate, validatedialog_new_width >= 1 && dialog_new_height >= 1before 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
Custompreset withwidth: 0, height: 0since it is not a valid document size.
11. Expose spray particle size / density in the UI
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-179already hasspray_settings()with Particle Size and Density sliders.properties.rs:134groupsTool::Spraywith brush properties, which only shows Size/Opacity/Hardness.toolbar.rs:95also groups Spray with Brush and only shows Size/Hardness.
Changes:
- In
properties.rs, whenactive_tool == Tool::Spray, call a newspray_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_densityand emit the existingMessage::SprayParticleSizeChanged/SprayDensityChangedmessages.
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()initializesshape_catalogfromdirs::data_local_dir()/hcie/shapes(matches~/.local/share/hcie/shapes) inhcie-engine-api/src/lib.rs:558-562.ShapeCatalog::refresh()inhcie-engine-api/src/shape_catalog.rsscans*.svgfiles there and assigns them indices.Tool::CustomShape(idx)exists and is handled inapp.rs:5945-5966, using a snapshot ofshape_catalog.all()duringVectorDrawEnd.sidebar/mod.rsTOOL_SLOTSdoes not contain a slot forTool::CustomShape. The vector-shapes slot only lists built-inVectorArrow,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::CustomShapestodock/state.rswith label "Custom Shapes". - Create
hcie-iced-app/crates/hcie-iced-gui/src/panels/custom_shapes.rs.- Accept
shapes: &[ShapeEntry]andselected_index: Option<usize>. - 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.accentborder.
- Accept
- Wire the new panel in
dock/view.rs. - Add the panel to
settings.rsdefaultvisible_panels/panel_orderso it appears in the initial dock layout. - Add a "Refresh" button in the panel that emits
Message::RefreshCustomShapes; handler callsengine.shape_catalog.refresh()and updatesapp.cached_custom_shapes. - Store a cached
Vec<(String, String)>of custom shapes inHcieIcedAppso 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 returnNoneinVectorDrawEnd(already mostly true; add explicit guard). - Verify that copied SVGs are valid —
ShapeCatalogsilently 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.
Files:
hcie-iced-app/crates/hcie-iced-gui/src/app.rshcie-iced-app/crates/hcie-iced-gui/src/sidebar/mod.rs(icon support already present; no change)
Changes:
- In
app.rshelperis_vector_shape_tool()(around line 1287), addTool::CustomShape(_)to thematches!pattern. - In
app.rsCanvasPointerPressedhandler (around line 2824), addTool::CustomShape(_)to the arm that startsMessage::VectorDrawStart. - Keep
VectorDrawEndunchanged; it already snapshotsshape_catalog.all()(line 5528-5539) and builds aVectorShape::SvgShapeforTool::CustomShape(idx)with correct bounds.
Edge cases to consider:
- Out-of-range
Tool::CustomShape(idx)already returnsNonefromVectorDrawEndand 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:
- Build:
cargo check -p hcie-iced-guiandcargo test -p hcie-iced-gui. - Run the Iced GUI.
- Window → Custom Shapes → select a shape.
- Drag on the canvas → a new vector layer containing the custom SVG shape appears.
- Undo reverts the inserted shape (history snapshot is pushed by
engine.add_vector_shape).
Validation Plan
cargo check -p hcie-iced-guicargo test -p hcie-iced-gui- 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.)