Add new Button
and IconButton
components (#3448)
This PR adds new `Button` and `IconButton` components built on top of our new button abstractions. Both of these buttons are built from the common `ButtonLike` base, and implement the `ButtonCommon` (name TBD) trait in order to provide a common interface. There are still some visual tweaks that we'll need to make to the new buttons, but those should be straightforward to make after we land this. Release Notes: - N/A
This commit is contained in:
parent
df5de47a78
commit
b357ae4dc3
23 changed files with 324 additions and 682 deletions
|
@ -178,6 +178,7 @@ use gpui::{
|
||||||
use project::Fs;
|
use project::Fs;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
|
use ui::prelude::*;
|
||||||
use ui::{
|
use ui::{
|
||||||
h_stack, v_stack, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconElement, IconSize,
|
h_stack, v_stack, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconElement, IconSize,
|
||||||
Label, List, ListHeader, ListItem, Tooltip,
|
Label, List, ListHeader, ListItem, Tooltip,
|
||||||
|
@ -2338,18 +2339,20 @@ impl CollabPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_signed_out(&mut self, cx: &mut ViewContext<Self>) -> Div {
|
fn render_signed_out(&mut self, cx: &mut ViewContext<Self>) -> Div {
|
||||||
v_stack().child(Button::new("Sign in to collaborate").on_click(cx.listener(
|
v_stack().child(
|
||||||
|this, _, cx| {
|
Button::new("sign_in", "Sign in to collaborate").on_click(cx.listener(
|
||||||
let client = this.client.clone();
|
|this, _, cx| {
|
||||||
cx.spawn(|_, mut cx| async move {
|
let client = this.client.clone();
|
||||||
client
|
cx.spawn(|_, mut cx| async move {
|
||||||
.authenticate_and_connect(true, &cx)
|
client
|
||||||
.await
|
.authenticate_and_connect(true, &cx)
|
||||||
.notify_async_err(&mut cx);
|
.await
|
||||||
})
|
.notify_async_err(&mut cx);
|
||||||
.detach()
|
})
|
||||||
},
|
.detach()
|
||||||
)))
|
},
|
||||||
|
)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_signed_in(&mut self, cx: &mut ViewContext<Self>) -> List {
|
fn render_signed_in(&mut self, cx: &mut ViewContext<Self>) -> List {
|
||||||
|
@ -2564,7 +2567,7 @@ impl CollabPanel {
|
||||||
.group_hover("", |style| style.visible())
|
.group_hover("", |style| style.visible())
|
||||||
.child(
|
.child(
|
||||||
IconButton::new("remove_contact", Icon::Close)
|
IconButton::new("remove_contact", Icon::Close)
|
||||||
.color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.tooltip(|cx| Tooltip::text("Remove Contact", cx))
|
.tooltip(|cx| Tooltip::text("Remove Contact", cx))
|
||||||
.on_click(cx.listener(move |this, _, cx| {
|
.on_click(cx.listener(move |this, _, cx| {
|
||||||
this.remove_contact(user_id, &github_login, cx);
|
this.remove_contact(user_id, &github_login, cx);
|
||||||
|
@ -2688,13 +2691,13 @@ impl CollabPanel {
|
||||||
.on_click(cx.listener(move |this, _, cx| {
|
.on_click(cx.listener(move |this, _, cx| {
|
||||||
this.respond_to_contact_request(user_id, false, cx);
|
this.respond_to_contact_request(user_id, false, cx);
|
||||||
}))
|
}))
|
||||||
.color(color)
|
.icon_color(color)
|
||||||
.tooltip(|cx| Tooltip::text("Decline invite", cx)),
|
.tooltip(|cx| Tooltip::text("Decline invite", cx)),
|
||||||
IconButton::new("remove_contact", Icon::Check)
|
IconButton::new("remove_contact", Icon::Check)
|
||||||
.on_click(cx.listener(move |this, _, cx| {
|
.on_click(cx.listener(move |this, _, cx| {
|
||||||
this.respond_to_contact_request(user_id, true, cx);
|
this.respond_to_contact_request(user_id, true, cx);
|
||||||
}))
|
}))
|
||||||
.color(color)
|
.icon_color(color)
|
||||||
.tooltip(|cx| Tooltip::text("Accept invite", cx)),
|
.tooltip(|cx| Tooltip::text("Accept invite", cx)),
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
|
@ -2703,7 +2706,7 @@ impl CollabPanel {
|
||||||
.on_click(cx.listener(move |this, _, cx| {
|
.on_click(cx.listener(move |this, _, cx| {
|
||||||
this.remove_contact(user_id, &github_login, cx);
|
this.remove_contact(user_id, &github_login, cx);
|
||||||
}))
|
}))
|
||||||
.color(color)
|
.icon_color(color)
|
||||||
.tooltip(|cx| Tooltip::text("Cancel invite", cx))]
|
.tooltip(|cx| Tooltip::text("Cancel invite", cx))]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2846,7 +2849,7 @@ impl CollabPanel {
|
||||||
"channel_chat",
|
"channel_chat",
|
||||||
Icon::MessageBubbles,
|
Icon::MessageBubbles,
|
||||||
)
|
)
|
||||||
.color(if has_messages_notification {
|
.icon_color(if has_messages_notification {
|
||||||
Color::Default
|
Color::Default
|
||||||
} else {
|
} else {
|
||||||
Color::Muted
|
Color::Muted
|
||||||
|
@ -2861,7 +2864,7 @@ impl CollabPanel {
|
||||||
.group_hover("", |style| style.visible())
|
.group_hover("", |style| style.visible())
|
||||||
.child(
|
.child(
|
||||||
IconButton::new("channel_notes", Icon::File)
|
IconButton::new("channel_notes", Icon::File)
|
||||||
.color(if has_notes_notification {
|
.icon_color(if has_notes_notification {
|
||||||
Color::Default
|
Color::Default
|
||||||
} else {
|
} else {
|
||||||
Color::Muted
|
Color::Muted
|
||||||
|
|
|
@ -37,10 +37,7 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::{
|
use ui::{h_stack, prelude::*, Avatar, Button, ButtonStyle2, IconButton, KeyBinding, Tooltip};
|
||||||
h_stack, Avatar, Button, ButtonCommon, ButtonLike, ButtonVariant, Clickable, Color, IconButton,
|
|
||||||
IconElement, IconSize, KeyBinding, Tooltip,
|
|
||||||
};
|
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{notifications::NotifyResultExt, Workspace};
|
use workspace::{notifications::NotifyResultExt, Workspace};
|
||||||
|
|
||||||
|
@ -156,8 +153,8 @@ impl Render for CollabTitlebarItem {
|
||||||
.border_color(gpui::red())
|
.border_color(gpui::red())
|
||||||
.id("project_owner_indicator")
|
.id("project_owner_indicator")
|
||||||
.child(
|
.child(
|
||||||
Button::new("player")
|
Button::new("player", "player")
|
||||||
.variant(ButtonVariant::Ghost)
|
.style(ButtonStyle2::Subtle)
|
||||||
.color(Some(Color::Player(0))),
|
.color(Some(Color::Player(0))),
|
||||||
)
|
)
|
||||||
.tooltip(move |cx| Tooltip::text("Toggle following", cx)),
|
.tooltip(move |cx| Tooltip::text("Toggle following", cx)),
|
||||||
|
@ -168,7 +165,10 @@ impl Render for CollabTitlebarItem {
|
||||||
.border()
|
.border()
|
||||||
.border_color(gpui::red())
|
.border_color(gpui::red())
|
||||||
.id("titlebar_project_menu_button")
|
.id("titlebar_project_menu_button")
|
||||||
.child(Button::new("project_name").variant(ButtonVariant::Ghost))
|
.child(
|
||||||
|
Button::new("project_name", "project_name")
|
||||||
|
.style(ButtonStyle2::Subtle),
|
||||||
|
)
|
||||||
.tooltip(move |cx| Tooltip::text("Recent Projects", cx)),
|
.tooltip(move |cx| Tooltip::text("Recent Projects", cx)),
|
||||||
)
|
)
|
||||||
// TODO - Add git menu
|
// TODO - Add git menu
|
||||||
|
@ -178,8 +178,8 @@ impl Render for CollabTitlebarItem {
|
||||||
.border_color(gpui::red())
|
.border_color(gpui::red())
|
||||||
.id("titlebar_git_menu_button")
|
.id("titlebar_git_menu_button")
|
||||||
.child(
|
.child(
|
||||||
Button::new("branch_name")
|
Button::new("branch_name", "branch_name")
|
||||||
.variant(ButtonVariant::Ghost)
|
.style(ButtonStyle2::Subtle)
|
||||||
.color(Some(Color::Muted)),
|
.color(Some(Color::Muted)),
|
||||||
)
|
)
|
||||||
.tooltip(move |cx| {
|
.tooltip(move |cx| {
|
||||||
|
@ -238,7 +238,10 @@ impl Render for CollabTitlebarItem {
|
||||||
h_stack()
|
h_stack()
|
||||||
.child(
|
.child(
|
||||||
h_stack()
|
h_stack()
|
||||||
.child(Button::new(if is_shared { "Unshare" } else { "Share" }))
|
.child(Button::new(
|
||||||
|
"toggle_sharing",
|
||||||
|
if is_shared { "Unshare" } else { "Share" },
|
||||||
|
))
|
||||||
.child(IconButton::new("leave-call", ui::Icon::Exit).on_click({
|
.child(IconButton::new("leave-call", ui::Icon::Exit).on_click({
|
||||||
let workspace = workspace.clone();
|
let workspace = workspace.clone();
|
||||||
move |_, cx| {
|
move |_, cx| {
|
||||||
|
@ -291,7 +294,7 @@ impl Render for CollabTitlebarItem {
|
||||||
this.child(ui::Avatar::data(avatar))
|
this.child(ui::Avatar::data(avatar))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.child(Button::new("Sign in").on_click(move |_, cx| {
|
this.child(Button::new("sign_in", "Sign in").on_click(move |_, cx| {
|
||||||
let client = client.clone();
|
let client = client.clone();
|
||||||
cx.spawn(move |mut cx| async move {
|
cx.spawn(move |mut cx| async move {
|
||||||
client
|
client
|
||||||
|
@ -301,27 +304,6 @@ impl Render for CollabTitlebarItem {
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}))
|
}))
|
||||||
// Temporary, will be removed when the last part of button2 is merged
|
|
||||||
.child(
|
|
||||||
div().border().border_color(gpui::blue()).child(
|
|
||||||
ButtonLike::new("test-button")
|
|
||||||
.children([
|
|
||||||
Avatar::uri(
|
|
||||||
"https://avatars.githubusercontent.com/u/1714999?v=4",
|
|
||||||
)
|
|
||||||
.into_element()
|
|
||||||
.into_any(),
|
|
||||||
IconElement::new(ui::Icon::ChevronDown)
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.into_element()
|
|
||||||
.into_any(),
|
|
||||||
])
|
|
||||||
.on_click(move |event, _cx| {
|
|
||||||
dbg!(format!("clicked: {:?}", event.down.position));
|
|
||||||
})
|
|
||||||
.tooltip(|cx| Tooltip::text("Test tooltip", cx)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,11 @@ use crate::notification_window_options;
|
||||||
use call::{ActiveCall, IncomingCall};
|
use call::{ActiveCall, IncomingCall};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, green, px, red, AppContext, Div, Element, ParentElement, Render, RenderOnce,
|
div, px, red, AppContext, Div, Element, ParentElement, Render, RenderOnce, Styled, ViewContext,
|
||||||
StatefulInteractiveElement, Styled, ViewContext, VisualContext as _, WindowHandle,
|
VisualContext as _, WindowHandle,
|
||||||
};
|
};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
use ui::prelude::*;
|
||||||
use ui::{h_stack, v_stack, Avatar, Button, Label};
|
use ui::{h_stack, v_stack, Avatar, Button, Label};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::AppState;
|
use workspace::AppState;
|
||||||
|
@ -199,14 +200,24 @@ impl IncomingCallNotification {
|
||||||
|
|
||||||
fn render_buttons(&self, cx: &mut ViewContext<Self>) -> impl Element {
|
fn render_buttons(&self, cx: &mut ViewContext<Self>) -> impl Element {
|
||||||
h_stack()
|
h_stack()
|
||||||
.child(Button::new("Accept").render(cx).bg(green()).on_click({
|
.child(
|
||||||
let state = self.state.clone();
|
Button::new("accept", "Accept")
|
||||||
move |_, cx| state.respond(true, cx)
|
.render(cx)
|
||||||
}))
|
// .bg(green())
|
||||||
.child(Button::new("Decline").render(cx).bg(red()).on_click({
|
.on_click({
|
||||||
let state = self.state.clone();
|
let state = self.state.clone();
|
||||||
move |_, cx| state.respond(false, cx)
|
move |_, cx| state.respond(true, cx)
|
||||||
}))
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("decline", "Decline")
|
||||||
|
.render(cx)
|
||||||
|
// .bg(red())
|
||||||
|
.on_click({
|
||||||
|
let state = self.state.clone();
|
||||||
|
move |_, cx| state.respond(false, cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
// enum Accept {}
|
// enum Accept {}
|
||||||
// enum Decline {}
|
// enum Decline {}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::ProjectDiagnosticsEditor;
|
use crate::ProjectDiagnosticsEditor;
|
||||||
use gpui::{div, Div, EventEmitter, ParentElement, Render, ViewContext, WeakView};
|
use gpui::{div, Div, EventEmitter, ParentElement, Render, ViewContext, WeakView};
|
||||||
|
use ui::prelude::*;
|
||||||
use ui::{Icon, IconButton, Tooltip};
|
use ui::{Icon, IconButton, Tooltip};
|
||||||
use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
|
use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,8 @@ use text::{OffsetUtf16, Rope};
|
||||||
use theme::{
|
use theme::{
|
||||||
ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings,
|
ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings,
|
||||||
};
|
};
|
||||||
use ui::{h_stack, v_stack, HighlightedLabel, IconButton, Popover, StyledExt, Tooltip};
|
use ui::prelude::*;
|
||||||
|
use ui::{h_stack, v_stack, HighlightedLabel, IconButton, Popover, Tooltip};
|
||||||
use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
|
use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{ItemEvent, ItemHandle},
|
item::{ItemEvent, ItemHandle},
|
||||||
|
@ -4391,7 +4392,7 @@ impl Editor {
|
||||||
editor.fold_at(&FoldAt { buffer_row }, cx);
|
editor.fold_at(&FoldAt { buffer_row }, cx);
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.color(ui::Color::Muted)
|
.icon_color(ui::Color::Muted)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|
|
@ -48,6 +48,7 @@ use std::{
|
||||||
};
|
};
|
||||||
use sum_tree::Bias;
|
use sum_tree::Bias;
|
||||||
use theme::{ActiveTheme, PlayerColor};
|
use theme::{ActiveTheme, PlayerColor};
|
||||||
|
use ui::prelude::*;
|
||||||
use ui::{h_stack, IconButton, Tooltip};
|
use ui::{h_stack, IconButton, Tooltip};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::item::Item;
|
use workspace::item::Item;
|
||||||
|
|
|
@ -18,7 +18,7 @@ use project::search::SearchQuery;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{any::Any, sync::Arc};
|
use std::{any::Any, sync::Arc};
|
||||||
|
|
||||||
use ui::{h_stack, ButtonGroup, Icon, IconButton, IconElement};
|
use ui::{h_stack, Icon, IconButton, IconElement};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::ItemHandle,
|
item::ItemHandle,
|
||||||
|
@ -214,10 +214,11 @@ impl Render for BufferSearchBar {
|
||||||
.child(
|
.child(
|
||||||
h_stack()
|
h_stack()
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.child(ButtonGroup::new(vec![
|
.child(
|
||||||
search_button_for_mode(SearchMode::Text),
|
h_stack()
|
||||||
search_button_for_mode(SearchMode::Regex),
|
.child(search_button_for_mode(SearchMode::Text))
|
||||||
]))
|
.child(search_button_for_mode(SearchMode::Regex)),
|
||||||
|
)
|
||||||
.when(supported_options.replacement, |this| {
|
.when(supported_options.replacement, |this| {
|
||||||
this.child(super::toggle_replace_button(self.replace_enabled))
|
this.child(super::toggle_replace_button(self.replace_enabled))
|
||||||
}),
|
}),
|
||||||
|
@ -586,8 +587,7 @@ impl BufferSearchBar {
|
||||||
|
|
||||||
// let style = theme.search.action_button.clone();
|
// let style = theme.search.action_button.clone();
|
||||||
|
|
||||||
IconButton::new(0, ui::Icon::SelectAll)
|
IconButton::new(0, ui::Icon::SelectAll).action(Box::new(SelectAllMatches))
|
||||||
.on_click(|_, cx| cx.dispatch_action(Box::new(SelectAllMatches)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn activate_search_mode(&mut self, mode: SearchMode, cx: &mut ViewContext<Self>) {
|
pub fn activate_search_mode(&mut self, mode: SearchMode, cx: &mut ViewContext<Self>) {
|
||||||
|
|
|
@ -3,7 +3,8 @@ pub use buffer_search::BufferSearchBar;
|
||||||
use gpui::{actions, Action, AppContext, IntoElement};
|
use gpui::{actions, Action, AppContext, IntoElement};
|
||||||
pub use mode::SearchMode;
|
pub use mode::SearchMode;
|
||||||
use project::search::SearchQuery;
|
use project::search::SearchQuery;
|
||||||
use ui::ButtonVariant;
|
use ui::prelude::*;
|
||||||
|
use ui::{ButtonStyle2, Icon, IconButton};
|
||||||
//pub use project_search::{ProjectSearchBar, ProjectSearchView};
|
//pub use project_search::{ProjectSearchBar, ProjectSearchView};
|
||||||
// use theme::components::{
|
// use theme::components::{
|
||||||
// action_button::Button, svg::Svg, ComponentExt, IconButtonStyle, ToggleIconButtonStyle,
|
// action_button::Button, svg::Svg, ComponentExt, IconButtonStyle, ToggleIconButtonStyle,
|
||||||
|
@ -83,35 +84,35 @@ impl SearchOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_button(&self, active: bool) -> impl IntoElement {
|
pub fn as_button(&self, active: bool) -> impl IntoElement {
|
||||||
ui::IconButton::new(0, self.icon())
|
IconButton::new(0, self.icon())
|
||||||
.on_click({
|
.on_click({
|
||||||
let action = self.to_toggle_action();
|
let action = self.to_toggle_action();
|
||||||
move |_, cx| {
|
move |_, cx| {
|
||||||
cx.dispatch_action(action.boxed_clone());
|
cx.dispatch_action(action.boxed_clone());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.variant(ui::ButtonVariant::Ghost)
|
.style(ButtonStyle2::Subtle)
|
||||||
.when(active, |button| button.variant(ButtonVariant::Filled))
|
.when(active, |button| button.style(ButtonStyle2::Filled))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_replace_button(active: bool) -> impl IntoElement {
|
fn toggle_replace_button(active: bool) -> impl IntoElement {
|
||||||
// todo: add toggle_replace button
|
// todo: add toggle_replace button
|
||||||
ui::IconButton::new(0, ui::Icon::Replace)
|
IconButton::new(0, Icon::Replace)
|
||||||
.on_click(|_, cx| {
|
.on_click(|_, cx| {
|
||||||
cx.dispatch_action(Box::new(ToggleReplace));
|
cx.dispatch_action(Box::new(ToggleReplace));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
.variant(ui::ButtonVariant::Ghost)
|
.style(ButtonStyle2::Subtle)
|
||||||
.when(active, |button| button.variant(ButtonVariant::Filled))
|
.when(active, |button| button.style(ButtonStyle2::Filled))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_replace_button(
|
fn render_replace_button(
|
||||||
action: impl Action + 'static + Send + Sync,
|
action: impl Action + 'static + Send + Sync,
|
||||||
icon: ui::Icon,
|
icon: Icon,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
// todo: add tooltip
|
// todo: add tooltip
|
||||||
ui::IconButton::new(0, icon).on_click(move |_, cx| {
|
IconButton::new(0, icon).on_click(move |_, cx| {
|
||||||
cx.dispatch_action(action.boxed_clone());
|
cx.dispatch_action(action.boxed_clone());
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use gpui::{ClickEvent, IntoElement, WindowContext};
|
use gpui::{ClickEvent, IntoElement, WindowContext};
|
||||||
use ui::{Button, ButtonVariant, IconButton};
|
use ui::prelude::*;
|
||||||
|
use ui::{Button, IconButton};
|
||||||
|
|
||||||
use crate::mode::SearchMode;
|
use crate::mode::SearchMode;
|
||||||
|
|
||||||
|
@ -23,13 +24,7 @@ pub(crate) fn render_search_mode_button(
|
||||||
is_active: bool,
|
is_active: bool,
|
||||||
on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
||||||
) -> Button {
|
) -> Button {
|
||||||
let button_variant = if is_active {
|
Button::new(mode.label(), mode.label())
|
||||||
ButtonVariant::Filled
|
.selected(is_active)
|
||||||
} else {
|
|
||||||
ButtonVariant::Ghost
|
|
||||||
};
|
|
||||||
|
|
||||||
Button::new(mode.label())
|
|
||||||
.on_click(on_click)
|
.on_click(on_click)
|
||||||
.variant(button_variant)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
mod avatar;
|
mod avatar;
|
||||||
mod button;
|
mod button;
|
||||||
mod button2;
|
|
||||||
mod checkbox;
|
mod checkbox;
|
||||||
mod context_menu;
|
mod context_menu;
|
||||||
mod disclosure;
|
mod disclosure;
|
||||||
mod divider;
|
mod divider;
|
||||||
mod icon;
|
mod icon;
|
||||||
mod icon_button;
|
|
||||||
mod keybinding;
|
mod keybinding;
|
||||||
mod label;
|
mod label;
|
||||||
mod list;
|
mod list;
|
||||||
|
@ -19,13 +17,11 @@ mod stories;
|
||||||
|
|
||||||
pub use avatar::*;
|
pub use avatar::*;
|
||||||
pub use button::*;
|
pub use button::*;
|
||||||
pub use button2::*;
|
|
||||||
pub use checkbox::*;
|
pub use checkbox::*;
|
||||||
pub use context_menu::*;
|
pub use context_menu::*;
|
||||||
pub use disclosure::*;
|
pub use disclosure::*;
|
||||||
pub use divider::*;
|
pub use divider::*;
|
||||||
pub use icon::*;
|
pub use icon::*;
|
||||||
pub use icon_button::*;
|
|
||||||
pub use keybinding::*;
|
pub use keybinding::*;
|
||||||
pub use label::*;
|
pub use label::*;
|
||||||
pub use list::*;
|
pub use list::*;
|
||||||
|
|
|
@ -1,228 +0,0 @@
|
||||||
use gpui::{
|
|
||||||
ClickEvent, DefiniteLength, Div, Hsla, IntoElement, StatefulInteractiveElement, WindowContext,
|
|
||||||
};
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
use crate::{h_stack, Color, Icon, IconButton, IconElement, Label, LineHeightStyle};
|
|
||||||
|
|
||||||
/// Provides the flexibility to use either a standard
|
|
||||||
/// button or an icon button in a given context.
|
|
||||||
pub enum ButtonOrIconButton {
|
|
||||||
Button(Button),
|
|
||||||
IconButton(IconButton),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Button> for ButtonOrIconButton {
|
|
||||||
fn from(value: Button) -> Self {
|
|
||||||
Self::Button(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<IconButton> for ButtonOrIconButton {
|
|
||||||
fn from(value: IconButton) -> Self {
|
|
||||||
Self::IconButton(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Clone, Copy)]
|
|
||||||
pub enum IconPosition {
|
|
||||||
#[default]
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Copy, Clone, PartialEq)]
|
|
||||||
pub enum ButtonVariant {
|
|
||||||
#[default]
|
|
||||||
Ghost,
|
|
||||||
Filled,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ButtonVariant {
|
|
||||||
pub fn bg_color(&self, cx: &mut WindowContext) -> Hsla {
|
|
||||||
match self {
|
|
||||||
ButtonVariant::Ghost => cx.theme().colors().ghost_element_background,
|
|
||||||
ButtonVariant::Filled => cx.theme().colors().element_background,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bg_color_hover(&self, cx: &mut WindowContext) -> Hsla {
|
|
||||||
match self {
|
|
||||||
ButtonVariant::Ghost => cx.theme().colors().ghost_element_hover,
|
|
||||||
ButtonVariant::Filled => cx.theme().colors().element_hover,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bg_color_active(&self, cx: &mut WindowContext) -> Hsla {
|
|
||||||
match self {
|
|
||||||
ButtonVariant::Ghost => cx.theme().colors().ghost_element_active,
|
|
||||||
ButtonVariant::Filled => cx.theme().colors().element_active,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
pub struct Button {
|
|
||||||
disabled: bool,
|
|
||||||
click_handler: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
|
|
||||||
icon: Option<Icon>,
|
|
||||||
icon_position: Option<IconPosition>,
|
|
||||||
label: SharedString,
|
|
||||||
variant: ButtonVariant,
|
|
||||||
width: Option<DefiniteLength>,
|
|
||||||
color: Option<Color>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for Button {
|
|
||||||
type Rendered = gpui::Stateful<Div>;
|
|
||||||
|
|
||||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
|
||||||
let (icon_color, label_color) = match (self.disabled, self.color) {
|
|
||||||
(true, _) => (Color::Disabled, Color::Disabled),
|
|
||||||
(_, None) => (Color::Default, Color::Default),
|
|
||||||
(_, Some(color)) => (Color::from(color), color),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut button = h_stack()
|
|
||||||
.id(SharedString::from(format!("{}", self.label)))
|
|
||||||
.relative()
|
|
||||||
.p_1()
|
|
||||||
.text_ui()
|
|
||||||
.rounded_md()
|
|
||||||
.bg(self.variant.bg_color(cx))
|
|
||||||
.cursor_pointer()
|
|
||||||
.hover(|style| style.bg(self.variant.bg_color_hover(cx)))
|
|
||||||
.active(|style| style.bg(self.variant.bg_color_active(cx)));
|
|
||||||
|
|
||||||
match (self.icon, self.icon_position) {
|
|
||||||
(Some(_), Some(IconPosition::Left)) => {
|
|
||||||
button = button
|
|
||||||
.gap_1()
|
|
||||||
.child(self.render_label(label_color))
|
|
||||||
.children(self.render_icon(icon_color))
|
|
||||||
}
|
|
||||||
(Some(_), Some(IconPosition::Right)) => {
|
|
||||||
button = button
|
|
||||||
.gap_1()
|
|
||||||
.children(self.render_icon(icon_color))
|
|
||||||
.child(self.render_label(label_color))
|
|
||||||
}
|
|
||||||
(_, _) => button = button.child(self.render_label(label_color)),
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(width) = self.width {
|
|
||||||
button = button.w(width).justify_center();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(click_handler) = self.click_handler.clone() {
|
|
||||||
button = button.on_click(move |event, cx| {
|
|
||||||
click_handler(event, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
button
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Button {
|
|
||||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
|
||||||
Self {
|
|
||||||
disabled: false,
|
|
||||||
click_handler: None,
|
|
||||||
icon: None,
|
|
||||||
icon_position: None,
|
|
||||||
label: label.into(),
|
|
||||||
variant: Default::default(),
|
|
||||||
width: Default::default(),
|
|
||||||
color: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ghost(label: impl Into<SharedString>) -> Self {
|
|
||||||
Self::new(label).variant(ButtonVariant::Ghost)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn variant(mut self, variant: ButtonVariant) -> Self {
|
|
||||||
self.variant = variant;
|
|
||||||
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(&ClickEvent, &mut WindowContext) + 'static) -> Self {
|
|
||||||
self.click_handler = Some(Rc::new(handler));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disabled(mut self, disabled: bool) -> Self {
|
|
||||||
self.disabled = disabled;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn color(mut self, color: Option<Color>) -> Self {
|
|
||||||
self.color = color;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn label_color(&self, color: Option<Color>) -> Color {
|
|
||||||
if self.disabled {
|
|
||||||
Color::Disabled
|
|
||||||
} else if let Some(color) = color {
|
|
||||||
color
|
|
||||||
} else {
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_label(&self, color: Color) -> Label {
|
|
||||||
Label::new(self.label.clone())
|
|
||||||
.color(color)
|
|
||||||
.line_height_style(LineHeightStyle::UILabel)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_icon(&self, icon_color: Color) -> Option<IconElement> {
|
|
||||||
self.icon.map(|i| IconElement::new(i).color(icon_color))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
pub struct ButtonGroup {
|
|
||||||
buttons: Vec<Button>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for ButtonGroup {
|
|
||||||
type Rendered = Div;
|
|
||||||
|
|
||||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
|
||||||
let mut group = h_stack();
|
|
||||||
|
|
||||||
for button in self.buttons.into_iter() {
|
|
||||||
group = group.child(button.render(cx));
|
|
||||||
}
|
|
||||||
|
|
||||||
group
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ButtonGroup {
|
|
||||||
pub fn new(buttons: Vec<Button>) -> Self {
|
|
||||||
Self { buttons }
|
|
||||||
}
|
|
||||||
}
|
|
91
crates/ui2/src/components/button/button.rs
Normal file
91
crates/ui2/src/components/button/button.rs
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
use gpui::AnyView;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::{ButtonCommon, ButtonLike, ButtonSize2, ButtonStyle2, Label, LineHeightStyle};
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct Button {
|
||||||
|
base: ButtonLike,
|
||||||
|
label: SharedString,
|
||||||
|
label_color: Option<Color>,
|
||||||
|
selected: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Button {
|
||||||
|
pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
|
||||||
|
Self {
|
||||||
|
base: ButtonLike::new(id),
|
||||||
|
label: label.into(),
|
||||||
|
label_color: None,
|
||||||
|
selected: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selected(mut self, selected: bool) -> Self {
|
||||||
|
self.selected = selected;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn color(mut self, label_color: impl Into<Option<Color>>) -> Self {
|
||||||
|
self.label_color = label_color.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clickable for Button {
|
||||||
|
fn on_click(
|
||||||
|
mut self,
|
||||||
|
handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
|
||||||
|
) -> Self {
|
||||||
|
self.base = self.base.on_click(handler);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Disableable for Button {
|
||||||
|
fn disabled(mut self, disabled: bool) -> Self {
|
||||||
|
self.base = self.base.disabled(disabled);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ButtonCommon for Button {
|
||||||
|
fn id(&self) -> &ElementId {
|
||||||
|
self.base.id()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn style(mut self, style: ButtonStyle2) -> Self {
|
||||||
|
self.base = self.base.style(style);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(mut self, size: ButtonSize2) -> Self {
|
||||||
|
self.base = self.base.size(size);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
|
||||||
|
self.base = self.base.tooltip(tooltip);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for Button {
|
||||||
|
type Rendered = ButtonLike;
|
||||||
|
|
||||||
|
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
||||||
|
let label_color = if self.base.disabled {
|
||||||
|
Color::Disabled
|
||||||
|
} else if self.selected {
|
||||||
|
Color::Selected
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
};
|
||||||
|
|
||||||
|
self.base.child(
|
||||||
|
Label::new(self.label)
|
||||||
|
.color(label_color)
|
||||||
|
.line_height_style(LineHeightStyle::UILabel),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,31 +1,17 @@
|
||||||
use gpui::{
|
use gpui::{rems, AnyElement, AnyView, ClickEvent, Div, Hsla, Rems, Stateful};
|
||||||
rems, AnyElement, AnyView, ClickEvent, Div, Hsla, IntoElement, Rems, Stateful,
|
|
||||||
StatefulInteractiveElement, WindowContext,
|
|
||||||
};
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::{h_stack, prelude::*};
|
use crate::h_stack;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
// 🚧 Heavily WIP 🚧
|
pub trait ButtonCommon: Clickable + Disableable {
|
||||||
|
fn id(&self) -> &ElementId;
|
||||||
// #[derive(Default, PartialEq, Clone, Copy)]
|
fn style(self, style: ButtonStyle2) -> Self;
|
||||||
// pub enum ButtonType2 {
|
fn size(self, size: ButtonSize2) -> Self;
|
||||||
// #[default]
|
fn tooltip(self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self;
|
||||||
// DefaultButton,
|
|
||||||
// IconButton,
|
|
||||||
// ButtonLike,
|
|
||||||
// SplitButton,
|
|
||||||
// ToggleButton,
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Clone, Copy)]
|
|
||||||
pub enum IconPosition2 {
|
|
||||||
#[default]
|
|
||||||
Before,
|
|
||||||
After,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
|
||||||
pub enum ButtonStyle2 {
|
pub enum ButtonStyle2 {
|
||||||
#[default]
|
#[default]
|
||||||
Filled,
|
Filled,
|
||||||
|
@ -34,7 +20,7 @@ pub enum ButtonStyle2 {
|
||||||
Transparent,
|
Transparent,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ButtonStyle {
|
pub struct ButtonStyle {
|
||||||
pub background: Hsla,
|
pub background: Hsla,
|
||||||
pub border_color: Hsla,
|
pub border_color: Hsla,
|
||||||
|
@ -181,82 +167,11 @@ impl ButtonSize2 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub struct Button {
|
|
||||||
// id: ElementId,
|
|
||||||
// icon: Option<Icon>,
|
|
||||||
// icon_color: Option<Color>,
|
|
||||||
// icon_position: Option<IconPosition2>,
|
|
||||||
// label: Option<Label>,
|
|
||||||
// label_color: Option<Color>,
|
|
||||||
// appearance: ButtonAppearance2,
|
|
||||||
// state: InteractionState,
|
|
||||||
// selected: bool,
|
|
||||||
// disabled: bool,
|
|
||||||
// tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
|
|
||||||
// width: Option<DefiniteLength>,
|
|
||||||
// action: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
|
|
||||||
// secondary_action: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
|
|
||||||
// /// Used to pass down some content to the button
|
|
||||||
// /// to enable creating custom buttons.
|
|
||||||
// children: SmallVec<[AnyElement; 2]>,
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub trait ButtonCommon: Clickable + Disableable {
|
|
||||||
fn id(&self) -> &ElementId;
|
|
||||||
fn style(self, style: ButtonStyle2) -> Self;
|
|
||||||
fn size(self, size: ButtonSize2) -> Self;
|
|
||||||
fn tooltip(self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub struct LabelButton {
|
|
||||||
// // Base properties...
|
|
||||||
// id: ElementId,
|
|
||||||
// appearance: ButtonAppearance,
|
|
||||||
// state: InteractionState,
|
|
||||||
// disabled: bool,
|
|
||||||
// size: ButtonSize,
|
|
||||||
// tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
|
|
||||||
// width: Option<DefiniteLength>,
|
|
||||||
// // Button-specific properties...
|
|
||||||
// label: Option<SharedString>,
|
|
||||||
// label_color: Option<Color>,
|
|
||||||
// icon: Option<Icon>,
|
|
||||||
// icon_color: Option<Color>,
|
|
||||||
// icon_position: Option<IconPosition>,
|
|
||||||
// // Define more fields for additional properties as needed
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl ButtonCommon for LabelButton {
|
|
||||||
// fn id(&self) -> &ElementId {
|
|
||||||
// &self.id
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn appearance(&mut self, appearance: ButtonAppearance) -> &mut Self {
|
|
||||||
// self.style= style;
|
|
||||||
// self
|
|
||||||
// }
|
|
||||||
// // implement methods from ButtonCommon trait...
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl LabelButton {
|
|
||||||
// pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
|
|
||||||
// Self {
|
|
||||||
// id: id.into(),
|
|
||||||
// label: Some(label.into()),
|
|
||||||
// // initialize other fields with default values...
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // ... Define other builder methods specific to Button type...
|
|
||||||
// }
|
|
||||||
|
|
||||||
// TODO: Icon Button
|
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct ButtonLike {
|
pub struct ButtonLike {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
style: ButtonStyle2,
|
pub(super) style: ButtonStyle2,
|
||||||
disabled: bool,
|
pub(super) disabled: bool,
|
||||||
size: ButtonSize2,
|
size: ButtonSize2,
|
||||||
tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
|
tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
|
||||||
on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||||
|
@ -325,6 +240,12 @@ impl ButtonCommon for ButtonLike {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ParentElement for ButtonLike {
|
||||||
|
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
||||||
|
&mut self.children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderOnce for ButtonLike {
|
impl RenderOnce for ButtonLike {
|
||||||
type Rendered = Stateful<Div>;
|
type Rendered = Stateful<Div>;
|
||||||
|
|
||||||
|
@ -349,57 +270,3 @@ impl RenderOnce for ButtonLike {
|
||||||
.children(self.children)
|
.children(self.children)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParentElement for ButtonLike {
|
|
||||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
|
||||||
&mut self.children
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub struct ToggleButton {
|
|
||||||
// // based on either IconButton2 or Button, with additional 'selected: bool' property
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl ButtonCommon for ToggleButton {
|
|
||||||
// fn id(&self) -> &ElementId {
|
|
||||||
// &self.id
|
|
||||||
// }
|
|
||||||
// // ... Implement other methods from ButtonCommon trait with builder patterns...
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl ToggleButton {
|
|
||||||
// pub fn new() -> Self {
|
|
||||||
// // Initialize with default values
|
|
||||||
// Self {
|
|
||||||
// // ... initialize fields, possibly with defaults or required parameters...
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // ... Define other builder methods specific to ToggleButton type...
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pub struct SplitButton {
|
|
||||||
// // Base properties...
|
|
||||||
// id: ElementId,
|
|
||||||
// // Button-specific properties, possibly including a DefaultButton
|
|
||||||
// secondary_action: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext)>>,
|
|
||||||
// // More fields as necessary...
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl ButtonCommon for SplitButton {
|
|
||||||
// fn id(&self) -> &ElementId {
|
|
||||||
// &self.id
|
|
||||||
// }
|
|
||||||
// // ... Implement other methods from ButtonCommon trait with builder patterns...
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl SplitButton {
|
|
||||||
// pub fn new(id: impl Into<ElementId>) -> Self {
|
|
||||||
// Self {
|
|
||||||
// id: id.into(),
|
|
||||||
// // ... initialize other fields with default values...
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // ... Define other builder methods specific to SplitButton type...
|
|
||||||
// }
|
|
102
crates/ui2/src/components/button/icon_button.rs
Normal file
102
crates/ui2/src/components/button/icon_button.rs
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
use gpui::{Action, AnyView};
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::{ButtonCommon, ButtonLike, ButtonSize2, ButtonStyle2, Icon, IconElement, IconSize};
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct IconButton {
|
||||||
|
base: ButtonLike,
|
||||||
|
icon: Icon,
|
||||||
|
icon_size: IconSize,
|
||||||
|
icon_color: Color,
|
||||||
|
selected: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IconButton {
|
||||||
|
pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {
|
||||||
|
Self {
|
||||||
|
base: ButtonLike::new(id),
|
||||||
|
icon,
|
||||||
|
icon_size: IconSize::default(),
|
||||||
|
icon_color: Color::Default,
|
||||||
|
selected: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selected(mut self, selected: bool) -> Self {
|
||||||
|
self.selected = selected;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn icon_size(mut self, icon_size: IconSize) -> Self {
|
||||||
|
self.icon_size = icon_size;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn icon_color(mut self, icon_color: Color) -> Self {
|
||||||
|
self.icon_color = icon_color;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn action(self, action: Box<dyn Action>) -> Self {
|
||||||
|
self.on_click(move |_event, cx| cx.dispatch_action(action.boxed_clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clickable for IconButton {
|
||||||
|
fn on_click(
|
||||||
|
mut self,
|
||||||
|
handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
|
||||||
|
) -> Self {
|
||||||
|
self.base = self.base.on_click(handler);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Disableable for IconButton {
|
||||||
|
fn disabled(mut self, disabled: bool) -> Self {
|
||||||
|
self.base = self.base.disabled(disabled);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ButtonCommon for IconButton {
|
||||||
|
fn id(&self) -> &ElementId {
|
||||||
|
self.base.id()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn style(mut self, style: ButtonStyle2) -> Self {
|
||||||
|
self.base = self.base.style(style);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(mut self, size: ButtonSize2) -> Self {
|
||||||
|
self.base = self.base.size(size);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
|
||||||
|
self.base = self.base.tooltip(tooltip);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for IconButton {
|
||||||
|
type Rendered = ButtonLike;
|
||||||
|
|
||||||
|
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
||||||
|
let icon_color = if self.base.disabled {
|
||||||
|
Color::Disabled
|
||||||
|
} else if self.selected {
|
||||||
|
Color::Selected
|
||||||
|
} else {
|
||||||
|
self.icon_color
|
||||||
|
};
|
||||||
|
|
||||||
|
self.base.child(
|
||||||
|
IconElement::new(self.icon)
|
||||||
|
.size(self.icon_size)
|
||||||
|
.color(icon_color),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
7
crates/ui2/src/components/button/mod.rs
Normal file
7
crates/ui2/src/components/button/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
mod button;
|
||||||
|
mod button_like;
|
||||||
|
mod icon_button;
|
||||||
|
|
||||||
|
pub use button::*;
|
||||||
|
pub use button_like::*;
|
||||||
|
pub use icon_button::*;
|
|
@ -39,8 +39,8 @@ impl RenderOnce for Disclosure {
|
||||||
false => Icon::ChevronRight,
|
false => Icon::ChevronRight,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
.when_some(self.on_toggle, move |this, on_toggle| {
|
.when_some(self.on_toggle, move |this, on_toggle| {
|
||||||
this.on_click(move |event, cx| on_toggle(event, cx))
|
this.on_click(move |event, cx| on_toggle(event, cx))
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,135 +0,0 @@
|
||||||
use crate::{h_stack, prelude::*, Icon, IconElement, IconSize};
|
|
||||||
use gpui::{prelude::*, Action, AnyView, ClickEvent, Div, Stateful};
|
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
pub struct IconButton {
|
|
||||||
id: ElementId,
|
|
||||||
icon: Icon,
|
|
||||||
color: Color,
|
|
||||||
size: IconSize,
|
|
||||||
variant: ButtonVariant,
|
|
||||||
disabled: bool,
|
|
||||||
selected: bool,
|
|
||||||
tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>>,
|
|
||||||
on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for IconButton {
|
|
||||||
type Rendered = Stateful<Div>;
|
|
||||||
|
|
||||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
|
||||||
let icon_color = match (self.disabled, self.selected, self.color) {
|
|
||||||
(true, _, _) => Color::Disabled,
|
|
||||||
(false, true, _) => Color::Selected,
|
|
||||||
_ => self.color,
|
|
||||||
};
|
|
||||||
|
|
||||||
let (mut bg_color, bg_active_color) = match self.variant {
|
|
||||||
ButtonVariant::Filled => (
|
|
||||||
cx.theme().colors().element_background,
|
|
||||||
cx.theme().colors().element_active,
|
|
||||||
),
|
|
||||||
ButtonVariant::Ghost => (
|
|
||||||
cx.theme().colors().ghost_element_background,
|
|
||||||
cx.theme().colors().ghost_element_active,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.selected {
|
|
||||||
bg_color = cx.theme().colors().element_selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut button = h_stack()
|
|
||||||
.id(self.id.clone())
|
|
||||||
.justify_center()
|
|
||||||
.rounded_md()
|
|
||||||
.p_1()
|
|
||||||
.bg(bg_color)
|
|
||||||
.cursor_pointer()
|
|
||||||
// Nate: Trying to figure out the right places we want to show a
|
|
||||||
// hover state here. I think it is a bit heavy to have it on every
|
|
||||||
// place we use an icon button.
|
|
||||||
// .hover(|style| style.bg(bg_hover_color))
|
|
||||||
.active(|style| style.bg(bg_active_color))
|
|
||||||
.child(
|
|
||||||
IconElement::new(self.icon)
|
|
||||||
.size(self.size)
|
|
||||||
.color(icon_color),
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(click_handler) = self.on_click {
|
|
||||||
button = button.on_click(move |event, cx| {
|
|
||||||
cx.stop_propagation();
|
|
||||||
click_handler(event, cx);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(tooltip) = self.tooltip {
|
|
||||||
if !self.selected {
|
|
||||||
button = button.tooltip(move |cx| tooltip(cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
button
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IconButton {
|
|
||||||
pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {
|
|
||||||
Self {
|
|
||||||
id: id.into(),
|
|
||||||
icon,
|
|
||||||
color: Color::default(),
|
|
||||||
size: Default::default(),
|
|
||||||
variant: ButtonVariant::default(),
|
|
||||||
selected: false,
|
|
||||||
disabled: false,
|
|
||||||
tooltip: None,
|
|
||||||
on_click: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn icon(mut self, icon: Icon) -> Self {
|
|
||||||
self.icon = icon;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn color(mut self, color: Color) -> Self {
|
|
||||||
self.color = color;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size(mut self, size: IconSize) -> Self {
|
|
||||||
self.size = size;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn variant(mut self, variant: ButtonVariant) -> Self {
|
|
||||||
self.variant = variant;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn selected(mut self, selected: bool) -> Self {
|
|
||||||
self.selected = selected;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disabled(mut self, disabled: bool) -> Self {
|
|
||||||
self.disabled = disabled;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
|
|
||||||
self.tooltip = Some(Box::new(tooltip));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_click(mut self, handler: impl 'static + Fn(&ClickEvent, &mut WindowContext)) -> Self {
|
|
||||||
self.on_click = Some(Box::new(handler));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn action(self, action: Box<dyn Action>) -> Self {
|
|
||||||
self.on_click(move |_event, cx| cx.dispatch_action(action.boxed_clone()))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -78,7 +78,7 @@ impl RenderOnce for ListHeader {
|
||||||
h_stack()
|
h_stack()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.items_center()
|
.items_center()
|
||||||
.children(icons.into_iter().map(|i| i.color(Color::Muted))),
|
.children(icons.into_iter().map(|i| i.icon_color(Color::Muted))),
|
||||||
),
|
),
|
||||||
Some(ListHeaderMeta::Button(label)) => div().child(label),
|
Some(ListHeaderMeta::Button(label)) => div().child(label),
|
||||||
Some(ListHeaderMeta::Text(label)) => div().child(label),
|
Some(ListHeaderMeta::Text(label)) => div().child(label),
|
||||||
|
|
|
@ -2,7 +2,7 @@ use gpui::{Div, Render};
|
||||||
use story::Story;
|
use story::Story;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::{h_stack, Button, Icon, IconPosition};
|
use crate::{Button, ButtonStyle2};
|
||||||
|
|
||||||
pub struct ButtonStory;
|
pub struct ButtonStory;
|
||||||
|
|
||||||
|
@ -12,66 +12,11 @@ impl Render for ButtonStory {
|
||||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
Story::container()
|
Story::container()
|
||||||
.child(Story::title_for::<Button>())
|
.child(Story::title_for::<Button>())
|
||||||
.child(
|
.child(Story::label("Default"))
|
||||||
div()
|
.child(Button::new("default_filled", "Click me"))
|
||||||
.flex()
|
.child(Story::label("Default (Subtle)"))
|
||||||
.gap_8()
|
.child(Button::new("default_subtle", "Click me").style(ButtonStyle2::Subtle))
|
||||||
.child(
|
.child(Story::label("Default (Transparent)"))
|
||||||
div().child(Story::label("Ghost (Default)")).child(
|
.child(Button::new("default_transparent", "Click me").style(ButtonStyle2::Transparent))
|
||||||
h_stack()
|
|
||||||
.gap_2()
|
|
||||||
.child(Button::new("Label").variant(ButtonVariant::Ghost)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(Story::label("Ghost – Left Icon"))
|
|
||||||
.child(
|
|
||||||
h_stack().gap_2().child(
|
|
||||||
Button::new("Label")
|
|
||||||
.variant(ButtonVariant::Ghost)
|
|
||||||
.icon(Icon::Plus)
|
|
||||||
.icon_position(IconPosition::Left),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(Story::label("Ghost – Right Icon"))
|
|
||||||
.child(
|
|
||||||
h_stack().gap_2().child(
|
|
||||||
Button::new("Label")
|
|
||||||
.variant(ButtonVariant::Ghost)
|
|
||||||
.icon(Icon::Plus)
|
|
||||||
.icon_position(IconPosition::Right),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div().child(Story::label("Filled")).child(
|
|
||||||
h_stack()
|
|
||||||
.gap_2()
|
|
||||||
.child(Button::new("Label").variant(ButtonVariant::Filled)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(Story::label("Filled – Left Button"))
|
|
||||||
.child(
|
|
||||||
h_stack().gap_2().child(
|
|
||||||
Button::new("Label")
|
|
||||||
.variant(ButtonVariant::Filled)
|
|
||||||
.icon(Icon::Plus)
|
|
||||||
.icon_position(IconPosition::Left),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(Story::label("Filled – Right Button"))
|
|
||||||
.child(
|
|
||||||
h_stack().gap_2().child(
|
|
||||||
Button::new("Label")
|
|
||||||
.variant(ButtonVariant::Filled)
|
|
||||||
.icon(Icon::Plus)
|
|
||||||
.icon_position(IconPosition::Right),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(Story::label("Button with `on_click`"))
|
|
||||||
.child(
|
|
||||||
Button::new("Label")
|
|
||||||
.variant(ButtonVariant::Ghost)
|
|
||||||
.on_click(|_, _cx| println!("Button clicked.")),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,5 @@ pub use crate::clickable::*;
|
||||||
pub use crate::disableable::*;
|
pub use crate::disableable::*;
|
||||||
pub use crate::fixed::*;
|
pub use crate::fixed::*;
|
||||||
pub use crate::selectable::*;
|
pub use crate::selectable::*;
|
||||||
pub use crate::StyledExt;
|
pub use crate::{ButtonCommon, Color, StyledExt};
|
||||||
pub use crate::{ButtonVariant, Color};
|
|
||||||
pub use theme::ActiveTheme;
|
pub use theme::ActiveTheme;
|
||||||
|
|
|
@ -181,6 +181,7 @@ pub mod simple_message_notification {
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{borrow::Cow, sync::Arc};
|
use std::{borrow::Cow, sync::Arc};
|
||||||
|
use ui::prelude::*;
|
||||||
use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt};
|
use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt};
|
||||||
|
|
||||||
#[derive(Clone, Default, Deserialize, PartialEq)]
|
#[derive(Clone, Default, Deserialize, PartialEq)]
|
||||||
|
@ -287,12 +288,14 @@ pub mod simple_message_notification {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.children(self.click_message.iter().map(|message| {
|
.children(self.click_message.iter().map(|message| {
|
||||||
Button::new(message.clone()).on_click(cx.listener(|this, _, cx| {
|
Button::new(message.clone(), message.clone()).on_click(cx.listener(
|
||||||
if let Some(on_click) = this.on_click.as_ref() {
|
|this, _, cx| {
|
||||||
(on_click)(cx)
|
if let Some(on_click) = this.on_click.as_ref() {
|
||||||
};
|
(on_click)(cx)
|
||||||
this.dismiss(cx)
|
};
|
||||||
}))
|
this.dismiss(cx)
|
||||||
|
},
|
||||||
|
))
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,14 +71,14 @@ impl Render for StatusBar {
|
||||||
div()
|
div()
|
||||||
.border()
|
.border()
|
||||||
.border_color(gpui::red())
|
.border_color(gpui::red())
|
||||||
.child(Button::new("15:22")),
|
.child(Button::new("status_line_column_numbers", "15:22")),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
// TODO: Language picker
|
// TODO: Language picker
|
||||||
div()
|
div()
|
||||||
.border()
|
.border()
|
||||||
.border_color(gpui::red())
|
.border_color(gpui::red())
|
||||||
.child(Button::new("Rust")),
|
.child(Button::new("status_buffer_language", "Rust")),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
|
|
|
@ -94,9 +94,9 @@ impl Render for Toolbar {
|
||||||
.border()
|
.border()
|
||||||
.border_color(gpui::red())
|
.border_color(gpui::red())
|
||||||
.p_1()
|
.p_1()
|
||||||
.child(Button::new("crates"))
|
.child(Button::new("breadcrumb_crates", "crates"))
|
||||||
.child(Label::new("/").color(Color::Muted))
|
.child(Label::new("/").color(Color::Muted))
|
||||||
.child(Button::new("workspace2")),
|
.child(Button::new("breadcrumb_workspace2", "workspace2")),
|
||||||
)
|
)
|
||||||
// Toolbar right side
|
// Toolbar right side
|
||||||
.child(
|
.child(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue