- 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.
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.rscallscrate::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_CACHEfor style-only previews. - Regenerate it with the new
THUMB_SIZE(40 px). The existing sine-wavegenerate_brush_previewcan stay, just adjust the drawing bounds to match. - Ensure the function returns
width=THUMB_SIZE, height=THUMB_SIZEpixels.
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
4to3. - Keep sliders, but make the panel column spacing
2or3.
Affected Files
hcie-iced-app/crates/hcie-iced-gui/src/panels/brushes.rs— main panel layout and preview logic.hcie-iced-app/crates/hcie-iced-gui/src/dock/view.rs— passapp.fg_colortobrushes::view.
No engine crates are modified. No Cargo.toml changes are needed because hcie_engine_api is already a dependency.
Validation
cargo check -p hcie-iced-guimust pass.cargo test -p hcie-iced-gui brushes(orcargo test -p hcie-iced-gui) must pass; the existing tests inbrushes.rs(catalog_contains_every_engine_brush_style,category_filter_keeps_expected_groups,media_crates_expose_complete_unique_catalog) must remain green.- 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.
- If the Iced CLI screenshot mechanism works, capture the Brushes panel for before/after comparison.
Risks
- 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. - 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. - 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.rsmay need a small bump (currently 160 px preferred 300 px). Test first; only bump if required. - Tests assume style-only cache: the existing
BRUSH_PREVIEW_CACHEtests 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_previewalready provides.