Improve focus (#14)
- Add `FocusableCycle` trait to help impl cycle focus. - Add `outline` style to StyledExt.
This commit is contained in:
parent
24348ec874
commit
3ccadf6cc3
15 changed files with 226 additions and 86 deletions
|
|
@ -23,6 +23,7 @@ pub fn init(_app_state: Arc<AppState>, cx: &mut AppContext) {
|
|||
|
||||
Theme::init(cx);
|
||||
ui::init(cx);
|
||||
story::init(cx);
|
||||
}
|
||||
|
||||
pub struct StoryWorkspace {
|
||||
|
|
|
|||
|
|
@ -34,19 +34,19 @@ impl Render for ButtonStory {
|
|||
.child(
|
||||
section("Normal Button", cx)
|
||||
.child(
|
||||
Button::new("button-1")
|
||||
Button::new("button-1", cx)
|
||||
.label("Primary Button")
|
||||
.style(ButtonStyle::Primary)
|
||||
.on_click(Self::on_click),
|
||||
)
|
||||
.child(
|
||||
Button::new("button-2")
|
||||
Button::new("button-2", cx)
|
||||
.label("Secondary Button")
|
||||
.style(ButtonStyle::Secondary)
|
||||
.on_click(Self::on_click),
|
||||
)
|
||||
.child(
|
||||
Button::new("button-4")
|
||||
Button::new("button-4", cx)
|
||||
.label("Danger Button")
|
||||
.style(ButtonStyle::Danger)
|
||||
.on_click(Self::on_click),
|
||||
|
|
@ -55,21 +55,21 @@ impl Render for ButtonStory {
|
|||
.child(
|
||||
section("Button with Icon", cx)
|
||||
.child(
|
||||
Button::new("button-icon-1")
|
||||
Button::new("button-icon-1", cx)
|
||||
.label("Confirm")
|
||||
.icon(IconName::Check)
|
||||
.style(ButtonStyle::Primary)
|
||||
.on_click(Self::on_click),
|
||||
)
|
||||
.child(
|
||||
Button::new("button-icon-2")
|
||||
Button::new("button-icon-2", cx)
|
||||
.label("Abort")
|
||||
.icon(IconName::Close)
|
||||
.style(ButtonStyle::Secondary)
|
||||
.on_click(Self::on_click),
|
||||
)
|
||||
.child(
|
||||
Button::new("button-icon-3")
|
||||
Button::new("button-icon-3", cx)
|
||||
.label("Maximize")
|
||||
.icon(Icon::new(IconName::Maximize))
|
||||
.style(ButtonStyle::Secondary)
|
||||
|
|
@ -83,21 +83,21 @@ impl Render for ButtonStory {
|
|||
.child(
|
||||
section("Small Size", cx)
|
||||
.child(
|
||||
Button::new("button-6")
|
||||
Button::new("button-6", cx)
|
||||
.label("Primary Button")
|
||||
.style(ButtonStyle::Primary)
|
||||
.size(ButtonSize::Small)
|
||||
.on_click(Self::on_click),
|
||||
)
|
||||
.child(
|
||||
Button::new("button-7")
|
||||
Button::new("button-7", cx)
|
||||
.label("Secondary Button")
|
||||
.style(ButtonStyle::Secondary)
|
||||
.size(ButtonSize::Small)
|
||||
.on_click(Self::on_click),
|
||||
)
|
||||
.child(
|
||||
Button::new("button-8")
|
||||
Button::new("button-8", cx)
|
||||
.label("Danger Button")
|
||||
.style(ButtonStyle::Danger)
|
||||
.size(ButtonSize::Small)
|
||||
|
|
@ -107,21 +107,21 @@ impl Render for ButtonStory {
|
|||
.child(
|
||||
section("XSmall Size", cx)
|
||||
.child(
|
||||
Button::new("button-xs-1")
|
||||
Button::new("button-xs-1", cx)
|
||||
.label("Primary Button")
|
||||
.style(ButtonStyle::Primary)
|
||||
.size(ButtonSize::XSmall)
|
||||
.on_click(Self::on_click),
|
||||
)
|
||||
.child(
|
||||
Button::new("button-xs-2")
|
||||
Button::new("button-xs-2", cx)
|
||||
.label("Secondary Button")
|
||||
.style(ButtonStyle::Secondary)
|
||||
.size(ButtonSize::XSmall)
|
||||
.on_click(Self::on_click),
|
||||
)
|
||||
.child(
|
||||
Button::new("button-xs-3")
|
||||
Button::new("button-xs-3", cx)
|
||||
.label("Danger Button")
|
||||
.style(ButtonStyle::Danger)
|
||||
.size(ButtonSize::XSmall)
|
||||
|
|
@ -135,21 +135,21 @@ impl Render for ButtonStory {
|
|||
.child(
|
||||
section("Disabled Button", cx)
|
||||
.child(
|
||||
Button::new("button-disabled1")
|
||||
Button::new("button-disabled1", cx)
|
||||
.label("Disabled Button")
|
||||
.style(ButtonStyle::Primary)
|
||||
.on_click(Self::on_click)
|
||||
.disabled(true),
|
||||
)
|
||||
.child(
|
||||
Button::new("button-disabled1")
|
||||
Button::new("button-disabled1", cx)
|
||||
.label("Disabled Button")
|
||||
.style(ButtonStyle::Secondary)
|
||||
.on_click(Self::on_click)
|
||||
.disabled(true),
|
||||
)
|
||||
.child(
|
||||
Button::new("button-disabled1")
|
||||
Button::new("button-disabled1", cx)
|
||||
.label("Disabled Button")
|
||||
.style(ButtonStyle::Danger)
|
||||
.on_click(Self::on_click)
|
||||
|
|
@ -159,19 +159,19 @@ impl Render for ButtonStory {
|
|||
.child(
|
||||
section("Selected Style", cx)
|
||||
.child(
|
||||
Button::new("button-selected-1")
|
||||
Button::new("button-selected-1", cx)
|
||||
.label("Selected Button")
|
||||
.style(ButtonStyle::Primary)
|
||||
.selected(true),
|
||||
)
|
||||
.child(
|
||||
Button::new("button-selected-2")
|
||||
Button::new("button-selected-2", cx)
|
||||
.label("Selected Button")
|
||||
.style(ButtonStyle::Secondary)
|
||||
.selected(true),
|
||||
)
|
||||
.child(
|
||||
Button::new("button-selected-3")
|
||||
Button::new("button-selected-3", cx)
|
||||
.label("Selected Button")
|
||||
.style(ButtonStyle::Danger)
|
||||
.selected(true),
|
||||
|
|
@ -181,46 +181,46 @@ impl Render for ButtonStory {
|
|||
.child(
|
||||
section("Icon Button", cx)
|
||||
.child(
|
||||
Button::new("icon-button-0")
|
||||
Button::new("icon-button-0", cx)
|
||||
.icon(IconName::Search)
|
||||
.style(ButtonStyle::Primary),
|
||||
)
|
||||
.child(Button::new("icon-button-1").icon(IconName::Info))
|
||||
.child(Button::new("icon-button-1", cx).icon(IconName::Info))
|
||||
.child(
|
||||
Button::new("icon-button-2")
|
||||
Button::new("icon-button-2", cx)
|
||||
.icon(IconName::Close)
|
||||
.style(ButtonStyle::Danger),
|
||||
)
|
||||
.child(
|
||||
Button::new("icon-button-3")
|
||||
Button::new("icon-button-3", cx)
|
||||
.icon(IconName::Search)
|
||||
.size(ButtonSize::Small)
|
||||
.style(ButtonStyle::Primary),
|
||||
)
|
||||
.child(
|
||||
Button::new("icon-button-4")
|
||||
Button::new("icon-button-4", cx)
|
||||
.icon(IconName::Info)
|
||||
.size(ButtonSize::Small),
|
||||
)
|
||||
.child(
|
||||
Button::new("icon-button-5")
|
||||
Button::new("icon-button-5", cx)
|
||||
.icon(IconName::Close)
|
||||
.size(ButtonSize::Small)
|
||||
.style(ButtonStyle::Danger),
|
||||
)
|
||||
.child(
|
||||
Button::new("icon-button-6")
|
||||
Button::new("icon-button-6", cx)
|
||||
.icon(IconName::Search)
|
||||
.size(ButtonSize::XSmall)
|
||||
.style(ButtonStyle::Primary),
|
||||
)
|
||||
.child(
|
||||
Button::new("icon-button-7")
|
||||
Button::new("icon-button-7", cx)
|
||||
.icon(IconName::Info)
|
||||
.size(ButtonSize::XSmall),
|
||||
)
|
||||
.child(
|
||||
Button::new("icon-button-8")
|
||||
Button::new("icon-button-8", cx)
|
||||
.icon(IconName::Close)
|
||||
.size(ButtonSize::XSmall)
|
||||
.style(ButtonStyle::Danger),
|
||||
|
|
|
|||
|
|
@ -1,13 +1,24 @@
|
|||
use gpui::{
|
||||
ClickEvent, IntoElement, ParentElement as _, Render, Styled as _, View, ViewContext,
|
||||
VisualContext, WindowContext,
|
||||
actions, AppContext, ClickEvent, FocusHandle, FocusableView, InteractiveElement, IntoElement,
|
||||
KeyBinding, ParentElement as _, Render, Styled, View, ViewContext, VisualContext,
|
||||
WindowContext,
|
||||
};
|
||||
|
||||
use ui::{input::TextInput, v_flex, IconName};
|
||||
use ui::{button::Button, h_flex, input::TextInput, v_flex, FocusableCycle, IconName};
|
||||
|
||||
use crate::section;
|
||||
|
||||
actions!(input_story, [Tab, TabPrev]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.bind_keys([
|
||||
KeyBinding::new("shift-tab", TabPrev, Some("InputStory")),
|
||||
KeyBinding::new("tab", Tab, Some("InputStory")),
|
||||
])
|
||||
}
|
||||
|
||||
pub struct InputStory {
|
||||
focus_handle: FocusHandle,
|
||||
input1: View<TextInput>,
|
||||
input2: View<TextInput>,
|
||||
mash_input: View<TextInput>,
|
||||
|
|
@ -54,6 +65,7 @@ impl InputStory {
|
|||
});
|
||||
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
input1,
|
||||
input2: cx.new_view(|cx| {
|
||||
let mut input = TextInput::new(cx);
|
||||
|
|
@ -77,11 +89,46 @@ impl InputStory {
|
|||
fn on_change(ev: &ClickEvent, cx: &mut WindowContext) {
|
||||
println!("Input changed: {:?}", ev);
|
||||
}
|
||||
|
||||
fn tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
|
||||
self.cycle_focus(true, cx);
|
||||
}
|
||||
|
||||
fn tab_prev(&mut self, _: &TabPrev, cx: &mut ViewContext<Self>) {
|
||||
self.cycle_focus(false, cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableCycle for InputStory {
|
||||
fn cycle_focus_handles(&self, cx: &mut ViewContext<Self>) -> Vec<FocusHandle> {
|
||||
[
|
||||
&self.input1,
|
||||
&self.input2,
|
||||
&self.disabled_input,
|
||||
&self.mash_input,
|
||||
&self.prefix_input1,
|
||||
&self.both_input1,
|
||||
&self.suffix_input1,
|
||||
]
|
||||
.iter()
|
||||
.map(|v| v.focus_handle(cx))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for InputStory {
|
||||
fn focus_handle(&self, cx: &gpui::AppContext) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for InputStory {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.key_context("InputStory")
|
||||
.track_focus(&self.focus_handle)
|
||||
.on_action(cx.listener(Self::tab))
|
||||
.on_action(cx.listener(Self::tab_prev))
|
||||
.size_full()
|
||||
.p_4()
|
||||
.justify_start()
|
||||
|
|
@ -102,5 +149,23 @@ impl Render for InputStory {
|
|||
.child(self.both_input1.clone())
|
||||
.child(self.suffix_input1.clone()),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.w_full()
|
||||
.gap_3()
|
||||
.child(
|
||||
Button::new("btn-submit", cx)
|
||||
.w_full()
|
||||
.style(ui::button::ButtonStyle::Primary)
|
||||
.label("Submit"),
|
||||
)
|
||||
.child(
|
||||
Button::new("btn-cancel", cx)
|
||||
.w_full()
|
||||
.label("Cancel")
|
||||
.into_element(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ pub use switch_story::SwitchStory;
|
|||
pub use tooltip_story::TooltipStory;
|
||||
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, Div, Element,
|
||||
EventEmitter, FocusableView, InteractiveElement, IntoElement, ParentElement, Pixels, Render,
|
||||
ScrollHandle, SharedString, StatefulInteractiveElement, Styled as _, Task, View, ViewContext,
|
||||
VisualContext, WindowContext,
|
||||
div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, Div, EventEmitter,
|
||||
FocusableView, InteractiveElement, IntoElement, ParentElement, Pixels, Render, ScrollHandle,
|
||||
SharedString, StatefulInteractiveElement, Styled as _, Task, View, ViewContext, VisualContext,
|
||||
WindowContext,
|
||||
};
|
||||
use workspace::{
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
|
|
@ -33,7 +33,11 @@ use workspace::{
|
|||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use ui::{divider::Divider, h_flex, label::Label, v_flex, StyledExt};
|
||||
use ui::{divider::Divider, h_flex, label::Label, v_flex};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
input_story::init(cx);
|
||||
}
|
||||
|
||||
pub fn story_case(
|
||||
name: &'static str,
|
||||
|
|
@ -286,7 +290,6 @@ impl Panel for MyPanel {
|
|||
}
|
||||
|
||||
fn set_size(&mut self, size: Option<gpui::Pixels>, cx: &mut ViewContext<Self>) {
|
||||
dbg!("================ set_size", size);
|
||||
if let Some(size) = size {
|
||||
self.update_size(size)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ use core::time;
|
|||
|
||||
use fake::Fake;
|
||||
use gpui::{
|
||||
actions, div, prelude::FluentBuilder as _, px, ElementId, FocusHandle, FocusableView,
|
||||
InteractiveElement, IntoElement, ParentElement, Render, RenderOnce, Styled, Timer, View,
|
||||
ViewContext, VisualContext, WindowContext,
|
||||
actions, div, px, ElementId, FocusHandle, FocusableView, InteractiveElement, IntoElement,
|
||||
ParentElement, Render, RenderOnce, Styled, Timer, View, ViewContext, VisualContext,
|
||||
WindowContext,
|
||||
};
|
||||
|
||||
use ui::{
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ impl Render for PickerStory {
|
|||
.gap_6()
|
||||
.child(
|
||||
v_flex().items_start().child(
|
||||
Button::new("show-picker")
|
||||
Button::new("show-picker", cx)
|
||||
.label("Show Picker...")
|
||||
.icon(IconName::Search)
|
||||
.style(ButtonStyle::Primary)
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ impl Render for Form {
|
|||
.child("This is a form container.")
|
||||
.child(self.input1.clone())
|
||||
.child(
|
||||
Button::primary("submit", "Submit")
|
||||
Button::primary("submit", "Submit", cx)
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
|
||||
)
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ impl PopoverStory {
|
|||
}
|
||||
|
||||
impl Render for PopoverStory {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let form = self.form.clone();
|
||||
|
||||
v_flex()
|
||||
|
|
@ -76,15 +76,15 @@ impl Render for PopoverStory {
|
|||
.child(
|
||||
v_flex().gap_4().child(
|
||||
Popover::new("info-top-left")
|
||||
.trigger(Button::new("info-top-left").label("Top Left"))
|
||||
.trigger(Button::new("info-top-left", cx).label("Top Left"))
|
||||
.content(|cx| {
|
||||
PopoverContent::new(cx, |_| {
|
||||
PopoverContent::new(cx, |cx| {
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.child("Hello, this is a Popover.")
|
||||
.child(Divider::horizontal())
|
||||
.child(
|
||||
Button::new("info1")
|
||||
Button::new("info1", cx)
|
||||
.label("Yes")
|
||||
.width(px(80.))
|
||||
.size(ButtonSize::Small),
|
||||
|
|
@ -97,15 +97,15 @@ impl Render for PopoverStory {
|
|||
.child(
|
||||
Popover::new("info-top-right")
|
||||
.anchor(AnchorCorner::TopRight)
|
||||
.trigger(Button::new("info-top-right").label("Top Right"))
|
||||
.trigger(Button::new("info-top-right", cx).label("Top Right"))
|
||||
.content(|cx| {
|
||||
PopoverContent::new(cx, |_| {
|
||||
PopoverContent::new(cx, |cx| {
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.child("Hello, this is a Popover on the Top Right.")
|
||||
.child(Divider::horizontal())
|
||||
.child(
|
||||
Button::new("info1")
|
||||
Button::new("info1", cx)
|
||||
.label("Yes")
|
||||
.width(px(80.))
|
||||
.size(ButtonSize::Small),
|
||||
|
|
@ -124,7 +124,9 @@ impl Render for PopoverStory {
|
|||
Popover::new("info-bottom-left")
|
||||
.anchor(AnchorCorner::BottomLeft)
|
||||
.trigger(
|
||||
Button::new("pop").label("Popup with Form").width(px(300.)),
|
||||
Button::new("pop", cx)
|
||||
.label("Popup with Form")
|
||||
.width(px(300.)),
|
||||
)
|
||||
.content(move |_| form.clone()),
|
||||
)
|
||||
|
|
@ -133,18 +135,18 @@ impl Render for PopoverStory {
|
|||
.anchor(AnchorCorner::BottomRight)
|
||||
.mouse_button(MouseButton::Right)
|
||||
.trigger(
|
||||
Button::new("pop")
|
||||
Button::new("pop", cx)
|
||||
.label("Mouse Right Click")
|
||||
.width(px(300.)),
|
||||
)
|
||||
.content(|cx| {
|
||||
PopoverContent::new(cx, |_| {
|
||||
PopoverContent::new(cx, |cx| {
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.child("Hello, this is a Popover on the Bottom Right.")
|
||||
.child(Divider::horizontal())
|
||||
.child(
|
||||
Button::new("info1")
|
||||
Button::new("info1", cx)
|
||||
.label("Yes")
|
||||
.width(px(80.))
|
||||
.size(ButtonSize::Small),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use gpui::{
|
||||
div, px, CursorStyle, InteractiveElement, ParentElement, Render, StatefulInteractiveElement,
|
||||
div, CursorStyle, InteractiveElement, ParentElement, Render, StatefulInteractiveElement,
|
||||
Styled, View, VisualContext as _, WindowContext,
|
||||
};
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ impl TooltipStory {
|
|||
}
|
||||
|
||||
impl Render for TooltipStory {
|
||||
fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
|
||||
v_flex()
|
||||
.p_4()
|
||||
.gap_5()
|
||||
|
|
@ -33,7 +33,7 @@ impl Render for TooltipStory {
|
|||
div()
|
||||
.cursor(CursorStyle::PointingHand)
|
||||
.child(
|
||||
Button::new("button")
|
||||
Button::new("button", cx)
|
||||
.label("Hover me")
|
||||
.style(ButtonStyle::Primary),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
use crate::{
|
||||
h_flex,
|
||||
theme::{ActiveTheme, Colorize as _, ThemeMode},
|
||||
Clickable, Disableable, Icon, Selectable,
|
||||
Clickable, Disableable, Icon, Selectable, StyledExt,
|
||||
};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, px, ClickEvent, DefiniteLength, Div, ElementId, Hsla,
|
||||
InteractiveElement, IntoElement, MouseButton, ParentElement, RenderOnce, SharedString,
|
||||
StatefulInteractiveElement as _, Styled, WindowContext,
|
||||
div, prelude::FluentBuilder as _, px, ClickEvent, DefiniteLength, Div, ElementId, FocusHandle,
|
||||
Focusable, Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement, Render,
|
||||
RenderOnce, SharedString, StatefulInteractiveElement as _, Styled, View, ViewContext,
|
||||
VisualContext, WindowContext,
|
||||
};
|
||||
|
||||
pub enum ButtonRounded {
|
||||
|
|
@ -34,6 +35,7 @@ pub enum ButtonStyle {
|
|||
pub struct Button {
|
||||
pub base: Div,
|
||||
id: ElementId,
|
||||
focus_handle: FocusHandle,
|
||||
icon: Option<Icon>,
|
||||
label: Option<SharedString>,
|
||||
disabled: bool,
|
||||
|
|
@ -48,9 +50,10 @@ pub struct Button {
|
|||
}
|
||||
|
||||
impl Button {
|
||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
pub fn new(id: impl Into<ElementId>, cx: &mut WindowContext) -> Self {
|
||||
Self {
|
||||
base: div(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
id: id.into(),
|
||||
icon: None,
|
||||
label: None,
|
||||
|
|
@ -66,16 +69,28 @@ impl Button {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn primary(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
|
||||
Self::new(id).label(label).style(ButtonStyle::Primary)
|
||||
pub fn primary(
|
||||
id: impl Into<ElementId>,
|
||||
label: impl Into<SharedString>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Self {
|
||||
Self::new(id, cx).label(label).style(ButtonStyle::Primary)
|
||||
}
|
||||
|
||||
pub fn danger(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
|
||||
Self::new(id).label(label).style(ButtonStyle::Danger)
|
||||
pub fn danger(
|
||||
id: impl Into<ElementId>,
|
||||
label: impl Into<SharedString>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Self {
|
||||
Self::new(id, cx).label(label).style(ButtonStyle::Danger)
|
||||
}
|
||||
|
||||
pub fn small(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
|
||||
Self::new(id).label(label).size(ButtonSize::Small)
|
||||
pub fn small(
|
||||
id: impl Into<ElementId>,
|
||||
label: impl Into<SharedString>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Self {
|
||||
Self::new(id, cx).label(label).size(ButtonSize::Small)
|
||||
}
|
||||
|
||||
pub fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
|
||||
|
|
@ -141,14 +156,22 @@ impl Clickable for Button {
|
|||
}
|
||||
}
|
||||
|
||||
impl Styled for Button {
|
||||
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
||||
self.base.style()
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Button {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let theme = cx.theme();
|
||||
let style: ButtonStyle = self.style;
|
||||
let normal_style = style.normal(cx);
|
||||
let focused = self.focus_handle.is_focused(cx);
|
||||
|
||||
self.base
|
||||
.id(self.id)
|
||||
.track_focus(&self.focus_handle)
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
|
|
@ -192,6 +215,9 @@ impl RenderOnce for Button {
|
|||
.border_color(normal_style.border)
|
||||
.bg(normal_style.bg)
|
||||
})
|
||||
.when(focused, |this| {
|
||||
this.border_color(cx.theme().ring).debug_blue()
|
||||
})
|
||||
.when_some(
|
||||
self.on_click.filter(|_| !self.disabled),
|
||||
|this, on_click| {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ use crate::{
|
|||
list::ListItem,
|
||||
picker::{self, Picker, PickerDelegate},
|
||||
theme::ActiveTheme,
|
||||
Icon, IconName,
|
||||
Icon, IconName, StyledExt,
|
||||
};
|
||||
|
||||
/// A trait for items that can be displayed in a dropdown.
|
||||
|
|
@ -221,13 +221,12 @@ where
|
|||
D: DropdownDelegate + 'static,
|
||||
{
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let group_id = format!("dropdown-group:{}", self.id);
|
||||
let title = self.title.clone().unwrap_or_else(|| "Select...".into());
|
||||
let focused = self.focus_handle.is_focused(cx);
|
||||
|
||||
div()
|
||||
.key_context("Dropdown")
|
||||
.group(group_id.clone())
|
||||
.group(format!("dropdown-group:{}", self.id))
|
||||
.track_focus(&self.focus_handle)
|
||||
.on_action(cx.listener(Self::up))
|
||||
.on_action(cx.listener(Self::down))
|
||||
|
|
@ -248,9 +247,9 @@ where
|
|||
.border_color(cx.theme().input)
|
||||
.rounded(px(cx.theme().radius))
|
||||
.shadow_sm()
|
||||
.when(focused, |this| this.outline(cx))
|
||||
.px_3()
|
||||
.py_2()
|
||||
.when(focused, |this| this.border_color(cx.theme().ring))
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
this.open = !this.open;
|
||||
cx.notify();
|
||||
|
|
@ -280,20 +279,12 @@ struct DropdownMenuElement<D: DropdownDelegate + 'static> {
|
|||
dropdown: View<Dropdown<D>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct DropdownMenuElementState {
|
||||
menu_element: Option<AnyElement>,
|
||||
layout_id: Option<LayoutId>,
|
||||
}
|
||||
|
||||
impl Default for DropdownMenuElementState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
menu_element: None,
|
||||
layout_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> IntoElement for DropdownMenuElement<D>
|
||||
where
|
||||
D: DropdownDelegate + 'static,
|
||||
|
|
|
|||
40
crates/ui/src/focusable.rs
Normal file
40
crates/ui/src/focusable.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
use gpui::{FocusHandle, ViewContext};
|
||||
|
||||
/// A trait for views that can cycle focus between its children.
|
||||
///
|
||||
/// This will provide a default implementation for the `cycle_focus` method that will cycle focus.
|
||||
///
|
||||
/// You should implement the `cycle_focus_handles` method to return a list of focus handles that
|
||||
/// should be cycled, and the cycle will follow the order of the list.
|
||||
pub trait FocusableCycle {
|
||||
/// Returns a list of focus handles that should be cycled.
|
||||
fn cycle_focus_handles(&self, cx: &mut ViewContext<Self>) -> Vec<FocusHandle>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Cycles focus between the focus handles returned by `cycle_focus_handles`.
|
||||
/// If `is_next` is `true`, it will cycle to the next focus handle, otherwise it will cycle to prev.
|
||||
fn cycle_focus(&self, is_next: bool, cx: &mut ViewContext<Self>)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let focused_handle = cx.focused();
|
||||
let handles = self.cycle_focus_handles(cx);
|
||||
let handles = if is_next {
|
||||
handles
|
||||
} else {
|
||||
handles.into_iter().rev().collect()
|
||||
};
|
||||
|
||||
let fallback_handle = handles[0].clone();
|
||||
let target_focus_handle = handles
|
||||
.into_iter()
|
||||
.skip_while(|handle| Some(handle) != focused_handle.as_ref())
|
||||
.skip(1)
|
||||
.next()
|
||||
.unwrap_or(fallback_handle);
|
||||
|
||||
target_focus_handle.focus(cx);
|
||||
cx.stop_propagation();
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ use std::ops::Range;
|
|||
|
||||
use crate::event::InterativeElementExt as _;
|
||||
use crate::theme::ActiveTheme;
|
||||
use crate::StyledExt as _;
|
||||
use blink_cursor::BlinkCursor;
|
||||
use gpui::*;
|
||||
use prelude::FluentBuilder as _;
|
||||
|
|
@ -679,14 +680,11 @@ impl Render for TextInput {
|
|||
.h_10()
|
||||
.when(self.appearance, |this| {
|
||||
this.bg(cx.theme().input)
|
||||
.border_color(if focused {
|
||||
cx.theme().ring
|
||||
} else {
|
||||
cx.theme().input
|
||||
})
|
||||
.border_color(cx.theme().input)
|
||||
.border_1()
|
||||
.rounded(px(cx.theme().radius))
|
||||
.shadow_sm()
|
||||
.when(focused, |this| this.outline(cx))
|
||||
.px_3()
|
||||
.bg(if self.disabled {
|
||||
cx.theme().muted
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
mod clickable;
|
||||
mod disableable;
|
||||
mod event;
|
||||
mod focusable;
|
||||
mod icon;
|
||||
mod scrollbar;
|
||||
mod selectable;
|
||||
|
|
@ -28,6 +29,7 @@ pub mod tooltip;
|
|||
pub use clickable::Clickable;
|
||||
pub use disableable::Disableable;
|
||||
pub use event::InterativeElementExt;
|
||||
pub use focusable::FocusableCycle;
|
||||
pub use selectable::{Selectable, Selection};
|
||||
|
||||
pub use icon::*;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::theme::{hsl, ActiveTheme};
|
||||
use gpui::{hsla, point, px, BoxShadow, Styled, WindowContext};
|
||||
use gpui::{div, hsla, point, px, BoxShadow, Styled, WindowContext};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
|
||||
pub enum ElevationIndex {
|
||||
|
|
@ -96,6 +96,18 @@ pub trait StyledExt: Styled + Sized {
|
|||
fn debug_pink(self) -> Self {
|
||||
self.border_1().border_color(hsl(300., 100., 47.))
|
||||
}
|
||||
|
||||
/// Render a border with a width of 1px, color ring color
|
||||
///
|
||||
/// Please ensure this after the shadow
|
||||
fn outline(self, cx: &WindowContext) -> Self {
|
||||
self.shadow(smallvec![BoxShadow {
|
||||
color: cx.theme().ring,
|
||||
offset: point(px(0.), px(0.)),
|
||||
blur_radius: px(0.1),
|
||||
spread_radius: px(1.),
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Styled> StyledExt for E {}
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ impl Colors {
|
|||
accent_foreground: hsl(240.0, 5.9, 10.0),
|
||||
border: hsl(240.0, 5.9, 90.0),
|
||||
input: hsl(240.0, 5.9, 90.0),
|
||||
ring: hsl(240.0, 5.9, 10.0),
|
||||
ring: hsl(240.0, 5.9, 65.0),
|
||||
selection: hsl(211.0, 97.0, 85.0),
|
||||
scrollbar: Hsla::transparent_black(),
|
||||
scrollbar_thumb: hsl(240.0, 5.9, 90.0).opacity(0.7),
|
||||
|
|
|
|||
Loading…
Reference in a new issue