gpuikit 0.4.0

A UI toolkit for GPUI applications
Documentation
use crate::theme::{ActiveTheme, Themeable};
use crate::{layout::h_stack, traits, traits::disableable::Disableable};
use gpui::{
    prelude::FluentBuilder, rems, AnyView, App, ClickEvent, ElementId, FontWeight,
    InteractiveElement, IntoElement, MouseButton, ParentElement, RenderOnce, SharedString,
    StatefulInteractiveElement, Styled, Window,
};

pub fn button(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Button {
    let label = label.into();
    let id = id.into();
    Button::new(id, label)
}

// todo: style through ButtonVariant
#[derive(Default)]
pub enum ButtonVariant {
    #[default]
    Filled,
}

#[derive(IntoElement)]
pub struct Button {
    id: ElementId,
    label: SharedString,
    disabled: bool,
    handler: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
    tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>>,
}

impl Button {
    pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
        let id = id.into();
        let label = label.into();

        Button {
            id,
            label,
            disabled: false,
            handler: None,
            tooltip: None,
        }
    }

    pub fn on_click(
        mut self,
        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
    ) -> Self {
        self.handler = Some(Box::new(handler));
        self
    }

    pub fn disabled(mut self, disabled: bool) -> Self {
        self.disabled = disabled;
        self
    }

    pub fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
        self.tooltip = Some(Box::new(tooltip));
        self
    }
}

impl RenderOnce for Button {
    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
        let theme = cx.theme();

        h_stack()
            .id(self.id)
            .h(rems(1.0))
            .px(rems(0.5))
            .gap(rems(0.25))
            .flex_none()
            .items_center()
            .justify_center()
            .rounded(rems(0.25))
            .text_xs()
            .font_weight(FontWeight::MEDIUM)
            .bg(theme.button_bg())
            .text_color(theme.fg())
            .whitespace_nowrap()
            .when(!self.disabled, |button| {
                button
                    .hover(|div| div.bg(theme.button_bg_hover()))
                    .active(|div| div.bg(theme.button_bg_active()))
                    .cursor_pointer()
            })
            .when(self.disabled, |button| {
                button
                    .opacity(0.65)
                    .cursor_not_allowed()
                    .text_color(theme.fg_muted())
            })
            .when_some(
                self.handler.filter(|_| !self.disabled),
                |button, handler| {
                    button
                        .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
                        .on_click(move |event, window, cx| {
                            cx.stop_propagation();
                            handler(event, window, cx)
                        })
                },
            )
            .when_some(self.tooltip, |button, tooltip| button.tooltip(tooltip))
            .child(self.label)
    }
}

impl traits::clickable::Clickable for Button {
    fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
        self.handler = Some(Box::new(handler));
        self
    }
}

impl traits::button::Button for Button {
    type Variant = ButtonVariant;

    fn variant(&self) -> Self::Variant {
        ButtonVariant::default()
    }
}

impl Disableable for Button {
    fn is_disabled(&self) -> bool {
        self.disabled
    }

    fn disabled(mut self, disabled: bool) -> Self {
        self.disabled = disabled;
        self
    }
}