eb17bf4205
- 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.
264 lines
9.1 KiB
Markdown
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.
|