WIP: Add a stateful button
This commit is contained in:
parent
07b9c6c302
commit
69fc2d0950
7 changed files with 197 additions and 36 deletions
|
@ -4,7 +4,7 @@ use gpui::{
|
|||
AppContext, Element, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use project::Project;
|
||||
use theme::components::{action_button::Button, label::Label, ComponentExt};
|
||||
use theme::components::{action_button::ActionButton, button::Button, label::Label, ComponentExt};
|
||||
use workspace::{
|
||||
item::Item, register_deserializable_item, ItemId, Pane, PaneBackdrop, Workspace, WorkspaceId,
|
||||
};
|
||||
|
@ -66,14 +66,14 @@ impl View for ComponentTest {
|
|||
Flex::column()
|
||||
.with_spacing(10.)
|
||||
.with_child(
|
||||
Button::action(NoAction)
|
||||
ActionButton::action(NoAction)
|
||||
.with_tooltip("Here's what a tooltip looks like", theme.tooltip.clone())
|
||||
.with_contents(Label::new("Click me!"))
|
||||
.with_style(theme.component_test.button.clone())
|
||||
.element(),
|
||||
)
|
||||
.with_child(
|
||||
Button::action(ToggleToggle)
|
||||
ActionButton::action(ToggleToggle)
|
||||
.with_tooltip("Here's what a tooltip looks like", theme.tooltip.clone())
|
||||
.with_contents(Label::new("Toggle me!"))
|
||||
.toggleable(self.toggled)
|
||||
|
@ -86,6 +86,13 @@ impl View for ComponentTest {
|
|||
.with_style(theme.component_test.disclosure.clone())
|
||||
.element(),
|
||||
)
|
||||
.with_child(
|
||||
Button::new(|click, this, cx| println!("Clicked! {:?}", click))
|
||||
.with_contents(Label::new("Print click to console!"))
|
||||
.disclosable(Some(self.disclosed), Box::new(ToggleDisclosure))
|
||||
.with_style(theme.component_test.disclosure.clone())
|
||||
.element(),
|
||||
)
|
||||
.constrained()
|
||||
.with_width(200.)
|
||||
.aligned()
|
||||
|
|
|
@ -149,6 +149,17 @@ impl<V: 'static, C: SafeStylable> StatefulSafeStylable<V> for C {
|
|||
}
|
||||
}
|
||||
|
||||
/// converting from stateful to stateless
|
||||
impl<V: 'static, C: StatefulSafeStylable<V>> SafeStylable for C {
|
||||
type Style = C::Style;
|
||||
|
||||
type Output = C::Output;
|
||||
|
||||
fn with_style(self, style: Self::Style) -> Self::Output {
|
||||
self.with_style(style)
|
||||
}
|
||||
}
|
||||
|
||||
// A helper for converting stateless components into stateful ones
|
||||
pub struct StatefulAdapter<C, V> {
|
||||
component: C,
|
||||
|
|
|
@ -166,6 +166,15 @@ impl<V: 'static> MouseEventHandler<V> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn on_click_dynamic(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: Box<dyn Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static>,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_click_dynamic(button, handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
|
|
|
@ -420,6 +420,31 @@ impl HandlerSet {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn on_click_dynamic<V>(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: Box<dyn Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static>,
|
||||
) -> Self
|
||||
where
|
||||
V: 'static,
|
||||
{
|
||||
self.insert(MouseEvent::click_disc(), Some(button),
|
||||
Rc::new(move |region_event, view, cx, view_id| {
|
||||
if let MouseEvent::Click(e) = region_event {
|
||||
let view = view.downcast_mut().unwrap();
|
||||
let mut cx = ViewContext::mutable(cx, view_id);
|
||||
let mut cx = EventContext::new(&mut cx);
|
||||
handler(e, view, &mut cx);
|
||||
cx.handled
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Click, found {:?}",
|
||||
region_event);
|
||||
}
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
|
||||
where
|
||||
V: 'static,
|
||||
|
|
|
@ -8,7 +8,9 @@ use gpui::{
|
|||
pub use mode::SearchMode;
|
||||
use project::search::SearchQuery;
|
||||
pub use project_search::{ProjectSearchBar, ProjectSearchView};
|
||||
use theme::components::{action_button::Button, svg::Svg, ComponentExt, ToggleIconButtonStyle};
|
||||
use theme::components::{
|
||||
action_button::ActionButton, svg::Svg, ComponentExt, ToggleIconButtonStyle,
|
||||
};
|
||||
|
||||
pub mod buffer_search;
|
||||
mod history;
|
||||
|
@ -89,7 +91,7 @@ impl SearchOptions {
|
|||
tooltip_style: TooltipStyle,
|
||||
button_style: ToggleIconButtonStyle,
|
||||
) -> AnyElement<V> {
|
||||
Button::dynamic_action(self.to_toggle_action())
|
||||
ActionButton::dynamic_action(self.to_toggle_action())
|
||||
.with_tooltip(format!("Toggle {}", self.label()), tooltip_style)
|
||||
.with_contents(Svg::new(self.icon()))
|
||||
.toggleable(active)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use gpui::{elements::SafeStylable, Action};
|
||||
|
||||
use crate::{Interactive, Toggleable};
|
||||
use crate::{ButtonStyle, Interactive, Toggleable};
|
||||
|
||||
use self::{action_button::ButtonStyle, disclosure::Disclosable, svg::SvgStyle, toggle::Toggle};
|
||||
use self::{disclosure::Disclosable, svg::SvgStyle, toggle::Toggle};
|
||||
|
||||
pub type IconButtonStyle = Interactive<ButtonStyle<SvgStyle>>;
|
||||
pub type ToggleIconButtonStyle = Toggleable<IconButtonStyle>;
|
||||
|
@ -25,6 +25,115 @@ impl<C: SafeStylable> ComponentExt<C> for C {
|
|||
}
|
||||
}
|
||||
|
||||
pub mod button {
|
||||
use std::borrow::Cow;
|
||||
|
||||
use gpui::{
|
||||
elements::{MouseEventHandler, StatefulComponent, StatefulSafeStylable, TooltipStyle},
|
||||
platform::{CursorStyle, MouseButton},
|
||||
scene::MouseClick,
|
||||
Action, Element, EventContext, TypeTag,
|
||||
};
|
||||
|
||||
use crate::{ButtonStyle, Interactive};
|
||||
|
||||
pub struct Button<V: 'static, C, S> {
|
||||
handler: Box<dyn Fn(MouseClick, &mut V, &mut EventContext<V>)>,
|
||||
tooltip: Option<(Cow<'static, str>, TooltipStyle, Option<Box<dyn Action>>)>,
|
||||
tag: TypeTag,
|
||||
contents: C,
|
||||
style: Interactive<S>,
|
||||
}
|
||||
|
||||
impl<V: 'static> Button<V, (), ()> {
|
||||
pub fn new<F>(handler: F) -> Button<V, (), ()>
|
||||
where
|
||||
F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
Self {
|
||||
contents: (),
|
||||
tag: TypeTag::new::<F>(),
|
||||
handler: Box::new(handler),
|
||||
style: Interactive::new_blank(),
|
||||
tooltip: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_tooltip(
|
||||
mut self,
|
||||
tooltip: impl Into<Cow<'static, str>>,
|
||||
tooltip_style: TooltipStyle,
|
||||
keybinding: Option<Box<dyn Action>>,
|
||||
) -> Self {
|
||||
self.tooltip = Some((tooltip.into(), tooltip_style, keybinding));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_contents<C: StatefulSafeStylable<V>>(self, contents: C) -> Button<V, C, ()> {
|
||||
Button {
|
||||
handler: self.handler,
|
||||
tag: self.tag,
|
||||
style: self.style,
|
||||
tooltip: self.tooltip,
|
||||
contents,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static, C: StatefulSafeStylable<V>> StatefulSafeStylable<V> for Button<V, C, ()> {
|
||||
type Style = Interactive<ButtonStyle<C::Style>>;
|
||||
type Output = Button<V, C, ButtonStyle<C::Style>>;
|
||||
|
||||
fn with_style(self, style: Self::Style) -> Self::Output {
|
||||
Button {
|
||||
handler: self.handler,
|
||||
tag: self.tag,
|
||||
contents: self.contents,
|
||||
tooltip: self.tooltip,
|
||||
style,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static, C: StatefulSafeStylable<V>> StatefulComponent<V>
|
||||
for Button<V, C, ButtonStyle<C::Style>>
|
||||
{
|
||||
fn render(self, v: &mut V, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
|
||||
let mut button = MouseEventHandler::new_dynamic(self.tag, 0, cx, |state, cx| {
|
||||
let style = self.style.style_for(state);
|
||||
let mut contents = self
|
||||
.contents
|
||||
.with_style(style.contents.to_owned())
|
||||
.render(v, cx)
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
.constrained();
|
||||
|
||||
if let Some(height) = style.button_height {
|
||||
contents = contents.with_height(height);
|
||||
}
|
||||
|
||||
if let Some(width) = style.button_width {
|
||||
contents = contents.with_width(width);
|
||||
}
|
||||
|
||||
contents.into_any()
|
||||
})
|
||||
.on_click_dynamic(MouseButton::Left, self.handler)
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.into_any();
|
||||
|
||||
if let Some((tooltip, style, action)) = self.tooltip {
|
||||
button = button
|
||||
.with_dynamic_tooltip(self.tag, 0, tooltip, action, style, cx)
|
||||
.into_any()
|
||||
}
|
||||
|
||||
button
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod disclosure {
|
||||
|
||||
use gpui::{
|
||||
|
@ -34,7 +143,7 @@ pub mod disclosure {
|
|||
use schemars::JsonSchema;
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
use super::{action_button::Button, svg::Svg, IconButtonStyle};
|
||||
use super::{action_button::ActionButton, svg::Svg, IconButtonStyle};
|
||||
|
||||
#[derive(Clone, Default, Deserialize, JsonSchema)]
|
||||
pub struct DisclosureStyle<S> {
|
||||
|
@ -104,7 +213,7 @@ pub mod disclosure {
|
|||
Flex::row()
|
||||
.with_spacing(self.style.spacing)
|
||||
.with_child(if let Some(disclosed) = self.disclosed {
|
||||
Button::dynamic_action(self.action)
|
||||
ActionButton::dynamic_action(self.action)
|
||||
.with_id(self.id)
|
||||
.with_contents(Svg::new(if disclosed {
|
||||
"icons/file_icons/chevron_down.svg"
|
||||
|
@ -184,29 +293,14 @@ pub mod action_button {
|
|||
use std::borrow::Cow;
|
||||
|
||||
use gpui::{
|
||||
elements::{Component, ContainerStyle, MouseEventHandler, SafeStylable, TooltipStyle},
|
||||
elements::{Component, MouseEventHandler, SafeStylable, TooltipStyle},
|
||||
platform::{CursorStyle, MouseButton},
|
||||
Action, Element, TypeTag,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
use crate::Interactive;
|
||||
use crate::{ButtonStyle, Interactive};
|
||||
|
||||
#[derive(Clone, Deserialize, Default, JsonSchema)]
|
||||
pub struct ButtonStyle<C> {
|
||||
#[serde(flatten)]
|
||||
pub container: ContainerStyle,
|
||||
// TODO: These are incorrect for the intended usage of the buttons.
|
||||
// The size should be constant, but putting them here duplicates them
|
||||
// across the states the buttons can be in
|
||||
pub button_width: Option<f32>,
|
||||
pub button_height: Option<f32>,
|
||||
#[serde(flatten)]
|
||||
contents: C,
|
||||
}
|
||||
|
||||
pub struct Button<C, S> {
|
||||
pub struct ActionButton<C, S> {
|
||||
action: Box<dyn Action>,
|
||||
tooltip: Option<(Cow<'static, str>, TooltipStyle)>,
|
||||
tag: TypeTag,
|
||||
|
@ -215,8 +309,8 @@ pub mod action_button {
|
|||
style: Interactive<S>,
|
||||
}
|
||||
|
||||
impl Button<(), ()> {
|
||||
pub fn dynamic_action(action: Box<dyn Action>) -> Button<(), ()> {
|
||||
impl ActionButton<(), ()> {
|
||||
pub fn dynamic_action(action: Box<dyn Action>) -> ActionButton<(), ()> {
|
||||
Self {
|
||||
contents: (),
|
||||
tag: action.type_tag(),
|
||||
|
@ -245,8 +339,8 @@ pub mod action_button {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn with_contents<C: SafeStylable>(self, contents: C) -> Button<C, ()> {
|
||||
Button {
|
||||
pub fn with_contents<C: SafeStylable>(self, contents: C) -> ActionButton<C, ()> {
|
||||
ActionButton {
|
||||
action: self.action,
|
||||
tag: self.tag,
|
||||
style: self.style,
|
||||
|
@ -257,12 +351,12 @@ pub mod action_button {
|
|||
}
|
||||
}
|
||||
|
||||
impl<C: SafeStylable> SafeStylable for Button<C, ()> {
|
||||
impl<C: SafeStylable> SafeStylable for ActionButton<C, ()> {
|
||||
type Style = Interactive<ButtonStyle<C::Style>>;
|
||||
type Output = Button<C, ButtonStyle<C::Style>>;
|
||||
type Output = ActionButton<C, ButtonStyle<C::Style>>;
|
||||
|
||||
fn with_style(self, style: Self::Style) -> Self::Output {
|
||||
Button {
|
||||
ActionButton {
|
||||
action: self.action,
|
||||
tag: self.tag,
|
||||
contents: self.contents,
|
||||
|
@ -273,7 +367,7 @@ pub mod action_button {
|
|||
}
|
||||
}
|
||||
|
||||
impl<C: SafeStylable> Component for Button<C, ButtonStyle<C::Style>> {
|
||||
impl<C: SafeStylable> Component for ActionButton<C, ButtonStyle<C::Style>> {
|
||||
fn render<V: 'static>(self, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
|
||||
let mut button = MouseEventHandler::new_dynamic(self.tag, self.id, cx, |state, cx| {
|
||||
let style = self.style.style_for(state);
|
||||
|
|
|
@ -3,7 +3,7 @@ mod theme_registry;
|
|||
mod theme_settings;
|
||||
pub mod ui;
|
||||
|
||||
use components::{action_button::ButtonStyle, disclosure::DisclosureStyle, ToggleIconButtonStyle};
|
||||
use components::{disclosure::DisclosureStyle, ToggleIconButtonStyle};
|
||||
use gpui::{
|
||||
color::Color,
|
||||
elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, SvgStyle, TooltipStyle},
|
||||
|
@ -987,6 +987,19 @@ impl<T> Toggleable<Interactive<T>> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Default, JsonSchema)]
|
||||
pub struct ButtonStyle<C> {
|
||||
#[serde(flatten)]
|
||||
pub container: ContainerStyle,
|
||||
// TODO: These are incorrect for the intended usage of the buttons.
|
||||
// The size should be constant, but putting them here duplicates them
|
||||
// across the states the buttons can be in
|
||||
pub button_width: Option<f32>,
|
||||
pub button_height: Option<f32>,
|
||||
#[serde(flatten)]
|
||||
contents: C,
|
||||
}
|
||||
|
||||
impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue