zed/crates/gpui/examples/components.rs
Mikayla e946b0a2ec
Finish building out adapters and names
Document core traits
Add start for a component storybook
2023-08-19 14:40:05 -07:00

237 lines
6.8 KiB
Rust

use button_component::Button;
use gpui::{
color::Color,
elements::{ContainerStyle, Flex, Label, ParentElement, StatefulComponent},
fonts::{self, TextStyle},
platform::WindowOptions,
AnyElement, App, Element, Entity, View, ViewContext,
};
use log::LevelFilter;
use pathfinder_geometry::vector::vec2f;
use simplelog::SimpleLogger;
use theme::Toggleable;
use toggleable_button::ToggleableButton;
// cargo run -p gpui --example components
fn main() {
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
App::new(()).unwrap().run(|cx| {
cx.platform().activate(true);
cx.add_window(WindowOptions::with_bounds(vec2f(300., 200.)), |_| {
TestView {
count: 0,
is_doubling: false,
}
});
});
}
pub struct TestView {
count: usize,
is_doubling: bool,
}
impl TestView {
fn increase_count(&mut self) {
if self.is_doubling {
self.count *= 2;
} else {
self.count += 1;
}
}
}
impl Entity for TestView {
type Event = ();
}
type ButtonStyle = ContainerStyle;
impl View for TestView {
fn ui_name() -> &'static str {
"TestView"
}
fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> AnyElement<Self> {
fonts::with_font_cache(cx.font_cache.to_owned(), || {
Flex::column()
.with_child(Label::new(
format!("Count: {}", self.count),
TextStyle::for_color(Color::red()),
))
.with_child(
Button::new(move |_, v: &mut Self, cx| {
v.increase_count();
cx.notify();
})
.with_text(
"Hello from a counting BUTTON",
TextStyle::for_color(Color::blue()),
)
.with_style(ButtonStyle::fill(Color::yellow()))
.element(),
)
.with_child(
ToggleableButton::new(self.is_doubling, move |_, v: &mut Self, cx| {
v.is_doubling = !v.is_doubling;
cx.notify();
})
.with_text("Double the count?", TextStyle::for_color(Color::black()))
.with_style(Toggleable {
inactive: ButtonStyle::fill(Color::red()),
active: ButtonStyle::fill(Color::green()),
})
.element(),
)
.expanded()
.contained()
.with_background_color(Color::white())
.into_any()
})
}
}
mod theme {
pub struct Toggleable<T> {
pub inactive: T,
pub active: T,
}
impl<T> Toggleable<T> {
pub fn style_for(&self, active: bool) -> &T {
if active {
&self.active
} else {
&self.inactive
}
}
}
}
// Component creation:
mod toggleable_button {
use gpui::{
elements::{ContainerStyle, LabelStyle, StatefulComponent},
scene::MouseClick,
EventContext, View,
};
use crate::{button_component::Button, theme::Toggleable};
pub struct ToggleableButton<V: View> {
active: bool,
style: Option<Toggleable<ContainerStyle>>,
button: Button<V>,
}
impl<V: View> ToggleableButton<V> {
pub fn new<F>(active: bool, on_click: F) -> Self
where
F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
{
Self {
active,
button: Button::new(on_click),
style: None,
}
}
pub fn with_text(self, text: &str, style: impl Into<LabelStyle>) -> ToggleableButton<V> {
ToggleableButton {
active: self.active,
style: self.style,
button: self.button.with_text(text, style),
}
}
pub fn with_style(self, style: Toggleable<ContainerStyle>) -> ToggleableButton<V> {
ToggleableButton {
active: self.active,
style: Some(style),
button: self.button,
}
}
}
impl<V: View> StatefulComponent<V> for ToggleableButton<V> {
fn render(self, v: &mut V, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
let button = if let Some(style) = self.style {
self.button.with_style(*style.style_for(self.active))
} else {
self.button
};
button.render(v, cx)
}
}
}
mod button_component {
use gpui::{
elements::{ContainerStyle, Label, LabelStyle, MouseEventHandler, StatefulComponent},
platform::MouseButton,
scene::MouseClick,
AnyElement, Element, EventContext, TypeTag, View, ViewContext,
};
type ClickHandler<V> = Box<dyn Fn(MouseClick, &mut V, &mut EventContext<V>)>;
pub struct Button<V: View> {
click_handler: ClickHandler<V>,
tag: TypeTag,
contents: Option<AnyElement<V>>,
style: Option<ContainerStyle>,
}
impl<V: View> Button<V> {
pub fn new<F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static>(handler: F) -> Self {
Self {
click_handler: Box::new(handler),
tag: TypeTag::new::<F>(),
style: None,
contents: None,
}
}
pub fn with_text(mut self, text: &str, style: impl Into<LabelStyle>) -> Self {
self.contents = Some(Label::new(text.to_string(), style).into_any());
self
}
pub fn _with_contents<E: Element<V>>(mut self, contents: E) -> Self {
self.contents = Some(contents.into_any());
self
}
pub fn with_style(mut self, style: ContainerStyle) -> Self {
self.style = Some(style);
self
}
}
impl<V: View> StatefulComponent<V> for Button<V> {
fn render(self, _: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V> {
let click_handler = self.click_handler;
let result = MouseEventHandler::new_dynamic(self.tag, 0, cx, |_, _| {
self.contents
.unwrap_or_else(|| gpui::elements::Empty::new().into_any())
})
.on_click(MouseButton::Left, move |click, v, cx| {
click_handler(click, v, cx);
})
.contained();
let result = if let Some(style) = self.style {
result.with_style(style)
} else {
result
};
result.into_any()
}
}
}