rg3d-ui 0.15.0

Extendable UI library
Documentation
use crate::{
    border::BorderBuilder,
    brush::Brush,
    core::{
        algebra::Vector2,
        color::{Color, Hsv},
        math::Rect,
        pool::Handle,
    },
    define_constructor,
    draw::{CommandTexture, Draw, DrawingContext},
    grid::{Column, GridBuilder, Row},
    message::{MessageDirection, MouseButton, UiMessage},
    numeric::{NumericUpDownBuilder, NumericUpDownMessage},
    popup::{Placement, PopupBuilder, PopupMessage},
    text::TextBuilder,
    widget::{Widget, WidgetBuilder, WidgetMessage},
    BuildContext, Control, NodeHandleMapping, Orientation, Thickness, UiNode, UserInterface,
    VerticalAlignment,
};
use std::sync::mpsc::Sender;
use std::{
    any::{Any, TypeId},
    ops::{Deref, DerefMut},
};

#[derive(Debug, Clone, PartialEq)]
pub enum HueBarMessage {
    /// Sets new hue value.
    Hue(f32),

    /// Sets new orientation
    Orientation(Orientation),
}

impl HueBarMessage {
    define_constructor!(HueBarMessage:Hue => fn hue(f32), layout: false);
    define_constructor!(HueBarMessage:Orientation => fn orientation(Orientation), layout: false);
}

#[derive(Debug, Clone, PartialEq)]
pub enum AlphaBarMessage {
    /// Sets new hue value.
    Alpha(f32),

    /// Sets new orientation
    Orientation(Orientation),
}

impl AlphaBarMessage {
    define_constructor!(AlphaBarMessage:Alpha => fn alpha(f32), layout: false);
    define_constructor!(AlphaBarMessage:Orientation => fn orientation(Orientation), layout: false);
}

#[derive(Debug, Clone, PartialEq)]
pub enum SaturationBrightnessFieldMessage {
    /// Sets new hue value on the field.
    Hue(f32),

    /// Sets new saturation value on the field.
    Saturation(f32),

    /// Sets new brightness value on the field.
    Brightness(f32),
}

impl SaturationBrightnessFieldMessage {
    define_constructor!(SaturationBrightnessFieldMessage:Hue => fn hue(f32), layout: false);
    define_constructor!(SaturationBrightnessFieldMessage:Saturation => fn saturation(f32), layout: false);
    define_constructor!(SaturationBrightnessFieldMessage:Brightness => fn brightness(f32), layout: false);
}

#[derive(Debug, Clone, PartialEq)]
pub enum ColorPickerMessage {
    /// Sets color in RGB.
    ///
    /// Direction: **To/From Widget**.
    Color(Color),

    /// Sets color in HSV.
    ///
    /// Direction: **To Widget**.
    Hsv(Hsv),
}

impl ColorPickerMessage {
    define_constructor!(ColorPickerMessage:Color => fn color(Color), layout: false);
    define_constructor!(ColorPickerMessage:Hsv => fn hsv(Hsv), layout: false);
}

#[derive(Debug, Clone, PartialEq)]
pub enum ColorFieldMessage {
    Color(Color),
}

impl ColorFieldMessage {
    define_constructor!(ColorFieldMessage:Color => fn color(Color), layout: false);
}

#[derive(Clone)]
pub struct AlphaBar {
    widget: Widget,
    orientation: Orientation,
    alpha: f32,
    is_picking: bool,
}

crate::define_widget_deref!(AlphaBar);

impl AlphaBar {
    fn alpha_at(&self, mouse_pos: Vector2<f32>) -> f32 {
        let relative_pos = mouse_pos - self.screen_position;
        let k = match self.orientation {
            Orientation::Vertical => relative_pos.y / self.actual_size().y,
            Orientation::Horizontal => relative_pos.x / self.actual_size().x,
        };
        k.min(1.0).max(0.0) * 255.0
    }
}

fn push_gradient_rect(
    drawing_context: &mut DrawingContext,
    bounds: &Rect<f32>,
    orientation: Orientation,
    prev_k: f32,
    prev_color: Color,
    curr_k: f32,
    curr_color: Color,
) {
    match orientation {
        Orientation::Vertical => {
            drawing_context.push_triangle_multicolor([
                (
                    Vector2::new(bounds.x(), bounds.y() + bounds.h() * prev_k),
                    prev_color,
                ),
                (
                    Vector2::new(bounds.x() + bounds.w(), bounds.y() + bounds.h() * prev_k),
                    prev_color,
                ),
                (
                    Vector2::new(bounds.x(), bounds.y() + bounds.h() * curr_k),
                    curr_color,
                ),
            ]);
            drawing_context.push_triangle_multicolor([
                (
                    Vector2::new(bounds.x() + bounds.w(), bounds.y() + bounds.h() * prev_k),
                    prev_color,
                ),
                (
                    Vector2::new(bounds.x() + bounds.w(), bounds.y() + bounds.h() * curr_k),
                    curr_color,
                ),
                (
                    Vector2::new(bounds.x(), bounds.y() + bounds.h() * curr_k),
                    curr_color,
                ),
            ]);
        }
        Orientation::Horizontal => {
            drawing_context.push_triangle_multicolor([
                (
                    Vector2::new(bounds.x() + bounds.w() * prev_k, bounds.y()),
                    prev_color,
                ),
                (
                    Vector2::new(bounds.x() + bounds.w() * curr_k, bounds.y()),
                    curr_color,
                ),
                (
                    Vector2::new(bounds.x() + bounds.w() * prev_k, bounds.y() + bounds.h()),
                    prev_color,
                ),
            ]);
            drawing_context.push_triangle_multicolor([
                (
                    Vector2::new(bounds.x() + bounds.w() * curr_k, bounds.y()),
                    curr_color,
                ),
                (
                    Vector2::new(bounds.x() + bounds.w() * curr_k, bounds.y() + bounds.h()),
                    curr_color,
                ),
                (
                    Vector2::new(bounds.x() + bounds.w() * prev_k, bounds.y() + bounds.h()),
                    prev_color,
                ),
            ]);
        }
    }
}

const CHECKERBOARD_SIZE: f32 = 6.0;

impl Control for AlphaBar {
    fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
        if type_id == TypeId::of::<Self>() {
            Some(self)
        } else {
            None
        }
    }

    fn draw(&self, drawing_context: &mut DrawingContext) {
        let bounds = self.screen_bounds();

        // Draw checker board first.
        let h_amount = (bounds.w() / CHECKERBOARD_SIZE).ceil() as usize;
        let v_amount = (bounds.h() / CHECKERBOARD_SIZE).ceil() as usize;
        for y in 0..v_amount {
            for x in 0..h_amount {
                let rect = Rect::new(
                    bounds.x() + x as f32 * CHECKERBOARD_SIZE,
                    bounds.y() + y as f32 * CHECKERBOARD_SIZE,
                    CHECKERBOARD_SIZE,
                    CHECKERBOARD_SIZE,
                );
                let color = if (x + y) & 1 == 0 {
                    Color::opaque(127, 127, 127)
                } else {
                    Color::WHITE
                };
                drawing_context.push_rect_multicolor(&rect, [color; 4]);
            }
        }
        drawing_context.commit(
            self.clip_bounds(),
            Brush::Solid(Color::WHITE),
            CommandTexture::None,
            None,
        );

        // Then draw alpha gradient.
        for alpha in 1..255 {
            let prev_color = Color::from_rgba(0, 0, 0, alpha - 1);
            let curr_color = Color::from_rgba(0, 0, 0, alpha);
            let prev_k = (alpha - 1) as f32 / 255.0;
            let curr_k = alpha as f32 / 255.0;
            push_gradient_rect(
                drawing_context,
                &bounds,
                self.orientation,
                prev_k,
                prev_color,
                curr_k,
                curr_color,
            );
        }

        let k = self.alpha / 255.0;
        match self.orientation {
            Orientation::Vertical => drawing_context.push_rect_multicolor(
                &Rect::new(bounds.x(), bounds.y() + bounds.h() * k, bounds.w(), 1.0),
                [Color::WHITE; 4],
            ),
            Orientation::Horizontal => drawing_context.push_rect_multicolor(
                &Rect::new(bounds.x() + k * bounds.w(), bounds.y(), 1.0, bounds.h()),
                [Color::WHITE; 4],
            ),
        }

        drawing_context.commit(
            self.clip_bounds(),
            Brush::Solid(Color::WHITE),
            CommandTexture::None,
            None,
        );
    }

    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
        self.widget.handle_routed_message(ui, message);

        if message.destination() == self.handle {
            if let Some(msg) = message.data::<WidgetMessage>() {
                if message.direction() == MessageDirection::FromWidget {
                    match *msg {
                        WidgetMessage::MouseDown { button, .. } => {
                            if button == MouseButton::Left {
                                self.is_picking = true;
                                ui.capture_mouse(self.handle);
                            }
                        }
                        WidgetMessage::MouseMove { pos, .. } => {
                            if self.is_picking {
                                ui.send_message(AlphaBarMessage::alpha(
                                    self.handle,
                                    MessageDirection::ToWidget,
                                    self.alpha_at(pos),
                                ))
                            }
                        }
                        WidgetMessage::MouseUp { button, .. } => {
                            if self.is_picking && button == MouseButton::Left {
                                self.is_picking = false;
                                ui.release_mouse_capture();
                            }
                        }
                        _ => (),
                    }
                }
            } else if let Some(msg) = message.data::<AlphaBarMessage>() {
                if message.direction() == MessageDirection::ToWidget {
                    match *msg {
                        AlphaBarMessage::Alpha(alpha) => {
                            if self.alpha != alpha {
                                self.alpha = alpha;
                                ui.send_message(message.reverse());
                            }
                        }
                        AlphaBarMessage::Orientation(orientation) => {
                            if self.orientation != orientation {
                                self.orientation = orientation;
                                ui.send_message(message.reverse());
                            }
                        }
                    }
                }
            }
        }
    }
}

pub struct AlphaBarBuilder {
    widget_builder: WidgetBuilder,
    orientation: Orientation,
    alpha: f32,
}

impl AlphaBarBuilder {
    pub fn new(widget_builder: WidgetBuilder) -> Self {
        Self {
            widget_builder,
            orientation: Orientation::Vertical,
            alpha: 255.0,
        }
    }

    pub fn with_alpha(mut self, alpha: f32) -> Self {
        self.alpha = alpha;
        self
    }

    pub fn build(self, ui: &mut BuildContext) -> Handle<UiNode> {
        let canvas = AlphaBar {
            widget: self.widget_builder.build(),
            orientation: self.orientation,
            alpha: self.alpha,
            is_picking: false,
        };
        ui.add_node(UiNode::new(canvas))
    }
}

#[derive(Clone)]
pub struct HueBar {
    widget: Widget,
    orientation: Orientation,
    is_picking: bool,
    hue: f32,
}

crate::define_widget_deref!(HueBar);

impl HueBar {
    fn hue_at(&self, mouse_pos: Vector2<f32>) -> f32 {
        let relative_pos = mouse_pos - self.screen_position;
        let k = match self.orientation {
            Orientation::Vertical => relative_pos.y / self.actual_size().y,
            Orientation::Horizontal => relative_pos.x / self.actual_size().x,
        };
        k.min(1.0).max(0.0) * 360.0
    }
}

impl Control for HueBar {
    fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
        if type_id == TypeId::of::<Self>() {
            Some(self)
        } else {
            None
        }
    }

    fn draw(&self, drawing_context: &mut DrawingContext) {
        let bounds = self.screen_bounds();
        for hue in 1..360 {
            let prev_color = Color::from(Hsv::new((hue - 1) as f32, 100.0, 100.0));
            let curr_color = Color::from(Hsv::new(hue as f32, 100.0, 100.0));
            let prev_k = (hue - 1) as f32 / 360.0;
            let curr_k = hue as f32 / 360.0;
            push_gradient_rect(
                drawing_context,
                &bounds,
                self.orientation,
                prev_k,
                prev_color,
                curr_k,
                curr_color,
            );
        }

        let k = self.hue / 360.0;
        match self.orientation {
            Orientation::Vertical => drawing_context.push_rect_multicolor(
                &Rect::new(bounds.x(), bounds.y() + bounds.h() * k, bounds.w(), 1.0),
                [Color::BLACK; 4],
            ),
            Orientation::Horizontal => drawing_context.push_rect_multicolor(
                &Rect::new(bounds.x() + k * bounds.w(), bounds.y(), 1.0, bounds.h()),
                [Color::BLACK; 4],
            ),
        }

        drawing_context.commit(
            self.clip_bounds(),
            Brush::Solid(Color::WHITE),
            CommandTexture::None,
            None,
        );
    }

    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
        self.widget.handle_routed_message(ui, message);

        if message.destination() == self.handle {
            if let Some(msg) = message.data::<WidgetMessage>() {
                if message.direction() == MessageDirection::FromWidget {
                    match *msg {
                        WidgetMessage::MouseDown { button, .. } => {
                            if button == MouseButton::Left {
                                self.is_picking = true;
                                ui.capture_mouse(self.handle);
                            }
                        }
                        WidgetMessage::MouseMove { pos, .. } => {
                            if self.is_picking {
                                ui.send_message(HueBarMessage::hue(
                                    self.handle,
                                    MessageDirection::ToWidget,
                                    self.hue_at(pos),
                                ))
                            }
                        }
                        WidgetMessage::MouseUp { button, .. } => {
                            if self.is_picking && button == MouseButton::Left {
                                self.is_picking = false;
                                ui.release_mouse_capture();
                            }
                        }
                        _ => (),
                    }
                }
            } else if let Some(msg) = message.data::<HueBarMessage>() {
                if message.direction() == MessageDirection::ToWidget {
                    match *msg {
                        HueBarMessage::Hue(hue) => {
                            if self.hue != hue {
                                self.hue = hue;
                                ui.send_message(message.reverse());
                            }
                        }
                        HueBarMessage::Orientation(orientation) => {
                            if self.orientation != orientation {
                                self.orientation = orientation;
                                ui.send_message(message.reverse());
                            }
                        }
                    }
                }
            }
        }
    }
}

pub struct HueBarBuilder {
    widget_builder: WidgetBuilder,
    orientation: Orientation,
    hue: f32,
}

impl HueBarBuilder {
    pub fn new(widget_builder: WidgetBuilder) -> Self {
        Self {
            widget_builder,
            orientation: Orientation::Vertical,
            hue: 0.0, // Red
        }
    }

    pub fn with_hue(mut self, hue: f32) -> Self {
        self.hue = hue;
        self
    }

    pub fn with_orientation(mut self, orientation: Orientation) -> Self {
        self.orientation = orientation;
        self
    }

    pub fn build(self, ui: &mut BuildContext) -> Handle<UiNode> {
        let bar = HueBar {
            widget: self.widget_builder.build(),
            orientation: self.orientation,
            is_picking: false,
            hue: self.hue,
        };
        ui.add_node(UiNode::new(bar))
    }
}

#[derive(Clone)]
pub struct SaturationBrightnessField {
    widget: Widget,
    is_picking: bool,
    hue: f32,
    saturation: f32,
    brightness: f32,
}

crate::define_widget_deref!(SaturationBrightnessField);

impl SaturationBrightnessField {
    fn saturation_at(&self, mouse_pos: Vector2<f32>) -> f32 {
        ((mouse_pos.x - self.screen_position.x) / self.screen_bounds().w())
            .min(1.0)
            .max(0.0)
            * 100.0
    }

    fn brightness_at(&self, mouse_pos: Vector2<f32>) -> f32 {
        100.0
            - ((mouse_pos.y - self.screen_position.y) / self.screen_bounds().h())
                .min(1.0)
                .max(0.0)
                * 100.0
    }
}

impl Control for SaturationBrightnessField {
    fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
        if type_id == TypeId::of::<Self>() {
            Some(self)
        } else {
            None
        }
    }

    fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
        let size = self.deref().arrange_override(ui, final_size);
        // Make sure field is always square.
        ui.send_message(WidgetMessage::width(
            self.handle,
            MessageDirection::ToWidget,
            final_size.y,
        ));
        size
    }

    fn draw(&self, drawing_context: &mut DrawingContext) {
        let bounds = self.screen_bounds();

        drawing_context.push_rect_multicolor(
            &bounds,
            [
                Color::from(Hsv::new(self.hue, 0.0, 100.0)),
                Color::from(Hsv::new(self.hue, 100.0, 100.0)),
                Color::from(Hsv::new(self.hue, 100.0, 0.0)),
                Color::from(Hsv::new(self.hue, 0.0, 0.0)),
            ],
        );
        drawing_context.commit(
            self.clip_bounds(),
            Brush::Solid(Color::WHITE),
            CommandTexture::None,
            None,
        );

        // Indicator must be drawn separately, otherwise it may be drawn incorrectly.
        let origin = Vector2::new(
            bounds.x() + self.saturation / 100.0 * bounds.w(),
            bounds.y() + (100.0 - self.brightness) / 100.0 * bounds.h(),
        );
        drawing_context.push_circle(
            origin,
            3.0,
            10,
            Color::from(Hsv::new(360.0 - self.hue, 100.0, 100.0)),
        );
        drawing_context.commit(
            self.clip_bounds(),
            Brush::Solid(Color::WHITE),
            CommandTexture::None,
            None,
        );
    }

    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
        self.widget.handle_routed_message(ui, message);

        if message.destination() == self.handle {
            if let Some(msg) = message.data::<WidgetMessage>() {
                if message.direction() == MessageDirection::FromWidget {
                    match *msg {
                        WidgetMessage::MouseDown { button, .. } => {
                            if button == MouseButton::Left {
                                self.is_picking = true;
                                ui.capture_mouse(self.handle);
                            }
                        }
                        WidgetMessage::MouseMove { pos, .. } => {
                            if self.is_picking {
                                ui.send_message(SaturationBrightnessFieldMessage::brightness(
                                    self.handle,
                                    MessageDirection::ToWidget,
                                    self.brightness_at(pos),
                                ));

                                ui.send_message(SaturationBrightnessFieldMessage::saturation(
                                    self.handle,
                                    MessageDirection::ToWidget,
                                    self.saturation_at(pos),
                                ));
                            }
                        }
                        WidgetMessage::MouseUp { button, .. } => {
                            if self.is_picking && button == MouseButton::Left {
                                self.is_picking = false;
                                ui.release_mouse_capture();
                            }
                        }
                        _ => (),
                    }
                }
            } else if let Some(msg) = message.data::<SaturationBrightnessFieldMessage>() {
                if message.direction() == MessageDirection::ToWidget {
                    match *msg {
                        SaturationBrightnessFieldMessage::Hue(hue) => {
                            let clamped = hue.min(360.0).max(0.0);
                            if self.hue != clamped {
                                self.hue = clamped;
                                ui.send_message(SaturationBrightnessFieldMessage::hue(
                                    self.handle,
                                    MessageDirection::FromWidget,
                                    self.hue,
                                ));
                            }
                        }
                        SaturationBrightnessFieldMessage::Saturation(saturation) => {
                            let clamped = saturation.min(100.0).max(0.0);
                            if self.saturation != clamped {
                                self.saturation = clamped;
                                ui.send_message(SaturationBrightnessFieldMessage::saturation(
                                    self.handle,
                                    MessageDirection::FromWidget,
                                    self.saturation,
                                ));
                            }
                        }
                        SaturationBrightnessFieldMessage::Brightness(brightness) => {
                            let clamped = brightness.min(100.0).max(0.0);
                            if self.brightness != clamped {
                                self.brightness = clamped;
                                ui.send_message(SaturationBrightnessFieldMessage::brightness(
                                    self.handle,
                                    MessageDirection::FromWidget,
                                    self.brightness,
                                ));
                            }
                        }
                    }
                }
            }
        }
    }
}

pub struct SaturationBrightnessFieldBuilder {
    widget_builder: WidgetBuilder,
    hue: f32,
    saturation: f32,
    brightness: f32,
}

impl SaturationBrightnessFieldBuilder {
    pub fn new(widget_builder: WidgetBuilder) -> Self {
        Self {
            widget_builder,
            hue: 0.0,
            saturation: 100.0,
            brightness: 100.0,
        }
    }

    pub fn with_hue(mut self, hue: f32) -> Self {
        self.hue = hue;
        self
    }

    pub fn with_saturation(mut self, saturation: f32) -> Self {
        self.saturation = saturation;
        self
    }

    pub fn with_brightness(mut self, brightness: f32) -> Self {
        self.brightness = brightness;
        self
    }

    pub fn build(self, ui: &mut BuildContext) -> Handle<UiNode> {
        let bar = SaturationBrightnessField {
            widget: self.widget_builder.build(),
            is_picking: false,
            saturation: self.saturation,
            brightness: self.brightness,
            hue: self.hue,
        };
        ui.add_node(UiNode::new(bar))
    }
}

#[derive(Clone)]
pub struct ColorPicker {
    widget: Widget,
    hue_bar: Handle<UiNode>,
    alpha_bar: Handle<UiNode>,
    saturation_brightness_field: Handle<UiNode>,
    red: Handle<UiNode>,
    green: Handle<UiNode>,
    blue: Handle<UiNode>,
    alpha: Handle<UiNode>,
    hue: Handle<UiNode>,
    saturation: Handle<UiNode>,
    brightness: Handle<UiNode>,
    color_mark: Handle<UiNode>,
    color: Color,
    hsv: Hsv,
}

crate::define_widget_deref!(ColorPicker);

fn mark_handled(message: UiMessage) -> UiMessage {
    message.set_handled(true);
    message
}

impl ColorPicker {
    fn sync_fields(&self, ui: &mut UserInterface, color: Color, hsv: Hsv) {
        ui.send_message(mark_handled(NumericUpDownMessage::value(
            self.hue,
            MessageDirection::ToWidget,
            hsv.hue(),
        )));

        ui.send_message(mark_handled(NumericUpDownMessage::value(
            self.saturation,
            MessageDirection::ToWidget,
            hsv.saturation(),
        )));

        ui.send_message(mark_handled(NumericUpDownMessage::value(
            self.brightness,
            MessageDirection::ToWidget,
            hsv.brightness(),
        )));

        ui.send_message(mark_handled(NumericUpDownMessage::value(
            self.red,
            MessageDirection::ToWidget,
            color.r as f32,
        )));

        ui.send_message(mark_handled(NumericUpDownMessage::value(
            self.green,
            MessageDirection::ToWidget,
            color.g as f32,
        )));

        ui.send_message(mark_handled(NumericUpDownMessage::value(
            self.blue,
            MessageDirection::ToWidget,
            color.b as f32,
        )));

        ui.send_message(mark_handled(NumericUpDownMessage::value(
            self.alpha,
            MessageDirection::ToWidget,
            color.a as f32,
        )));

        ui.send_message(mark_handled(WidgetMessage::background(
            self.color_mark,
            MessageDirection::ToWidget,
            Brush::Solid(color),
        )));
    }
}

impl Control for ColorPicker {
    fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
        if type_id == TypeId::of::<Self>() {
            Some(self)
        } else {
            None
        }
    }

    fn resolve(&mut self, node_map: &NodeHandleMapping) {
        node_map.resolve(&mut self.hue_bar);
        node_map.resolve(&mut self.alpha_bar);
        node_map.resolve(&mut self.saturation_brightness_field);
        node_map.resolve(&mut self.red);
        node_map.resolve(&mut self.green);
        node_map.resolve(&mut self.blue);
        node_map.resolve(&mut self.alpha);
        node_map.resolve(&mut self.hue);
        node_map.resolve(&mut self.saturation);
        node_map.resolve(&mut self.brightness);
        node_map.resolve(&mut self.color_mark);
    }

    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
        self.widget.handle_routed_message(ui, message);

        if let Some(&HueBarMessage::Hue(hue)) = message.data::<HueBarMessage>() {
            if message.destination() == self.hue_bar
                && message.direction() == MessageDirection::FromWidget
            {
                ui.send_message(SaturationBrightnessFieldMessage::hue(
                    self.saturation_brightness_field,
                    MessageDirection::ToWidget,
                    hue,
                ));

                let mut hsv = self.hsv;
                hsv.set_hue(hue);
                ui.send_message(ColorPickerMessage::hsv(
                    self.handle,
                    MessageDirection::ToWidget,
                    hsv,
                ));
            }
        } else if let Some(&AlphaBarMessage::Alpha(alpha)) = message.data::<AlphaBarMessage>() {
            if message.destination() == self.alpha_bar
                && message.direction() == MessageDirection::FromWidget
            {
                ui.send_message(ColorPickerMessage::color(
                    self.handle,
                    MessageDirection::ToWidget,
                    Color::from_rgba(self.color.r, self.color.g, self.color.b, alpha as u8),
                ));
            }
        } else if let Some(msg) = message.data::<SaturationBrightnessFieldMessage>() {
            if message.destination() == self.saturation_brightness_field
                && message.direction() == MessageDirection::FromWidget
            {
                match *msg {
                    SaturationBrightnessFieldMessage::Brightness(brightness) => {
                        let mut hsv = self.hsv;
                        hsv.set_brightness(brightness);
                        ui.send_message(ColorPickerMessage::hsv(
                            self.handle,
                            MessageDirection::ToWidget,
                            hsv,
                        ));
                    }
                    SaturationBrightnessFieldMessage::Saturation(saturation) => {
                        let mut hsv = self.hsv;
                        hsv.set_saturation(saturation);
                        ui.send_message(ColorPickerMessage::hsv(
                            self.handle,
                            MessageDirection::ToWidget,
                            hsv,
                        ));
                    }
                    _ => {}
                }
            }
        } else if let Some(&NumericUpDownMessage::Value(value)) =
            message.data::<NumericUpDownMessage<f32>>()
        {
            if message.direction() == MessageDirection::FromWidget && !message.handled() {
                if message.destination() == self.hue {
                    ui.send_message(HueBarMessage::hue(
                        self.hue_bar,
                        MessageDirection::ToWidget,
                        value,
                    ));
                } else if message.destination() == self.saturation {
                    ui.send_message(SaturationBrightnessFieldMessage::saturation(
                        self.saturation_brightness_field,
                        MessageDirection::ToWidget,
                        value,
                    ));
                } else if message.destination() == self.brightness {
                    ui.send_message(SaturationBrightnessFieldMessage::brightness(
                        self.saturation_brightness_field,
                        MessageDirection::ToWidget,
                        value,
                    ));
                } else if message.destination() == self.red {
                    ui.send_message(ColorPickerMessage::color(
                        self.handle,
                        MessageDirection::ToWidget,
                        Color::from_rgba(value as u8, self.color.g, self.color.b, self.color.a),
                    ));
                } else if message.destination() == self.green {
                    ui.send_message(ColorPickerMessage::color(
                        self.handle,
                        MessageDirection::ToWidget,
                        Color::from_rgba(self.color.r, value as u8, self.color.b, self.color.a),
                    ));
                } else if message.destination() == self.blue {
                    ui.send_message(ColorPickerMessage::color(
                        self.handle,
                        MessageDirection::ToWidget,
                        Color::from_rgba(self.color.r, self.color.g, value as u8, self.color.a),
                    ));
                } else if message.destination() == self.alpha {
                    ui.send_message(ColorPickerMessage::color(
                        self.handle,
                        MessageDirection::ToWidget,
                        Color::from_rgba(self.color.r, self.color.g, self.color.b, value as u8),
                    ));
                }
            }
        } else if let Some(msg) = message.data::<ColorPickerMessage>() {
            if message.destination() == self.handle
                && message.direction() == MessageDirection::ToWidget
            {
                match *msg {
                    ColorPickerMessage::Color(color) => {
                        if self.color != color {
                            self.color = color;
                            self.hsv = Hsv::from(color);

                            self.sync_fields(ui, color, self.hsv);

                            ui.send_message(message.reverse());
                        }
                    }
                    ColorPickerMessage::Hsv(hsv) => {
                        if self.hsv != hsv {
                            self.hsv = hsv;
                            let opaque = Color::from(hsv);
                            self.color =
                                Color::from_rgba(opaque.r, opaque.g, opaque.b, self.color.a);

                            self.sync_fields(ui, self.color, hsv);

                            ui.send_message(message.reverse());
                        }
                    }
                }
            }
        }
    }
}

pub struct ColorPickerBuilder {
    widget_builder: WidgetBuilder,
    color: Color,
}

fn make_text_mark(ctx: &mut BuildContext, text: &str, row: usize, column: usize) -> Handle<UiNode> {
    TextBuilder::new(
        WidgetBuilder::new()
            .with_vertical_alignment(VerticalAlignment::Center)
            .on_row(row)
            .on_column(column),
    )
    .with_text(text)
    .build(ctx)
}

fn make_input_field(
    ctx: &mut BuildContext,
    value: f32,
    max_value: f32,
    row: usize,
    column: usize,
) -> Handle<UiNode> {
    NumericUpDownBuilder::new(
        WidgetBuilder::new()
            .with_margin(Thickness::uniform(1.0))
            .on_row(row)
            .on_column(column),
    )
    .with_value(value)
    .with_min_value(0.0)
    .with_max_value(max_value)
    .with_precision(0)
    .with_step(1.0)
    .build(ctx)
}

impl ColorPickerBuilder {
    pub fn new(widget_builder: WidgetBuilder) -> Self {
        Self {
            widget_builder,
            color: Color::WHITE,
        }
    }

    pub fn with_color(mut self, color: Color) -> Self {
        self.color = color;
        self
    }

    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
        let hue_bar;
        let alpha_bar;
        let saturation_brightness_field;
        let red;
        let green;
        let blue;
        let hue;
        let saturation;
        let brightness;
        let color_mark;
        let alpha;
        let hsv = Hsv::from(self.color);

        let numerics_grid = GridBuilder::new(
            WidgetBuilder::new()
                .on_row(1)
                .with_child(make_text_mark(ctx, "R", 0, 0))
                .with_child({
                    red = make_input_field(ctx, self.color.r as f32, 255.0, 0, 1);
                    red
                })
                .with_child(make_text_mark(ctx, "G", 1, 0))
                .with_child({
                    green = make_input_field(ctx, self.color.g as f32, 255.0, 1, 1);
                    green
                })
                .with_child(make_text_mark(ctx, "B", 2, 0))
                .with_child({
                    blue = make_input_field(ctx, self.color.b as f32, 255.0, 2, 1);
                    blue
                })
                .with_child(make_text_mark(ctx, "H", 0, 2))
                .with_child({
                    hue = make_input_field(ctx, hsv.hue(), 360.0, 0, 3);
                    hue
                })
                .with_child(make_text_mark(ctx, "S", 1, 2))
                .with_child({
                    saturation = make_input_field(ctx, hsv.saturation(), 100.0, 1, 3);
                    saturation
                })
                .with_child(make_text_mark(ctx, "B", 2, 2))
                .with_child({
                    brightness = make_input_field(ctx, hsv.brightness(), 100.0, 2, 3);
                    brightness
                })
                .with_child(make_text_mark(ctx, "A", 3, 0))
                .with_child({
                    alpha = make_input_field(ctx, self.color.a as f32, 255.0, 3, 1);
                    alpha
                }),
        )
        .add_column(Column::strict(10.0))
        .add_column(Column::stretch())
        .add_column(Column::strict(10.0))
        .add_column(Column::stretch())
        .add_row(Row::strict(25.0))
        .add_row(Row::strict(25.0))
        .add_row(Row::strict(25.0))
        .add_row(Row::strict(25.0))
        .add_row(Row::stretch())
        .build(ctx);

        let widget = self
            .widget_builder
            .with_child(
                GridBuilder::new(
                    WidgetBuilder::new()
                        .with_child({
                            saturation_brightness_field = SaturationBrightnessFieldBuilder::new(
                                WidgetBuilder::new()
                                    .with_margin(Thickness::uniform(1.0))
                                    .on_column(0),
                            )
                            .build(ctx);
                            saturation_brightness_field
                        })
                        .with_child({
                            hue_bar = HueBarBuilder::new(
                                WidgetBuilder::new()
                                    .with_margin(Thickness::uniform(1.0))
                                    .on_column(1),
                            )
                            .build(ctx);
                            hue_bar
                        })
                        .with_child({
                            alpha_bar = AlphaBarBuilder::new(
                                WidgetBuilder::new()
                                    .with_margin(Thickness::uniform(1.0))
                                    .on_column(2),
                            )
                            .with_alpha(self.color.a as f32)
                            .build(ctx);
                            alpha_bar
                        })
                        .with_child(
                            GridBuilder::new(
                                WidgetBuilder::new()
                                    .on_column(3)
                                    .with_child({
                                        color_mark = BorderBuilder::new(
                                            WidgetBuilder::new()
                                                .on_row(0)
                                                .with_margin(Thickness::uniform(1.0)),
                                        )
                                        .build(ctx);
                                        color_mark
                                    })
                                    .with_child(numerics_grid),
                            )
                            .add_row(Row::strict(25.0))
                            .add_row(Row::stretch())
                            .add_column(Column::stretch())
                            .build(ctx),
                        ),
                )
                .add_column(Column::auto())
                .add_column(Column::strict(20.0))
                .add_column(Column::strict(20.0))
                .add_column(Column::stretch())
                .add_row(Row::auto())
                .build(ctx),
            )
            .build();

        let picker = ColorPicker {
            widget,
            hue_bar,
            saturation_brightness_field,
            red,
            green,
            blue,
            hue,
            saturation,
            brightness,
            color: self.color,
            color_mark,
            hsv,
            alpha_bar,
            alpha,
        };
        ctx.add_node(UiNode::new(picker))
    }
}

#[derive(Clone)]
pub struct ColorField {
    widget: Widget,
    popup: Handle<UiNode>,
    picker: Handle<UiNode>,
    color: Color,
}

crate::define_widget_deref!(ColorField);

impl Control for ColorField {
    fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
        if type_id == TypeId::of::<Self>() {
            Some(self)
        } else {
            None
        }
    }

    fn on_remove(&self, sender: &Sender<UiMessage>) {
        // Popup won't be deleted with the color field, because it is not the child of the field.
        // So we have to remove it manually.
        sender
            .send(WidgetMessage::remove(
                self.popup,
                MessageDirection::ToWidget,
            ))
            .unwrap();
    }

    fn draw(&self, drawing_context: &mut DrawingContext) {
        let bounds = self.screen_bounds();

        drawing_context.push_rect_filled(&bounds, None);
        drawing_context.commit(
            self.clip_bounds(),
            Brush::Solid(self.color),
            CommandTexture::None,
            None,
        );
    }

    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
        self.widget.handle_routed_message(ui, message);

        if let Some(&WidgetMessage::MouseDown { button, .. }) = message.data::<WidgetMessage>() {
            if message.destination() == self.handle
                && message.direction() == MessageDirection::FromWidget
                && button == MouseButton::Left
            {
                ui.send_message(WidgetMessage::width(
                    self.popup,
                    MessageDirection::ToWidget,
                    self.actual_size().x,
                ));
                ui.send_message(PopupMessage::placement(
                    self.popup,
                    MessageDirection::ToWidget,
                    Placement::LeftBottom(self.handle),
                ));
                ui.send_message(PopupMessage::open(self.popup, MessageDirection::ToWidget));
                ui.send_message(ColorPickerMessage::color(
                    self.picker,
                    MessageDirection::ToWidget,
                    self.color,
                ));
            }
        } else if let Some(&ColorFieldMessage::Color(color)) = message.data::<ColorFieldMessage>() {
            if message.destination() == self.handle
                && message.direction() == MessageDirection::ToWidget
                && self.color != color
            {
                self.color = color;
                ui.send_message(ColorPickerMessage::color(
                    self.picker,
                    MessageDirection::ToWidget,
                    self.color,
                ));
                ui.send_message(message.reverse());
            }
        }
    }

    // We have to use preview message because popup it *not* in visual tree of our control and
    // handle_routed_message won't trigger because of it.
    fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
        if let Some(PopupMessage::Close) = message.data::<PopupMessage>() {
            if message.destination() == self.popup {
                let picker = ui
                    .node(self.picker)
                    .cast::<ColorPicker>()
                    .expect("self.picker must be ColorPicker!");
                ui.send_message(ColorFieldMessage::color(
                    self.handle,
                    MessageDirection::ToWidget,
                    picker.color,
                ));
            }
        }
    }
}

pub struct ColorFieldBuilder {
    widget_builder: WidgetBuilder,
    color: Color,
}

impl ColorFieldBuilder {
    pub fn new(widget_builder: WidgetBuilder) -> Self {
        Self {
            widget_builder,
            color: Color::WHITE,
        }
    }

    pub fn with_color(mut self, color: Color) -> Self {
        self.color = color;
        self
    }

    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
        let picker;
        let popup = PopupBuilder::new(WidgetBuilder::new())
            .with_content({
                picker = ColorPickerBuilder::new(WidgetBuilder::new())
                    .with_color(self.color)
                    .build(ctx);
                picker
            })
            .build(ctx);

        let field = ColorField {
            widget: self.widget_builder.with_preview_messages(true).build(),
            popup,
            picker,
            color: self.color,
        };
        ctx.add_node(UiNode::new(field))
    }
}