Skip to content

Integrate "color" into "explore" GUI - #172

Merged
MatthewPeterKelly merged 5 commits into
mainfrom
feature/interactive-module-live-editor
Jun 4, 2026
Merged

MatthewPeterKelly merged 5 commits into
mainfrom
feature/interactive-module-live-editor

Conversation

@MatthewPeterKelly

Copy link
Copy Markdown
Owner

Upgrade the "explore" GUI to include the full interactive support for editing the color map live. Here is what the new GUI looks like:

Screenshot 2026-06-02 211723

I could use a bit of polish, but it works!.

Keyframes can be inserted between existing frames with the "+" button; click a keyframe color to pull up the HSV color editor. Adjust the relative spacing with the spin boxes between each keyframe. Explore mode interaction and dynamic rendering quality still work. Color updates are super fast because they reuse a cached scalar field, rather than recomputing the fractal.

MatthewPeterKelly and others added 4 commits June 1, 2026 07:20
Phase 4, commit 1 of 4 (GUI unification roadmap §5). Pure module reorg
with no behavior change — sets up the home for the live color editor in
later commits.

- Move ExploreApp/explore from src/core/user_interface.rs into a new
  src/core/interactive/ module (mod.rs + app.rs); rename ExploreApp to
  FractalApp.
- Delete src/core/user_interface.rs; swap the module decl in core/mod.rs.
- Retarget the dispatch sites in cli/explore.rs and newtons_method.rs
  from user_interface::explore to interactive::explore.

All CI gates green; pixel-hash regression tests unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 4, commit 2 of 4 (GUI unification roadmap §5). Adds the plumbing
for live color editing; inert until the editor panel is wired in the next
commit (nothing sets the dirty flag yet besides R-reset).

- RenderingPipeline::recolorize_only — skips compute/histogram (steps a/b),
  rebuilds CDFs (identically, from the retained histograms) + LUTs +
  background, then re-walks the existing field. Two unit tests: byte-
  identical to a full render when the palette is unchanged, and matches a
  full re-render after a keyframe edit.
- PixelGrid gains: a separate Arc<Mutex<ColorPalette>> as the editor's
  source of truth (synced into the fractal at the start of each render /
  recolorize), a color_dirty flag, a recolorize() background task, a
  last_sampling_level so recolorize walks the same populated field cells,
  and initial_color_palette so reset() (R key) restores the initial colors.
- update() spawns a recolorize when color_dirty is set and no full render
  is launched this tick (view changes take priority, since they regenerate
  the field a recolorize would re-walk).

Design note: the editor's palette lives in its own lightweight mutex rather
than being edited through the pipeline mutex directly (as the roadmap text
suggests). The background render holds the pipeline mutex for the entire
compute, so locking it on the UI thread would freeze the editor during long
renders. The separate mutex keeps the UI responsive; the fractal's embedded
palette stays authoritative for serialization, refreshed each render.

All CI gates green; pixel-hash regression tests unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 4, final commit (GUI unification roadmap §5). Adds the right-side
color editor and wires edits to the preview live. Combines the roadmap's
planned "editor widget", "live-sync", and "key remap" commits: the pure
edit helpers are public API the key handlers call, so splitting them across
a commit boundary would have required temporary #[allow(dead_code)].

- New src/core/interactive/editor.rs: show_palette_editor mutates a
  ColorPalette in place and returns whether anything changed. Background
  swatch, per-color-map tabs (suppressed when there is a single map; "Root
  N" otherwise, switching resets selection), vertical keyframe color cells
  with `+` insert buttons and segment-fraction drag values, a read-only
  gradient bar (paint_gradient_bar lifted from the demo editor), and an
  inline color picker bound to the selected keyframe.
- Pure edit helpers with unit tests: set_segment_fraction (proportional
  rescale + MIN_FRACTION clamp + position rebuild), insert_midpoint
  (interpolated color at the segment midpoint), delete_keyframe (anchors
  protected).
- app.rs: FractalApp gains EditorState; layout is now Panel::right (editor)
  + CentralPanel (preview), both BLACK / no separator per §4.1, panel
  resizable via size_range per §4.3. On any palette change the preview is
  marked color-dirty and repainted.
- Key remaps (§6.3): Esc clears the keyframe selection (no longer quits);
  Delete removes the selected keyframe; R resets view AND palette; Q /
  Ctrl+C quit.

All CI gates green; pixel-hash regression tests unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update both status tables and annotate the Phase 4 section with the two
intentional deviations (separate palette mutex; merged commits). Phase 5
is now the next step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR delivers Phase 4 of the GUI unification roadmap by shipping a live color-palette editor integrated into the interactive fractal explorer. The editor enables fast re-coloring of cached fractal fields without recomputation, with color-dirty task scheduling, keyframe manipulation (insert, delete, adjust fractions), and right-side panel UI integration.

Changes

Phase 4: Interactive Color Editor

Layer / File(s) Summary
Module reorganization and entry point wiring
src/core/mod.rs, src/core/interactive/mod.rs, src/core/image_utils.rs, src/cli/explore.rs, src/fractals/newtons_method.rs
The user_interface module is replaced with a new interactive module at the core level, and both CLI and fractal explorers are re-routed to use the unified interactive::explore entry point. The color_palette_mut method doc is updated to reflect its use in re-color passes.
Color palette editor module and UI state
src/core/interactive/editor.rs
New egui-based palette editor with persistent EditorState, interactive UI for keyframe selection and color/fraction editing, helper functions (set_segment_fraction, insert_midpoint, delete_keyframe), a gradient preview renderer, and unit tests verifying fraction constraints, interpolation, and anchor preservation.
Render pipeline color-only re-rendering
src/core/render_pipeline.rs
RenderingPipeline::recolorize_only method re-colors cached fractal fields by refreshing cache state and running the colorization pass without recomputing values or histograms. Unit tests verify byte-identical output when palette is unchanged and correct mutations when palette is edited.
PixelGrid color state and task orchestration
src/core/render_window.rs
Extended PixelGrid with shared mutable palette, color_dirty atomic flag, initial palette snapshot, and cached sampling level. Public palette() and mark_color_dirty() methods are added. Background task logic differentiates between full render and palette-only recolorize, synchronizing the shared palette into both paths and scheduling recolorize only when no full render launched and the flag is set.
Interactive app with editor panel and keybindings
src/core/interactive/app.rs
Replaces ExploreApp with FractalApp, adds persistent EditorState, defines editor panel sizing, renders a right-side color editor panel, rewires keybindings (Q/Ctrl+C to quit, Esc to clear selection, Delete to remove keyframe + trigger color re-render, R to reset both view and palette), and requests repaint when palette changes.
Project roadmap documentation
docs/gui-unification-roadmap.md
Marks Phase 4 as shipped and documents two key deviations from the original plan: palette mutex location on PixelGrid vs. pipeline, and consolidation of editor/live-sync/key-remap into a single PR.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • MatthewPeterKelly/fractal-renderer#166: Both PRs modify docs/gui-unification-roadmap.md—PR #166 established the canonical roadmap structure, and this PR updates it to mark Phase 4 shipped and document the implementation deviations.
  • MatthewPeterKelly/fractal-renderer#154: Both PRs modify the explore_fractal routing in src/fractals/newtons_method.rs—PR #154 extended CoshMinusOne handling in the same logic that this PR re-routes to the new interactive::explore entry point.

Poem

🐰 A palette so fine, now live in the view,
Colors dance as keyframes shift through and through,
No need to recompute the fractal anew,
Just paint with a brush and the editor's ✨ hue!
Phase 4 complete—the GUI rings true. 🎨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title directly corresponds to the main objective of the PR: integrating a live color editor into the explore GUI, as evidenced by changes across app.rs, editor.rs, and render_window.rs to support palette editing.
Description check ✅ Passed The description is clearly related to the changeset, explaining the new color editor feature, keyframe operations, fast recoloring mechanism, and providing a visual example of the integrated GUI.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/interactive-module-live-editor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR upgrades the interactive “explore” GUI by moving it under a unified core::interactive module and adding a live color-palette editor panel. It also introduces a fast “recolorize only” render path so color edits can update the preview without recomputing the fractal field.

Changes:

  • Replace user_interface::explore with interactive::explore and add the new src/core/interactive/ module.
  • Add a live palette editor UI (keyframes/background) and wire it into the interactive app, including key handling for selection/delete/reset.
  • Add a color-only pipeline path (RenderingPipeline::recolorize_only) and PixelGrid plumbing (color_dirty, palette mutex, last_sampling_level) to enable fast color updates.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/fractals/newtons_method.rs Switch Newton explorer entrypoint to core::interactive::explore.
src/core/render_window.rs Add palette state + dirty flag + recolorize task support in PixelGrid.
src/core/render_pipeline.rs Add recolorize_only fast-path plus unit tests for equivalence vs full render.
src/core/mod.rs Export interactive module and remove user_interface from core.
src/core/interactive/mod.rs New interactive module root; re-export explore.
src/core/interactive/editor.rs New palette editor widget + keyframe editing helpers + tests.
src/core/interactive/app.rs Wire editor panel into explorer app; add keybindings; route color edits to recolorize.
src/core/image_utils.rs Clarify intent of color_palette_mut for interactive sync.
src/cli/explore.rs Switch CLI explorer entrypoint to core::interactive::explore.
docs/gui-unification-roadmap.md Update roadmap to mark Phase 4 shipped and document deviations.
Comments suppressed due to low confidence (1)

src/core/interactive/app.rs:261

  • R is handled with key_down, so holding the key will repeatedly call reset() every frame. That can continuously re-trigger color-dirty and/or full renders unnecessarily; this should be edge-triggered like other one-shot actions (e.g. Space).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/core/render_window.rs
Comment thread src/core/render_window.rs
Comment thread src/core/interactive/editor.rs Outdated
Comment thread src/core/render_pipeline.rs
Comment thread src/core/render_window.rs
Copilot review follow-ups:

- set_segment_fraction: fix a broken invariant. The old code clamped each
  rescaled segment up to MIN_FRACTION and then divided everything by the
  total, which could push a just-floored segment back below MIN_FRACTION.
  Rebuild so the fractions sum to exactly 1.0 by construction — give each
  other segment a MIN_FRACTION floor, then distribute only the surplus in
  proportion to the old widths. No normalization pass, so the floor holds.
  Add a regression test with highly unequal segments.
- recolorize_only: add the same defensive debug-asserts render() has
  (field outer dim and out.size vs the fractal spec) to catch stale-buffer
  call sites (e.g. after a resize) early.
- PixelGrid::update: clear color_dirty when a full render is launched. The
  full render already clones the current palette into the fractal, so a
  pending color edit is satisfied; clearing avoids a redundant recolorize
  firing the instant that render completes.
- render_window: drop the stale "wired in the next commit" notes and
  #[allow(dead_code)] on palette() / mark_color_dirty() — both are used.
- app: edge-trigger the R reset (key_pressed, not key_down) so holding the
  key resets once instead of re-cloning the palette and re-marking dirty
  every frame. reset() forces a full render via the regulator, so the view
  still refreshes correctly.

All CI gates green; pixel-hash regression tests unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@MatthewPeterKelly

Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/core/interactive/mod.rs (1)

7-10: ⚡ Quick win

Add item docs for the exported module surface.

These new public items are undocumented right now. Please add /// comments to app, editor, and the explore re-export so this module stays guideline-compliant.

As per coding guidelines, "Every pub item (structs, enums, traits, functions, type aliases, and their pub fields/methods) must have a /// doc comment".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/interactive/mod.rs` around lines 7 - 10, Add missing doc comments
for the public module surface: add a short `///` doc comment above `pub mod
app;` describing the app submodule's purpose, another `///` comment above `pub
mod editor;` describing the editor submodule, and a `///` comment above the `pub
use app::explore;` re-export explaining what `explore` provides to users; ensure
each comment is a concise sentence following crate docs style so all `pub` items
(`app`, `editor`, `explore`) have `///` documentation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/render_window.rs`:
- Around line 100-109: render_to_file() currently serializes only
self.image_specification(), so exported snapshots don't include live edits in
the editor-held palette (palette: Arc<Mutex<ColorPalette>>); update
render_to_file() (or the serialization path it calls) to include the current
palette by reading the Arc<Mutex<ColorPalette>> lock and embedding that
ColorPalette into the serialized fractal params (or call a helper that syncs the
locked palette into the struct used by image_specification() before
serializing). Ensure reset still restores initial_color_palette as before and
that image_specification() uses the synced palette so PNG + JSON remain
consistent.

---

Nitpick comments:
In `@src/core/interactive/mod.rs`:
- Around line 7-10: Add missing doc comments for the public module surface: add
a short `///` doc comment above `pub mod app;` describing the app submodule's
purpose, another `///` comment above `pub mod editor;` describing the editor
submodule, and a `///` comment above the `pub use app::explore;` re-export
explaining what `explore` provides to users; ensure each comment is a concise
sentence following crate docs style so all `pub` items (`app`, `editor`,
`explore`) have `///` documentation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: df0c18e6-5853-4370-96b0-1f3d47c9803b

📥 Commits

Reviewing files that changed from the base of the PR and between e7a779b and b2a3da4.

📒 Files selected for processing (10)
  • docs/gui-unification-roadmap.md
  • src/cli/explore.rs
  • src/core/image_utils.rs
  • src/core/interactive/app.rs
  • src/core/interactive/editor.rs
  • src/core/interactive/mod.rs
  • src/core/mod.rs
  • src/core/render_pipeline.rs
  • src/core/render_window.rs
  • src/fractals/newtons_method.rs

Comment thread src/core/render_window.rs
Comment on lines +100 to +109
// Editor's source-of-truth color palette, held in its own lightweight
// mutex so the UI thread can read/mutate it every frame without ever
// locking the (long-held) pipeline mutex. Synced into the fractal at the
// start of each render / recolorize. See the Phase-4 roadmap deviation
// note: this avoids freezing the editor during a long render.
palette: Arc<Mutex<ColorPalette>>,

// The palette the fractal was constructed with; restored by `reset` so
// `R` returns to the initial colors as well as the initial view.
initial_color_palette: ColorPalette,

@coderabbitai coderabbitai Bot Jun 4, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Snapshot exports no longer capture live palette edits.

palette is now the editor's source of truth, but render_to_file() still serializes only self.image_specification(). After any keyframe/background change, the saved PNG reflects the edited colors while the sibling JSON cannot reproduce them. Please export the current palette together with the current viewport/render params, or sync the shared palette back into the serialized fractal params before writing snapshots.

Also applies to: 156-159

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/render_window.rs` around lines 100 - 109, render_to_file() currently
serializes only self.image_specification(), so exported snapshots don't include
live edits in the editor-held palette (palette: Arc<Mutex<ColorPalette>>);
update render_to_file() (or the serialization path it calls) to include the
current palette by reading the Arc<Mutex<ColorPalette>> lock and embedding that
ColorPalette into the serialized fractal params (or call a helper that syncs the
locked palette into the struct used by image_specification() before
serializing). Ensure reset still restores initial_color_palette as before and
that image_specification() uses the synced palette so PNG + JSON remain
consistent.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That will be the topic of the next PR, so I'm going to leave that as a follow-up.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MatthewPeterKelly — understood, makes sense to keep that as a focused follow-up. Would you like me to open a GitHub issue to track this? I can file it with a summary of the problem (exported PNG reflects live palette edits but the sibling JSON doesn't include the current palette state) so it doesn't get lost between PRs.

(ᵔ ᴥ ᵔ)


🧠 Learnings used
Learnt from: MatthewPeterKelly
Repo: MatthewPeterKelly/fractal-renderer PR: 154
File: examples/render-newton-cosh-minus-one/params.json:17-27
Timestamp: 2026-01-21T23:44:22.592Z
Learning: In Newton method fractals for this repository, a single color map in FullColorSpec can be intentionally used to color the fractal based on convergence speed rather than which specific root/attractor each point converges to, producing a different style of visualization.

Learnt from: MatthewPeterKelly
Repo: MatthewPeterKelly/fractal-renderer PR: 154
File: examples/render-newton-cosh-minus-one/main.rs:4-7
Timestamp: 2026-01-09T23:01:01.712Z
Learning: In the fractal-renderer repo, the cargo alias rex is defined in .cargo/config.toml as a cargo alias expanding to 'run --release --example'. Use 'cargo rex' to run Rust example binaries (in release mode) across all Rust source files (i.e., for any example under the repository).

@MatthewPeterKelly

Copy link
Copy Markdown
Owner Author

Code LGTM from a quick human review pass. Some minor improvements to be had in the GUI support code, but we'll tune those up in follow-up PRs to polish the GUI from a usability standpoint. Rendering pipeline details will be polished in the next PR along with the implementation of the correct "save" implementation.

@MatthewPeterKelly
MatthewPeterKelly merged commit 9c10460 into main Jun 4, 2026
7 checks passed
@MatthewPeterKelly
MatthewPeterKelly deleted the feature/interactive-module-live-editor branch June 4, 2026 09:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants