Skip to content

ColorPicker

Header: winui/ui/input/ColorPicker.hpp. Spec: winui/ui/input/ColorPicker.hpp.

A hue/saturation/value/alpha color editor with channel, alpha and hex text entry and a preview swatch.

A container of independently focusable sub-controls, laid out vertically from the top: hue and saturation sliders beside the preview (standing in for the 2D spectrum, which is not drawn), the value slider, the alpha slider, an optional More button, then the text entry section (an RGB/HSV dropdown and a #RRGGBB hex box, three channel boxes and an Opacity box). Tab walks the parts in that order; the picker itself is never focused.

Every part has a *_visible switch. A hidden part is not laid out, drawn, hit-tested or reachable by Tab, and showing it again needs no rebuild. A channel box applies a valid value as it is typed (0-255 for RGB, 0-359 hue, 0-100 saturation, value and opacity); Enter reverts invalid text. The hex box commits on Enter only: 6 or 8 hex digits (#RRGGBBAA, '#' optional, case-insensitive); anything else reverts.

Example:

auto row = std::make_unique<ui::StackControl>(ui::Orientation::Horizontal);
ui::ColorPickerControl* picker = row->add(std::make_unique<ui::ColorPickerControl>(ui::Color::from_rgba(0, 120, 215),
                                                                            /*alpha_enabled=*/true));
picker->set_more_button_visible(true); // text entry collapsed behind "More"
ui::BorderControl* swatch = row->add(std::make_unique<ui::BorderControl>());
swatch->set_width(100.0f);
swatch->set_height(100.0f);
swatch->set_background(picker->color());
picker->on_color_changed([swatch](ui::Color c) { swatch->set_background(c); });
window.set_content(std::move(row));

// Declaratively, two-way through a Property (the equality gate stops the echo):
ui::Property<ui::Color> accent{ui::Color::from_rgba(0, 120, 215)};
window.set_content(ui::HorizontalStack{
    .spacing  = 16,
    .content  = {
        ui::ColorPicker{
            .color               = accent,
            .alpha_enabled       = true,
            .more_button_visible = true,
        },
        ui::Border{.background = ui::bind(accent, [](ui::Color c) { return std::optional{c}; }),
                          .width      = 100.0f,
                          .height     = 100.0f},
    },
});

ui::ColorPicker

A hue/saturation/value/alpha color editor with channel, alpha and hex text entry and a preview swatch. Builds a ui::ColorPickerControl. Each *_visible field shows or hides one part; ui::ColorPickerControl's class comment describes the parts.

.color = prop is two-way: it shows the property and writes the picked color back into it before on_color_changed runs.

Example:

ui::Property<ui::Color> accent{ui::Color::from_rgba(0, 120, 215)};

window.set_content(ui::HorizontalStack{.spacing = 16, .content = {
    ui::ColorPicker{
        .color                = accent,
        .alpha_enabled        = true,
        .more_button_visible  = true,
        .horizontal_alignment = ui::Alignment::Leading,
    },
    ui::Border{.background = ui::bind(accent, [](ui::Color c) { return std::optional{c}; }),
               .width      = 100.0f,
               .height     = 100.0f},
}});

Every field, in the order a designated initializer names them:

ui::ColorPicker{
    .color, .alpha_enabled, .spectrum_visible, .preview_visible, .color_slider_visible,
    .alpha_slider_visible, .more_button_visible, .color_channel_text_input_visible,
    .alpha_text_input_visible, .hex_input_visible, .style, .margin, .width, .height,
    .min_width, .min_height, .max_width, .max_height, .horizontal_alignment,
    .vertical_alignment, .visible, .tool_tip, .enabled, .accessibility, .on_color_changed,
    .on_pointer_entered, .on_pointer_exited, .on_got_focus, .on_lost_focus, .on_key_down,
    .on_wheel, .control
}

Fields, in declaration order (designators must follow it):

Field Type Default
color ui::TwoWayField<ui::Color> {ui::colors::Black} The selected color. Its alpha is kept only while alpha_enabled is true.
alpha_enabled ui::Binding<bool> {false} Show and honor the alpha channel. Applied before color.
spectrum_visible ui::Binding<bool> {true} The hue and saturation sliders.
preview_visible ui::Binding<bool> {true} The preview swatch.
color_slider_visible ui::Binding<bool> {true} The value slider.
alpha_slider_visible ui::Binding<bool> {true} The alpha slider (with alpha_enabled).
more_button_visible ui::Binding<bool> {false} The More button.
color_channel_text_input_visible ui::Binding<bool> {true} The RGB/HSV dropdown and channel boxes.
alpha_text_input_visible ui::Binding<bool> {true} The Opacity box (with alpha_enabled).
hex_input_visible ui::Binding<bool> {true} The #RRGGBB box.
on_color_changed ui::Callback<void(ui::Color)> Fires with the new color when it changes, from any part (slider drag, keyboard step, typed channel value or committed hex edit).
on_pointer_entered ui::Action The pointer moved onto the control.
on_pointer_exited ui::Action The pointer left the control.
on_got_focus ui::Action Keyboard focus arrived on the control, or on anything inside it, from outside it. A control with nothing focusable in it never gets focus, and never raises this.
on_lost_focus ui::Action Keyboard focus left the control and everything inside it.
on_key_down ui::Callback<ui::Handling(const ui::KeyEvent&)> A key pressed while focus is on the control or inside it, before the control's own handling; ui::Handled stops it there (Element::on_key_down()).
on_wheel ui::Callback<ui::Handling(const ui::PointerEvent&)> A wheel turn over the control, before the control scrolls; ui::Handled stops it there (Element::on_wheel()).
control ui::ControlOut<ui::ColorPicker> Out: the handle that receives the built control.

Plus the common fields style, margin, width, height, min_width, min_height, max_width, max_height, horizontal_alignment, vertical_alignment, visible, tool_tip, enabled, accessibility every spec carries (conventions).

build

std::unique_ptr<Element> build()&&

Consumes the spec and returns the built ui::ColorPickerControl; the caller owns it.

ui::ColorPickerControl

Derives from ui::Element.

A hue/saturation/value/alpha color editor with channel, alpha and hex text entry and a preview swatch.

A container of independently focusable sub-controls, laid out vertically from the top: hue and saturation sliders beside the preview (standing in for the 2D spectrum, which is not drawn), the value slider, the alpha slider, an optional More button, then the text entry section (an RGB/HSV dropdown and a #RRGGBB hex box, three channel boxes and an Opacity box). Tab walks the parts in that order; the picker itself is never focused.

Every part has a *_visible switch. A hidden part is not laid out, drawn, hit-tested or reachable by Tab, and showing it again needs no rebuild. A channel box applies a valid value as it is typed (0-255 for RGB, 0-359 hue, 0-100 saturation, value and opacity); Enter reverts invalid text. The hex box commits on Enter only: 6 or 8 hex digits (#RRGGBBAA, '#' optional, case-insensitive); anything else reverts.

Example:

auto row = std::make_unique<ui::StackControl>(ui::Orientation::Horizontal);
ui::ColorPickerControl* picker = row->add(std::make_unique<ui::ColorPickerControl>(ui::Color::from_rgba(0, 120, 215),
                                                                            /*alpha_enabled=*/true));
picker->set_more_button_visible(true); // text entry collapsed behind "More"
ui::BorderControl* swatch = row->add(std::make_unique<ui::BorderControl>());
swatch->set_width(100.0f);
swatch->set_height(100.0f);
swatch->set_background(picker->color());
picker->on_color_changed([swatch](ui::Color c) { swatch->set_background(c); });
window.set_content(std::move(row));

// Declaratively, two-way through a Property (the equality gate stops the echo):
ui::Property<ui::Color> accent{ui::Color::from_rgba(0, 120, 215)};
window.set_content(ui::HorizontalStack{
    .spacing  = 16,
    .content  = {
        ui::ColorPicker{
            .color               = accent,
            .alpha_enabled       = true,
            .more_button_visible = true,
        },
        ui::Border{.background = ui::bind(accent, [](ui::Color c) { return std::optional{c}; }),
                          .width      = 100.0f,
                          .height     = 100.0f},
    },
});

ColorPickerControl

ColorPickerControl(Color color = colors::Black, bool alpha_enabled = false)

A picker showing color; its alpha is kept only when alpha_enabled.

~ColorPickerControl

~ColorPickerControl() override

Destroys the owned sub-controls.

color

Color color() const

The selected color, computed from the sliders' HSV values on every call. Opaque while alpha_enabled() is false.

set_color

void set_color(Color color)

Forces alpha to 1 when alpha_enabled() is false; updates every slider, box and the preview. Fires on_color_changed only if the resulting color differs from the current one.

alpha_enabled

bool alpha_enabled() const

Whether the alpha channel is shown and honored; false by default. While false, the alpha slider and box are hidden regardless of their own switches and the color is opaque.

set_alpha_enabled

void set_alpha_enabled(bool enabled)

Turns the alpha channel on or off. Turning it off makes the color opaque, firing on_color_changed if that changes it.

spectrum_visible

bool spectrum_visible() const

Whether the hue and saturation sliders show; true by default.

set_spectrum_visible

void set_spectrum_visible(bool visible)

Shows or hides the hue and saturation sliders standing in for the spectrum. Hiding them stretches the preview across the row at 44 DIPs tall. Updates the layout.

preview_visible

bool preview_visible() const

Whether the preview swatch shows; true by default.

set_preview_visible

void set_preview_visible(bool visible)

Shows or hides the swatch showing the current color. Updates the layout.

color_slider_visible

bool color_slider_visible() const

Whether the value slider shows; true by default.

set_color_slider_visible

void set_color_slider_visible(bool visible)

Shows or hides the value slider. Updates the layout.

alpha_slider_visible

bool alpha_slider_visible() const

Whether the alpha slider shows; true by default, but shown only while alpha_enabled() too.

set_alpha_slider_visible

void set_alpha_slider_visible(bool visible)

Shows or hides the alpha slider, shown only while alpha_enabled() too. Updates the layout.

more_button_visible

bool more_button_visible() const

Whether the More button shows; false by default.

set_more_button_visible

void set_more_button_visible(bool visible)

Shows or hides the More button. While shown, the text entry section is collapsed until the button is clicked. Updates the layout.

color_channel_text_input_visible

bool color_channel_text_input_visible() const

Whether the channel inputs show; true by default.

set_color_channel_text_input_visible

void set_color_channel_text_input_visible(bool visible)

Shows or hides the RGB/HSV dropdown and the three channel boxes. Hiding them moves the hex box to the left. Updates the layout.

alpha_text_input_visible

bool alpha_text_input_visible() const

Whether the Opacity box shows; true by default, but shown only while alpha_enabled() too.

set_alpha_text_input_visible

void set_alpha_text_input_visible(bool visible)

Shows or hides the Opacity box, shown only while alpha_enabled() too. Updates the layout.

hex_input_visible

bool hex_input_visible() const

Whether the #RRGGBB box shows; true by default.

set_hex_input_visible

void set_hex_input_visible(bool visible)

Shows or hides the #RRGGBB box. Updates the layout.

more_expanded

bool more_expanded() const

Whether the text entry section is expanded behind the More button. Meaningless while the button is hidden, when the section is always shown.

on_color_changed

void on_color_changed(Callback<void(Color)> handler)

Fired with the new color whenever color changes, from a slider drag, a click-to-jump on any channel's track, a keyboard step, a typed channel value, a committed hex edit or set_color() alike. Replaces any previous handler.

for_each_child

void for_each_child(ChildVisitor visit) const override

Lists the shown sub-controls in Tab order, so a hidden part gets no focus, input, animation or automation. The picker itself is never a tab stop.

measure_override

Size measure_override(Size available, const MeasureContext& context) const override

Stacks the shown parts at the available width; the height follows from what is shown.

arrange_override

void arrange_override(const Rect& final_rect, const ArrangeContext& context) override

Places every shown part.

render_override

void render_override(Renderer& renderer, const RenderContext& context) const override

Draws the shown sub-controls, the channel labels and the preview swatch.

clear_dirty_override

void clear_dirty_override(DirtyFlags flags) override

Reaches every sub-control, shown or not, unlike for_each_child(), so a part that comes back into view does not carry flags from before it was hidden.

enabled_changed

void enabled_changed() override

is_enabled() flipped, through set_enabled() here or on an ancestor, or a move to another parent. Drop any press or hover state. The element is redrawn either way. No-op by default.

Tests

  • tests/test_color_picker.cpp