Skip to content

Visual Editor: Share color and opacity editing controls - #13729

Merged
NigelBreslaw merged 7 commits into
masterfrom
nigel/visual-editor-color-fields
Oct 2, 2026
Merged

NigelBreslaw merged 7 commits into
masterfrom
nigel/visual-editor-color-fields

Conversation

@NigelBreslaw

@NigelBreslaw NigelBreslaw commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

The Visual Editor's inspector, picker, gradient-stop rows, and gallery use separate color controls with different editing behavior. They also all look different.

Share one InspectorColorValueField for RGB and opacity editing across these surfaces.
Gradient-stop rows now support inline color and opacity edits.
Source expressions and gradient fields retain their existing display and picker behavior.

Give drop-shadow and inner-shadow colors a full-width row above the angle control so the hex value stays readable.
Keep parsing, alpha preservation, and canonical formatting in Rust.
Remove unused conversion APIs, duplicate callback forwarding, and obsolete gradient-row styling.
The inspector retains its explicit commit and cancellation handlers.

Reuse the sorted stop-index model when its sequence is unchanged.
Color previews and selection changes therefore preserve gradient row controls and pointer capture.

@NigelBreslaw
NigelBreslaw marked this pull request as ready for review September 30, 2026 08:04
@tronical

Copy link
Copy Markdown
Member

@claude review. Among the usual things, also check if the RefCell is really needed there (my guess is that it isn't)

@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Claude finished @tronical's task in 1m 6s —— View job


Review of #13729

I read the whole diff. I did not build it or run the UI tests.

Is the RefCell needed?

Partly. You are right that RefCell is more than this code needs.

  • The callback passed to on_gradient_stop_order is an Fn, not an FnMut. It can't change a plain captured value. So the cache does need some interior mutability (brushes.rs:61).
  • RefCell is the heavy way to get that. Two lighter options:
    1. Use Cell<slint::ModelRc<i32>>. Compare with take(), then put the value back with set(). It can't panic on a double borrow.
    2. Keep one Rc<VecModel<i32>> in the closure and call set_vec only when the order changes. Then no cell is needed, because the model already holds its own state. This changes the model inside a pure callback while a binding is being evaluated. That can notify the repeater in the middle of the evaluation. I would only do it if you test that it behaves.
  • Today's RefCell is safe. Nothing inside the borrow calls back into the closure. So this is a style point, not a bug. I would go with option 1.
  • Another note: the cache is one slot shared by every caller of the callback. Two gradient lists in the same window (inspector and gallery) would evict each other. Nothing breaks. You just lose the reuse.

Public API and compatibility

  • Nothing under api/ changed.
  • Public .slint elements are unchanged. Everything touched is inside tools/editor.
  • Inside the editor, this removes or renames Api callbacks and structs:
    • Removed: string-is-color, string-to-color, color-to-data, rgba_to_color, color-field-*, and ColorData.
    • Added: color-value-* and fill-with-color.
    • Changed: ColorFieldData lost color-text and opacity. ColorFieldEdit gained color.
  • This breaks editor-internal code that used the old names. I found no leftover users in the changed files. I did not check the whole repo.
  • The new InspectorColorValueField is exported from the inspector index.slint. Its properties and callbacks are new, so nothing existing breaks.

Things to check

  1. Hidden solid field. color-field.slint keeps solid-field alive and toggles visible. It also keeps the old swatch-focus and hides it. That is two swatch/focus widgets in one component. Using if root.is-literal-color: would be cleaner. But focus-swatch() would then need a guard, and the picker anchor would need a check. It may be deliberate, to keep pointer capture. If so, a short note in the commit message would help.
  2. Live edit and partial input. In gradient rows, live-edit: true calls accept-input(text, false) on every keystroke. An invalid partial like ff returns false, so it should be harmless. I would like a UI test that types a partial hex and then cancels. test_linear_gradient_canvas.py covers scrubbing and previews, but I did not see this case.
  3. Edit state reset. text-field-base.slint now sets edited = false when focus is lost, and begin-edit fires edit-started once per edit. color-edit-original therefore refreshes on each new edit session. If a focus-out happens with no text change, edited was never set, so this is fine. If an accepted value ends focus mid-edit, the next edit takes a fresh original. That looks right.
  4. scrub-canceled in the gradient row. It calls preview(scrub-original). That puts back the pre-scrub color, including its exact alpha. This matches the commit "Preserve exact alpha across canceled scrubs".
  5. Unused import. color-field.slint still imports InspectorTextFieldBase. Please check whether it is still used after the removal. Also check Metrics and ColorIndicator.

Style

Comments and messages follow the style guide. I found nothing to flag.

Summary

No blocking issues. The RefCell can become a Cell. Please look at items 1, 2 and 5.

@NigelBreslaw
NigelBreslaw force-pushed the nigel/visual-editor-color-fields branch from ff562f5 to 5f7f862 Compare September 30, 2026 15:41
ChangeLog: Unify color and opacity editing across Visual Editor fill controls.
Delete unused conversion callbacks and color data. Share draft edit and scrub handlers through the color value field while retaining the inspector's transaction overrides. Use the shared opacity width for gradient headings and format RGB display text directly in uppercase.
Reuse the previous stop-index model when the sorted sequence is unchanged. Returning a fresh model recreated the rows after each preview and dropped pointer capture.

Cover continuous opacity scrubbing, reversal, cancellation, and atomic source history through pointer-driven UI tests.
…crubs

Capture the color before the first text edit so intermediate RGBA prefixes
cannot replace the alpha preserved by subsequent RGB input.
Capture the complete color when an opacity scrub begins and restore it on
cancellation instead of reconstructing alpha from a rounded percentage.

Add keyboard and pointer regressions for solid pickers, stop pickers, and
inline gradient rows, including new edits after acceptance or focus loss.
Use Cell to retain the sorted index model without a runtime borrow.

Cover partial hex input and Escape cancellation in inline gradient rows. Check unchanged source and restored color and opacity after reopening.
Use retrying assertions and replace the removed label lookup helper.

Keep a fixed scrubber handle to detect destroyed rows during pointer capture.
@NigelBreslaw
NigelBreslaw force-pushed the nigel/visual-editor-color-fields branch from 5f7f862 to 03e90c8 Compare October 2, 2026 13:43
@NigelBreslaw
NigelBreslaw requested a review from tronical October 2, 2026 13:51
@NigelBreslaw
NigelBreslaw merged commit ac155f6 into master Oct 2, 2026
49 checks passed
@NigelBreslaw
NigelBreslaw deleted the nigel/visual-editor-color-fields branch October 2, 2026 14:50
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