guise-ui 1.5.3

A component library for gpui, Zed's GPU-accelerated UI framework: a themed palette, sizing tokens, 130+ composable components, a reactive state layer, and an in-app Safari-style inspector.
Documentation
//! `Button` — the flagship interactive control, with the library's variants,
//! colors, and sizes.

use gpui::prelude::*;
use gpui::{div, px, App, ClickEvent, ElementId, FontWeight, IntoElement, SharedString, Window};

use crate::devtools::Probed;
use crate::input::ClickHandler;
use crate::style::{surface, ColorValue, Variant};
use crate::theme::{theme, Size};

/// A clickable button.
#[derive(IntoElement)]
pub struct Button {
  id: ElementId,
  label: SharedString,
  variant: Variant,
  color: ColorValue,
  size: Size,
  radius: Option<Size>,
  full_width: bool,
  disabled: bool,
  left_section: Option<gpui::AnyElement>,
  right_section: Option<gpui::AnyElement>,
  on_click: Option<ClickHandler>,
}

impl Button {
  pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
    Button {
      id: id.into(),
      label: label.into(),
      variant: Variant::Filled,
      color: ColorValue::default(),
      size: Size::Sm,
      radius: None,
      full_width: false,
      disabled: false,
      left_section: None,
      right_section: None,
      on_click: None,
    }
  }

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

  /// Override the button color. Accepts a palette `ColorName` or any explicit
  /// color (e.g. `color!(rgba(34, 139, 230, 1))`).
  pub fn color(mut self, color: impl Into<ColorValue>) -> Self {
    self.color = color.into();
    self
  }

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

  pub fn radius(mut self, radius: Size) -> Self {
    self.radius = Some(radius);
    self
  }

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

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

  /// Content shown before the label (e.g. an icon).
  pub fn left_section(mut self, section: impl IntoElement) -> Self {
    self.left_section = Some(section.into_any_element());
    self
  }

  /// Content shown after the label.
  pub fn right_section(mut self, section: impl IntoElement) -> Self {
    self.right_section = Some(section.into_any_element());
    self
  }

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

  /// (height, horizontal padding, font size) for the size scale.
  fn metrics(&self) -> (f32, f32, f32) {
    match self.size {
      Size::Xs => (30.0, 14.0, 12.0),
      Size::Sm => (36.0, 18.0, 14.0),
      Size::Md => (42.0, 22.0, 16.0),
      Size::Lg => (50.0, 26.0, 18.0),
      Size::Xl => (60.0, 32.0, 20.0),
    }
  }
}

impl RenderOnce for Button {
  fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
    let t = theme(cx);
    let s = surface(t, self.color, self.variant);
    let (height, pad_x, font) = self.metrics();
    let radius = t.radius(self.radius.unwrap_or(t.default_radius));
    let focus = t.primary().hsla();

    let mut el = div()
      .id(self.id)
      .flex()
      .items_center()
      .justify_center()
      .gap(px(8.0))
      .h(px(height))
      .px(px(pad_x))
      .rounded(px(radius))
      .bg(s.bg)
      .text_color(s.fg)
      .text_size(px(font))
      .font_weight(FontWeight::SEMIBOLD)
      .tab_index(0)
      .focus(move |style| style.border_2().border_color(focus))
      .on_key_down(crate::input::handle_tab);

    if let Some(border) = s.border {
      el = el.border_1().border_color(border);
    }
    if self.full_width {
      el = el.w_full();
    }
    if let Some(left) = self.left_section {
      el = el.child(left);
    }
    el = el.child(self.label);
    if let Some(right) = self.right_section {
      el = el.child(right);
    }

    let element = if self.disabled {
      el.tab_stop(false).opacity(0.6)
    } else {
      let hover_bg = s.bg_hover;
      el = el.hover(move |st| st.bg(hover_bg));
      if let Some(handler) = self.on_click {
        el = el.on_click(handler);
      }
      el
    };

    element
      .probe("Button")
      .attr("variant", self.variant.label())
      .attr("size", self.size.label())
      .attr_if("disabled", self.disabled)
  }
}