ZIm/crates/ui/src/elements/button.rs
Marshall Bowers f26ca0866c
Mainline GPUI2 UI work (#3062)
This PR mainlines the current state of new GPUI2-based UI from the
`gpui2-ui` branch.

Release Notes:

- N/A

---------

Co-authored-by: Nate Butler <iamnbutler@gmail.com>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Co-authored-by: Nate <nate@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-09-28 19:36:21 -04:00

203 lines
5.6 KiB
Rust

use std::rc::Rc;
use gpui2::geometry::DefiniteLength;
use gpui2::platform::MouseButton;
use gpui2::{EventContext, Hsla, Interactive, WindowContext};
use crate::prelude::*;
use crate::{h_stack, theme, Icon, IconColor, IconElement, Label, LabelColor, LabelSize};
#[derive(Default, PartialEq, Clone, Copy)]
pub enum IconPosition {
#[default]
Left,
Right,
}
#[derive(Default, Copy, Clone, PartialEq)]
pub enum ButtonVariant {
#[default]
Ghost,
Filled,
}
struct ButtonHandlers<V> {
click: Option<Rc<dyn Fn(&mut V, &mut EventContext<V>)>>,
}
impl<V> Default for ButtonHandlers<V> {
fn default() -> Self {
Self { click: None }
}
}
#[derive(Element)]
pub struct Button<V: 'static> {
label: String,
variant: ButtonVariant,
state: InteractionState,
icon: Option<Icon>,
icon_position: Option<IconPosition>,
width: Option<DefiniteLength>,
handlers: ButtonHandlers<V>,
}
impl<V: 'static> Button<V> {
pub fn new<L>(label: L) -> Self
where
L: Into<String>,
{
Self {
label: label.into(),
variant: Default::default(),
state: Default::default(),
icon: None,
icon_position: None,
width: Default::default(),
handlers: ButtonHandlers::default(),
}
}
pub fn ghost<L>(label: L) -> Self
where
L: Into<String>,
{
Self::new(label).variant(ButtonVariant::Ghost)
}
pub fn variant(mut self, variant: ButtonVariant) -> Self {
self.variant = variant;
self
}
pub fn state(mut self, state: InteractionState) -> Self {
self.state = state;
self
}
pub fn icon(mut self, icon: Icon) -> Self {
self.icon = Some(icon);
self
}
pub fn icon_position(mut self, icon_position: IconPosition) -> Self {
if self.icon.is_none() {
panic!("An icon must be present if an icon_position is provided.");
}
self.icon_position = Some(icon_position);
self
}
pub fn width(mut self, width: Option<DefiniteLength>) -> Self {
self.width = width;
self
}
pub fn on_click(mut self, handler: impl Fn(&mut V, &mut EventContext<V>) + 'static) -> Self {
self.handlers.click = Some(Rc::new(handler));
self
}
fn background_color(&self, cx: &mut ViewContext<V>) -> Hsla {
let theme = theme(cx);
let system_color = SystemColor::new();
match (self.variant, self.state) {
(ButtonVariant::Ghost, InteractionState::Hovered) => {
theme.lowest.base.hovered.background
}
(ButtonVariant::Ghost, InteractionState::Active) => {
theme.lowest.base.pressed.background
}
(ButtonVariant::Filled, InteractionState::Enabled) => {
theme.lowest.on.default.background
}
(ButtonVariant::Filled, InteractionState::Hovered) => {
theme.lowest.on.hovered.background
}
(ButtonVariant::Filled, InteractionState::Active) => theme.lowest.on.pressed.background,
(ButtonVariant::Filled, InteractionState::Disabled) => {
theme.lowest.on.disabled.background
}
_ => system_color.transparent,
}
}
fn label_color(&self) -> LabelColor {
match self.state {
InteractionState::Disabled => LabelColor::Disabled,
_ => Default::default(),
}
}
fn icon_color(&self) -> IconColor {
match self.state {
InteractionState::Disabled => IconColor::Disabled,
_ => Default::default(),
}
}
fn border_color(&self, cx: &WindowContext) -> Hsla {
let theme = theme(cx);
let system_color = SystemColor::new();
match self.state {
InteractionState::Focused => theme.lowest.accent.default.border,
_ => system_color.transparent,
}
}
fn render_label(&self) -> Label {
Label::new(self.label.clone())
.size(LabelSize::Small)
.color(self.label_color())
}
fn render_icon(&self, icon_color: IconColor) -> Option<IconElement> {
self.icon.map(|i| IconElement::new(i).color(icon_color))
}
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
let icon_color = self.icon_color();
let system_color = SystemColor::new();
let border_color = self.border_color(cx);
let mut el = h_stack()
.h_6()
.px_1()
.items_center()
.rounded_md()
.border()
.border_color(border_color)
.fill(self.background_color(cx));
match (self.icon, self.icon_position) {
(Some(_), Some(IconPosition::Left)) => {
el = el
.gap_1()
.child(self.render_label())
.children(self.render_icon(icon_color))
}
(Some(_), Some(IconPosition::Right)) => {
el = el
.gap_1()
.children(self.render_icon(icon_color))
.child(self.render_label())
}
(_, _) => el = el.child(self.render_label()),
}
if let Some(width) = self.width {
el = el.w(width).justify_center();
}
if let Some(click_handler) = self.handlers.click.clone() {
el = el.on_mouse_down(MouseButton::Left, move |view, event, cx| {
click_handler(view, cx);
});
}
el
}
}