Files
hcie-rust-v3.05/.kilo/plans/1784597466971-iced-brushes-compact-preview-plan.md
T
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

264 lines
9.1 KiB
Markdown

# 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:
```rust
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:
```rust
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:
```rust
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`:
```rust
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:
```rust
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`:
```rust
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:
```rust
let preview_handle = get_brush_preview(current_style);
let live_preview = container(iced::widget::image(preview_handle).width(64).height(64))
...
```
Replace with:
```rust
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.