yororen_ui 0.2.0

Reusable UI components and widgets built on top of gpui.
Documentation
use gpui::{
    Div, ElementId, Hsla, InteractiveElement, IntoElement, MouseButton, MouseDownEvent,
    MouseMoveEvent, ParentElement, RenderOnce, Styled, div, prelude::FluentBuilder,
};

use crate::theme::{ActionVariantKind, ActiveTheme};

/// Creates a new drag handle element.
pub fn drag_handle(id: impl Into<ElementId>) -> DragHandle {
    DragHandle::new().id(id)
}

type DragMoveFn = Box<dyn Fn(&MouseMoveEvent, &mut gpui::Window, &mut gpui::App)>;

type DragStartFn = Box<dyn Fn(&MouseDownEvent, &mut gpui::Window, &mut gpui::App)>;

type DragEndFn = Box<dyn Fn(&MouseMoveEvent, &mut gpui::Window, &mut gpui::App)>;

#[derive(IntoElement)]
pub struct DragHandle {
    element_id: ElementId,
    base: Div,

    on_drag_start: Option<DragStartFn>,
    on_drag_move: Option<DragMoveFn>,
    on_drag_end: Option<DragEndFn>,

    enabled: bool,
    dragging: bool,
    variant: ActionVariantKind,

    bg: Option<Hsla>,
    hover_bg: Option<Hsla>,
}

impl Default for DragHandle {
    fn default() -> Self {
        Self::new()
    }
}

impl DragHandle {
    pub fn new() -> Self {
        Self {
            element_id: "ui:drag-handle".into(),
            base: div(),

            on_drag_start: None,
            on_drag_move: None,
            on_drag_end: None,

            enabled: true,
            dragging: false,
            variant: ActionVariantKind::Neutral,

            bg: None,
            hover_bg: None,
        }
    }

    pub fn id(mut self, id: impl Into<ElementId>) -> Self {
        self.element_id = id.into();
        self
    }

    /// Alias for `id(...)`. Use `key(...)` when you want to emphasize state identity.
    pub fn key(self, key: impl Into<ElementId>) -> Self {
        self.id(key)
    }

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

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

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

    pub fn on_drag_start<F>(mut self, listener: F) -> Self
    where
        F: 'static + Fn(&MouseDownEvent, &mut gpui::Window, &mut gpui::App),
    {
        self.on_drag_start = Some(Box::new(listener));
        self
    }

    pub fn on_drag_move<F>(mut self, listener: F) -> Self
    where
        F: 'static + Fn(&MouseMoveEvent, &mut gpui::Window, &mut gpui::App),
    {
        self.on_drag_move = Some(Box::new(listener));
        self
    }

    pub fn on_drag_end<F>(mut self, listener: F) -> Self
    where
        F: 'static + Fn(&MouseMoveEvent, &mut gpui::Window, &mut gpui::App),
    {
        self.on_drag_end = Some(Box::new(listener));
        self
    }

    pub fn bg(mut self, fill: impl Into<Hsla>) -> Self {
        self.bg = Some(fill.into());
        self
    }

    pub fn hover_bg(mut self, fill: impl Into<Hsla>) -> Self {
        self.hover_bg = Some(fill.into());
        self
    }
}

impl ParentElement for DragHandle {
    fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {
        self.base.extend(elements);
    }
}

impl Styled for DragHandle {
    fn style(&mut self) -> &mut gpui::StyleRefinement {
        self.base.style()
    }
}

impl RenderOnce for DragHandle {
    fn render(self, _window: &mut gpui::Window, _cx: &mut gpui::App) -> impl IntoElement {
        let enabled = self.enabled;
        let dragging = self.dragging;
        let on_drag_start = self.on_drag_start;
        let on_drag_move = self.on_drag_move;
        let on_drag_end = self.on_drag_end;
        let bg = self.bg;
        let hover_bg = self.hover_bg;
        let variant = self.variant;

        let action_variant = _cx.theme().action_variant(variant);
        let hover_bg = hover_bg.unwrap_or(action_variant.hover_bg);
        let mut resolved_bg = bg.unwrap_or(action_variant.bg);
        let mut resolved_hover_bg = hover_bg;

        if !enabled {
            resolved_bg = action_variant.disabled_bg;
            resolved_hover_bg = action_variant.disabled_bg;
        }

        self.base
            .id(self.element_id)
            .when(enabled, |this| this.cursor_grab())
            .when(dragging, |this| this.cursor_grabbing())
            .bg(resolved_bg)
            .hover(move |this| this.bg(resolved_hover_bg))
            .on_mouse_down(MouseButton::Left, move |ev, window, cx| {
                if enabled && let Some(handler) = &on_drag_start {
                    handler(ev, window, cx);
                }
            })
            .on_mouse_move(move |ev, window, cx| {
                if enabled && ev.dragging() {
                    if let Some(handler) = &on_drag_move {
                        handler(ev, window, cx);
                    }
                } else if enabled && let Some(handler) = &on_drag_end {
                    handler(ev, window, cx);
                }
            })
    }
}