fyrox-ui 0.20.0

Extendable UI library
Documentation
use crate::{
    border::BorderBuilder,
    brush::{Brush, GradientPoint},
    core::{algebra::Vector2, pool::Handle},
    decorator::DecoratorBuilder,
    define_constructor,
    message::{MessageDirection, UiMessage},
    text::TextBuilder,
    ttf::SharedFont,
    widget::{Widget, WidgetBuilder, WidgetMessage},
    BuildContext, Control, HorizontalAlignment, NodeHandleMapping, Thickness, UiNode,
    UserInterface, VerticalAlignment, BRUSH_LIGHT, BRUSH_LIGHTER, BRUSH_LIGHTEST, COLOR_DARKEST,
    COLOR_LIGHTEST,
};
use std::{
    any::{Any, TypeId},
    ops::{Deref, DerefMut},
};

#[derive(Debug, Clone, PartialEq)]
pub enum ButtonMessage {
    Click,
    Content(ButtonContent),
}

impl ButtonMessage {
    define_constructor!(ButtonMessage:Click => fn click(), layout: false);
    define_constructor!(ButtonMessage:Content => fn content(ButtonContent), layout: false);
}

#[derive(Clone)]
pub struct Button {
    pub widget: Widget,
    pub decorator: Handle<UiNode>,
    pub content: Handle<UiNode>,
}

crate::define_widget_deref!(Button);

impl Control for Button {
    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.content);
        node_map.resolve(&mut self.decorator);
    }

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

        if let Some(msg) = message.data::<WidgetMessage>() {
            if message.destination() == self.handle()
                || self.has_descendant(message.destination(), ui)
            {
                match msg {
                    WidgetMessage::MouseUp { .. } => {
                        ui.send_message(ButtonMessage::click(
                            self.handle(),
                            MessageDirection::FromWidget,
                        ));
                        ui.release_mouse_capture();
                        message.set_handled(true);
                    }
                    WidgetMessage::MouseDown { .. } => {
                        ui.capture_mouse(message.destination());
                        message.set_handled(true);
                    }
                    _ => (),
                }
            }
        } else if let Some(msg) = message.data::<ButtonMessage>() {
            if message.destination() == self.handle() {
                match msg {
                    ButtonMessage::Click => (),
                    ButtonMessage::Content(content) => {
                        if self.content.is_some() {
                            ui.send_message(WidgetMessage::remove(
                                self.content,
                                MessageDirection::ToWidget,
                            ));
                        }
                        self.content = content.build(&mut ui.build_ctx());
                        ui.send_message(WidgetMessage::link(
                            self.content,
                            MessageDirection::ToWidget,
                            self.decorator,
                        ));
                    }
                }
            }
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum ButtonContent {
    Text {
        text: String,
        font: Option<SharedFont>,
    },
    Node(Handle<UiNode>),
}

impl ButtonContent {
    pub fn text<S: AsRef<str>>(s: S) -> Self {
        Self::Text {
            text: s.as_ref().to_owned(),
            font: None,
        }
    }

    pub fn text_with_font<S: AsRef<str>>(s: S, font: SharedFont) -> Self {
        Self::Text {
            text: s.as_ref().to_owned(),
            font: Some(font),
        }
    }

    pub fn node(node: Handle<UiNode>) -> Self {
        Self::Node(node)
    }

    fn build(&self, ctx: &mut BuildContext) -> Handle<UiNode> {
        match self {
            Self::Text { text, font } => TextBuilder::new(WidgetBuilder::new())
                .with_text(text)
                .with_horizontal_text_alignment(HorizontalAlignment::Center)
                .with_vertical_text_alignment(VerticalAlignment::Center)
                .with_font(font.clone().unwrap_or_else(|| ctx.ui.default_font.clone()))
                .build(ctx),
            Self::Node(node) => *node,
        }
    }
}

pub struct ButtonBuilder {
    widget_builder: WidgetBuilder,
    content: Option<ButtonContent>,
    back: Option<Handle<UiNode>>,
}

impl ButtonBuilder {
    pub fn new(widget_builder: WidgetBuilder) -> Self {
        Self {
            widget_builder,
            content: None,
            back: None,
        }
    }

    pub fn with_text(mut self, text: &str) -> Self {
        self.content = Some(ButtonContent::text(text));
        self
    }

    pub fn with_text_and_font(mut self, text: &str, font: SharedFont) -> Self {
        self.content = Some(ButtonContent::text_with_font(text, font));
        self
    }

    pub fn with_content(mut self, node: Handle<UiNode>) -> Self {
        self.content = Some(ButtonContent::Node(node));
        self
    }

    pub fn with_back(mut self, decorator: Handle<UiNode>) -> Self {
        self.back = Some(decorator);
        self
    }

    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
        let content = self.content.map(|c| c.build(ctx)).unwrap_or_default();

        let back = self.back.unwrap_or_else(|| {
            DecoratorBuilder::new(
                BorderBuilder::new(
                    WidgetBuilder::new()
                        .with_foreground(Brush::LinearGradient {
                            from: Vector2::new(0.5, 0.0),
                            to: Vector2::new(0.5, 1.0),
                            stops: vec![
                                GradientPoint {
                                    stop: 0.0,
                                    color: COLOR_LIGHTEST,
                                },
                                GradientPoint {
                                    stop: 0.25,
                                    color: COLOR_LIGHTEST,
                                },
                                GradientPoint {
                                    stop: 1.0,
                                    color: COLOR_DARKEST,
                                },
                            ],
                        })
                        .with_child(content),
                )
                .with_stroke_thickness(Thickness::uniform(1.0)),
            )
            .with_normal_brush(BRUSH_LIGHT)
            .with_hover_brush(BRUSH_LIGHTER)
            .with_pressed_brush(BRUSH_LIGHTEST)
            .build(ctx)
        });

        if content.is_some() {
            ctx.link(content, back);
        }

        let button = Button {
            widget: self.widget_builder.with_child(back).build(),
            decorator: back,
            content,
        };
        ctx.add_node(UiNode::new(button))
    }
}