Files
hcie-rust-v3.05/.kilo/plans/1784597466971-iced-brushes-compact-preview-plan.md
Your Name eb17bf4205
mandatory-regression-gate / deterministic-tests (push) Waiting to run
mandatory-regression-gate / protected-performance-path (push) Waiting to run
feat: implement compact layout and real-time preview for Brushes & Tips panel
- Introduced a more compact layout for the Brushes & Tips panel in the Iced GUI.
- Added a real-time brush-tip preview that updates based on current brush parameters and foreground color.
- Adjusted thumbnail sizes and grid layout to improve usability and visual clarity.
- Implemented caching for live previews to optimize performance.
- Updated existing code to integrate new layout and preview functionalities.
- Ensured no changes were made to engine crates, maintaining stability.
2026-07-25 16:28:31 +03:00

9.1 KiB

Iced Brushes Panel — Compact Layout + Real-Time Preview

Goal

Make the hcie-iced-app Brushes & Tips panel more compact and add a real-time brush-tip preview that reacts to current brush parameters and foreground color.

Scope is limited to the Iced GUI layer. Engine crates stay locked and untouched; all rendering goes through the public hcie_engine_api surface.


Context

  • Active target workspace (per user override): hcie-iced-app/.
  • Primary file: hcie-iced-app/crates/hcie-iced-gui/src/panels/brushes.rs.
  • Panel wiring: hcie-iced-app/crates/hcie-iced-gui/src/dock/view.rs calls crate::panels::brushes::view(...).
  • Current layout:
    • Category tabs with 16 px SVG icons.
    • Thumbnail image 48 px inside a 56 px cell.
    • Two-column grid with 4 px spacing.
    • Label text size 9 px.
    • A bottom "Preview" area using a static style-keyed 64 px image.
  • Available engine APIs (already public in hcie_engine_api):
    • Engine::render_brush_stamp_preview(width, height, &BrushTip, color) — fast single-dab preview.
    • Engine::render_brush_preview(width, height, &BrushTip, color, &points) — full stroke preview.

Decisions

Decision Choice
Preview API Engine::render_brush_stamp_preview (fast, parameter-aware).
Thumbnail image size 36 px.
Thumbnail cell size 40 px.
Grid columns 3.
Label text 8 px, kept.
Grid/item spacing 3 px.
Tab icon size Keep 16 px (already compact).
Live preview color Current foreground color fg_color.
Cache key (style, size_byte, hardness_byte, opacity_byte, fg_color) so the preview updates when parameters change.
Watercolor preset thumbnails Keep current splat renderer but shrink to 36 px image / 40 px cell and align with the new grid.
Imported preset thumbnails Keep list row style but shrink preview to 36 px image in 40 px cell.

Implementation Steps

1. Add foreground color to panel signature

In hcie-iced-app/crates/hcie-iced-gui/src/dock/view.rs, pass app.fg_color into brushes::view(...).

In hcie-iced-app/crates/hcie-iced-gui/src/panels/brushes.rs, extend the function signature:

pub fn view(
    current_style: BrushStyle,
    current_size: f32,
    current_opacity: f32,
    current_hardness: f32,
    brush_color_variant: bool,
    brush_variant_amount: f32,
    active_category: BrushCategory,
    imported_presets: &[hcie_engine_api::BrushPreset],
    active_brush_preset: Option<&hcie_engine_api::BrushPreset>,
    fg_color: [u8; 4],   // NEW
    colors: ThemeColors,
) -> Element<'static, Message>

2. Introduce compact layout constants

At the top of brushes.rs, replace/adjust constants:

const THUMB_SIZE: u32 = 40;
const PREVIEW_IMG_SIZE: u32 = 36;
const GRID_COLUMNS: usize = 3;
const GRID_SPACING: u16 = 3;
const ITEM_SPACING: u16 = 3;
const LABEL_SIZE: u16 = 8;

Also add a real-time preview cache keyed by parameters:

static LIVE_PREVIEW_CACHE: OnceLock<Mutex<HashMap<PreviewKey, iced::widget::image::Handle>>> = OnceLock::new();

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct PreviewKey {
    style_idx: usize,
    size: u8,       // quantized to whole px
    hardness: u8,   // 0..100
    opacity: u8,    // 0..100
    r: u8,
    g: u8,
    b: u8,
    a: u8,
}

3. Build a parameter-aware preview generator

Add a helper that constructs a BrushTip from the current tool state and calls Engine::render_brush_stamp_preview:

fn build_tip_from_state(style: BrushStyle, size: f32, opacity: f32, hardness: f32) -> hcie_engine_api::BrushTip {
    hcie_engine_api::BrushTip {
        style,
        size,
        opacity,
        hardness,
        ..Default::default()
    }
}

Add:

fn get_live_stamp_preview(
    style: BrushStyle,
    size: f32,
    opacity: f32,
    hardness: f32,
    fg_color: [u8; 4],
) -> iced::widget::image::Handle {
    let key = PreviewKey {
        style_idx: style as usize,
        size: size.clamp(1.0, 255.0) as u8,
        hardness: (hardness * 100.0).clamp(0.0, 100.0) as u8,
        opacity: (opacity * 100.0).clamp(0.0, 100.0) as u8,
        r: fg_color[0],
        g: fg_color[1],
        b: fg_color[2],
        a: fg_color[3],
    };
    let cache = LIVE_PREVIEW_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
    let mut cache = cache.lock().unwrap_or_else(|p| p.into_inner());
    if let Some(handle) = cache.get(&key) {
        return handle.clone();
    }
    let tip = build_tip_from_state(style, size, opacity, hardness);
    let pixels = hcie_engine_api::Engine::render_brush_stamp_preview(
        PREVIEW_IMG_SIZE,
        PREVIEW_IMG_SIZE,
        &tip,
        fg_color,
    );
    let handle = iced::widget::image::Handle::from_rgba(
        PREVIEW_IMG_SIZE,
        PREVIEW_IMG_SIZE,
        pixels,
    );
    cache.insert(key, handle.clone());
    handle
}

4. Update existing thumbnail cache to new size

  • Keep BRUSH_PREVIEW_CACHE for style-only previews.
  • Regenerate it with the new THUMB_SIZE (40 px). The existing sine-wave generate_brush_preview can stay, just adjust the drawing bounds to match.
  • Ensure the function returns width=THUMB_SIZE, height=THUMB_SIZE pixels.

5. Re-layout the grid

Change the grid construction from 2 columns to 3 columns using GRID_COLUMNS:

let mut grid_rows = column![].spacing(GRID_SPACING);
let mut current_row = row![].spacing(GRID_SPACING);

for (idx, preset) in filtered_styles.iter().enumerate() {
    // ... build 40 px cell with 36 px image + 8 px label ...
    current_row = current_row.push(clickable);
    if (idx + 1) % GRID_COLUMNS == 0 || idx == filtered_styles.len() - 1 {
        grid_rows = grid_rows.push(current_row);
        current_row = row![].spacing(GRID_SPACING);
    }
}

Apply the same 3-column compact layout to:

  • Built-in styles grid.
  • Watercolor presets grid.
  • Media presets grid.

For imported presets, keep the list-row layout but set the preview image to 36 px and container to 40 px.

6. Replace the bottom static preview with the live preview

Locate the existing code:

let preview_handle = get_brush_preview(current_style);
let live_preview = container(iced::widget::image(preview_handle).width(64).height(64))
    ...

Replace with:

let preview_handle = get_live_stamp_preview(
    current_style,
    current_size,
    current_opacity,
    current_hardness,
    fg_color,
);
let live_preview = container(iced::widget::image(preview_handle).width(PREVIEW_IMG_SIZE as f32).height(PREVIEW_IMG_SIZE as f32))
    .width(Length::Fill)
    .height(56)
    .center_x(Length::Fill)
    .center_y(Length::Fill)
    .style(move |_theme| styles::recessed_control(colors));

Make the preview area slightly shorter (56 px instead of 80 px) to reclaim vertical space.

7. Tighten slider/controls spacing

  • Reduce the column padding from 4 to 3.
  • Keep sliders, but make the panel column spacing 2 or 3.

Affected Files

  1. hcie-iced-app/crates/hcie-iced-gui/src/panels/brushes.rs — main panel layout and preview logic.
  2. hcie-iced-app/crates/hcie-iced-gui/src/dock/view.rs — pass app.fg_color to brushes::view.

No engine crates are modified. No Cargo.toml changes are needed because hcie_engine_api is already a dependency.


Validation

  1. cargo check -p hcie-iced-gui must pass.
  2. cargo test -p hcie-iced-gui brushes (or cargo test -p hcie-iced-gui) must pass; the existing tests in brushes.rs (catalog_contains_every_engine_brush_style, category_filter_keeps_expected_groups, media_crates_expose_complete_unique_catalog) must remain green.
  3. Launch the Iced GUI and open the Brushes & Tips panel. Visually verify:
    • 3-column grid of 40 px cells.
    • 8 px labels still readable.
    • Bottom preview area updates when size, opacity, hardness, or foreground color changes.
    • Selection border still highlights the active brush.
  4. If the Iced CLI screenshot mechanism works, capture the Brushes panel for before/after comparison.

Risks

  1. Cache growth: the parameter-keyed cache can grow quickly if size/opacity/hardness are continuous. Mitigation: quantize size to u8, hardness/opacity to 0..100, and fg color to full bytes; this bounds the space to ~256 * 101 * 101 * 256^4 possible keys but only populated entries consume memory.
  2. Performance of render_brush_stamp_preview: small single-dab previews (36x36 or 40x40) should be fast; if not, consider throttling updates to panel redraws only.
  3. Iced layout overflow: switching to 3 columns with 3 px spacing in a narrow pane may still wrap. If so, the pane minimum width in dock/sizing.rs may need a small bump (currently 160 px preferred 300 px). Test first; only bump if required.
  4. Tests assume style-only cache: the existing BRUSH_PREVIEW_CACHE tests do not inspect cache contents, so resizing should not break them.

Out of Scope

  • EGUI (hcie-egui-app) changes — explicitly excluded by the user; it remains a reference only.
  • Engine-side brush rendering changes — engine crates are locked.
  • New brush engine presets or categories.
  • Bitmap/imported brush stamp preview fidelity beyond what render_brush_stamp_preview already provides.