# Watercolor Brush Category — Implementation Plan ## Goal Add a new **Watercolor** brush category to the iced GUI's Brushes panel. Each brush has a watercolor-splat preview (not the standard sine-wave stroke). The brushes simulate realistic watercolor effects. Implemented as a new crate with zero modification to existing brush/engine crates. ## Architecture Overview ### Existing brush flow 1. `hcie-brush-engine` defines `BrushStyle` enum (33 variants including `Watercolor`) and `BrushPreset` struct 2. `hcie-engine-api` re-exports `BrushPreset` and provides `draw_specialized_stroke` → dispatches to `draw_watercolor_brush` for `BrushStyle::Watercolor` 3. `hcie-iced-gui/src/panels/brushes.rs` defines `BrushCategory` enum, `BRUSH_STYLES` static array, and `generate_brush_preview()` for thumbnails 4. When a preset is selected → `Message::BrushPresetSelected(preset)` → sets `tool_state.brush_style`, `brush_size`, `brush_opacity`, `brush_hardness`, `brush_spacing`, `active_brush_preset` ### Key constraint Engine crates (`hcie-brush-engine`, `hcie-protocol`, `hcie-engine-api`, `hcie-draw`) are **locked** — agents must not modify them. The new crate lives in the GUI layer and reuses the engine's existing `BrushStyle::Watercolor` rendering path. ### New crate strategy - Define watercolor presets using existing `BrushPreset` (from `hcie-engine-api`) with `style: BrushStyle::Watercolor` - Provide a **watercolor splat preview renderer** that generates RGBA pixels for thumbnails - The brushes panel integrates the new crate's presets under a "Watercolor" category tab - Actual drawing uses the engine's existing watercolor brush rendering — no engine changes needed --- ## Step 1: Create the new crate `hcie-watercolor-brushes` **Path:** `hcie-iced-app/crates/hcie-watercolor-brushes/` ### `Cargo.toml` ```toml [package] name = "hcie-watercolor-brushes" version.workspace = true edition.workspace = true [dependencies] hcie-engine-api = { path = "../../../hcie-engine-api" } ``` ### `src/lib.rs` — Presets + Preview Renderer Exports: - `pub fn watercolor_presets() -> Vec` — returns 8 watercolor brush presets - `pub fn render_watercolor_splat(style: BrushStyle, base_color: [u8; 3]) -> Vec` — generates 64×64 RGBA watercolor splat preview #### Presets (8 brushes) | ID | Name | Size | Opacity | Hardness | Spacing | Description | |----|------|------|---------|----------|---------|-------------| | `wc_wash` | Watercolor Wash | 80 | 0.25 | 0.0 | 0.08 | Large, transparent wash | | `wc_wet` | Wet Watercolor | 60 | 0.35 | 0.0 | 0.06 | Medium, wet-on-wet | | `wc_dry` | Dry Brush | 30 | 0.70 | 0.6 | 0.12 | Small, textured, high opacity | | `wc_glaze` | Glazing | 50 | 0.15 | 0.0 | 0.10 | Very transparent, layered | | `wc_splat` | Splatter | 40 | 0.50 | 0.2 | 0.30 | Scattered droplets | | `wc_bleed` | Color Bleed | 70 | 0.30 | 0.0 | 0.07 | Heavy diffusion edges | | `wc_grain` | Granulation | 45 | 0.40 | 0.3 | 0.09 | Visible paper grain texture | | `wc_bloom` | Bloom | 90 | 0.20 | 0.0 | 0.05 | Soft, blooming edges | All use `BrushStyle::Watercolor` as the engine style — the engine's existing `draw_watercolor_brush` handles the actual stroke rendering. #### Watercolor Splat Preview Algorithm (64×64 RGBA) The splat is generated procedurally using multi-octave value noise: 1. **Base shape:** Radial gradient from center, `alpha = (1 - dist/radius)^1.5` 2. **Edge displacement:** Displace the radius at each angle using 3-octave value noise → irregular organic boundary 3. **Multiple layers:** 3-5 overlapping semi-transparent blobs at slightly offset positions 4. **Color variation:** Shift hue slightly per layer (±10°) for color separation 5. **Granulation:** High-frequency noise modulates alpha in the outer 40% of the radius 6. **Wet edge:** Slightly darker/more saturated ring near the boundary (wet-edge accumulation effect) The noise function is a simple hash-based value noise (no external deps): ```rust fn value_noise(x: f32, y: f32) -> f32 { // hash-based pseudo-random, returns 0.0..1.0 } fn fbm(x: f32, y: f32, octaves: u32) -> f32 { // fractal Brownian motion using value_noise } ``` --- ## Step 2: Register the crate in the workspace **File:** `Cargo.toml` (workspace root) Add to `[workspace] members`: ```toml "hcie-iced-app/crates/hcie-watercolor-brushes", ``` --- ## Step 3: Add `hcie-watercolor-brushes` dependency to `hcie-iced-gui` **File:** `hcie-iced-app/crates/hcie-iced-gui/Cargo.toml` Add: ```toml hcie-watercolor-brushes = { path = "../hcie-watercolor-brushes" } ``` --- ## Step 4: Update the Brushes panel **File:** `hcie-iced-app/crates/hcie-iced-gui/src/panels/brushes.rs` ### 4a. Add `Watercolor` variant to `BrushCategory` ```rust pub enum BrushCategory { All, Drawing, Painting, Watercolor, // ← NEW Effects, Custom, Imported, } ``` ### 4b. Update `category_matches_style` for the new variant ```rust BrushCategory::Watercolor => style_category == BrushCategory::Watercolor, ``` ### 4c. Add Watercolor tab to the category tabs array ```rust (BrushCategory::Watercolor, "Watercolor", "icons/brush.svg"), ``` ### 4d. Render watercolor presets when Watercolor category is active When `active_category == BrushCategory::Watercolor`: - Call `hcie_watercolor_brushes::watercolor_presets()` to get the preset list - For each preset, call `hcie_watercolor_brushes::render_watercolor_splat(...)` for the thumbnail - Render as a grid (same 2-column layout as existing brushes) - Each cell sends `Message::BrushPresetSelected(preset)` on click ### 4e. Update `view` function signature The function already accepts `imported_presets: &[BrushPreset]`. No signature change needed — the watercolor presets are fetched internally from the new crate. ### 4f. Update test `catalog_contains_every_engine_brush_style` The test asserts `BRUSH_STYLES.len() == 34`. This doesn't change (watercolor presets are separate from `BRUSH_STYLES`). The test remains valid. --- ## Step 5: Add "Watercolor" to the egui GUI category filter (optional, for parity) **File:** `hcie-egui-app/crates/hcie-gui-egui/src/app/tools/brushes_panel.rs` Add "Watercolor" to the categories array and update `matches_style_category` to map it to `BrushStyle::Watercolor`. This is a minimal change for parity — the watercolor presets themselves would need separate integration in the egui panel if desired. **Note:** This step is optional for the initial implementation. The user's request focuses on the iced GUI. --- ## Files Changed | File | Change | |------|--------| | `hcie-iced-app/crates/hcie-watercolor-brushes/Cargo.toml` | **New** — crate manifest | | `hcie-iced-app/crates/hcie-watercolor-brushes/src/lib.rs` | **New** — presets + splat renderer | | `Cargo.toml` (workspace root) | Add member `hcie-iced-app/crates/hcie-watercolor-brushes` | | `hcie-iced-app/crates/hcie-iced-gui/Cargo.toml` | Add dependency `hcie-watercolor-brushes` | | `hcie-iced-app/crates/hcie-iced-gui/src/panels/brushes.rs` | Add `Watercolor` category, tab, and grid rendering | **Files NOT modified:** All engine crates (`hcie-brush-engine`, `hcie-protocol`, `hcie-engine-api`, `hcie-draw`), existing brush definitions, existing `BRUSH_STYLES` array. --- ## Validation 1. `cargo check -p hcie-watercolor-brushes` — new crate compiles 2. `cargo check -p hcie-iced-gui` — GUI compiles with new dependency 3. `cargo test -p hcie-iced-gui` — all existing tests pass 4. `cargo test -p hcie-watercolor-brushes` — new crate tests pass 5. `cargo test --test feature_scorecard` — scorecard passes 6. Visual: launch the app, open Brushes panel, click "Watercolor" tab → 8 watercolor presets with splat previews appear 7. Visual: select a watercolor brush → draw on canvas → realistic watercolor stroke rendered 8. Regression: existing Drawing/Painting/Effects brushes unchanged