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);
|
Theme::init(cx);
|
||||||
ui::init(cx);
|
ui::init(cx);
|
||||||
|
story::init(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StoryWorkspace {
|
pub struct StoryWorkspace {
|
||||||
|
|
|
||||||
|
|
@ -34,19 +34,19 @@ impl Render for ButtonStory {
|
||||||
.child(
|
.child(
|
||||||
section("Normal Button", cx)
|
section("Normal Button", cx)
|
||||||
.child(
|
.child(
|
||||||
Button::new("button-1")
|
Button::new("button-1", cx)
|
||||||
.label("Primary Button")
|
.label("Primary Button")
|
||||||
.style(ButtonStyle::Primary)
|
.style(ButtonStyle::Primary)
|
||||||
.on_click(Self::on_click),
|
.on_click(Self::on_click),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("button-2")
|
Button::new("button-2", cx)
|
||||||
.label("Secondary Button")
|
.label("Secondary Button")
|
||||||
.style(ButtonStyle::Secondary)
|
.style(ButtonStyle::Secondary)
|
||||||
.on_click(Self::on_click),
|
.on_click(Self::on_click),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("button-4")
|
Button::new("button-4", cx)
|
||||||
.label("Danger Button")
|
.label("Danger Button")
|
||||||
.style(ButtonStyle::Danger)
|
.style(ButtonStyle::Danger)
|
||||||
.on_click(Self::on_click),
|
.on_click(Self::on_click),
|
||||||
|
|
@ -55,21 +55,21 @@ impl Render for ButtonStory {
|
||||||
.child(
|
.child(
|
||||||
section("Button with Icon", cx)
|
section("Button with Icon", cx)
|
||||||
.child(
|
.child(
|
||||||
Button::new("button-icon-1")
|
Button::new("button-icon-1", cx)
|
||||||
.label("Confirm")
|
.label("Confirm")
|
||||||
.icon(IconName::Check)
|
.icon(IconName::Check)
|
||||||
.style(ButtonStyle::Primary)
|
.style(ButtonStyle::Primary)
|
||||||
.on_click(Self::on_click),
|
.on_click(Self::on_click),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("button-icon-2")
|
Button::new("button-icon-2", cx)
|
||||||
.label("Abort")
|
.label("Abort")
|
||||||
.icon(IconName::Close)
|
.icon(IconName::Close)
|
||||||
.style(ButtonStyle::Secondary)
|
.style(ButtonStyle::Secondary)
|
||||||
.on_click(Self::on_click),
|
.on_click(Self::on_click),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("button-icon-3")
|
Button::new("button-icon-3", cx)
|
||||||
.label("Maximize")
|
.label("Maximize")
|
||||||
.icon(Icon::new(IconName::Maximize))
|
.icon(Icon::new(IconName::Maximize))
|
||||||
.style(ButtonStyle::Secondary)
|
.style(ButtonStyle::Secondary)
|
||||||
|
|
@ -83,21 +83,21 @@ impl Render for ButtonStory {
|
||||||
.child(
|
.child(
|
||||||
section("Small Size", cx)
|
section("Small Size", cx)
|
||||||
.child(
|
.child(
|
||||||
Button::new("button-6")
|
Button::new("button-6", cx)
|
||||||
.label("Primary Button")
|
.label("Primary Button")
|
||||||
.style(ButtonStyle::Primary)
|
.style(ButtonStyle::Primary)
|
||||||
.size(ButtonSize::Small)
|
.size(ButtonSize::Small)
|
||||||
.on_click(Self::on_click),
|
.on_click(Self::on_click),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("button-7")
|
Button::new("button-7", cx)
|
||||||
.label("Secondary Button")
|
.label("Secondary Button")
|
||||||
.style(ButtonStyle::Secondary)
|
.style(ButtonStyle::Secondary)
|
||||||
.size(ButtonSize::Small)
|
.size(ButtonSize::Small)
|
||||||
.on_click(Self::on_click),
|
.on_click(Self::on_click),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("button-8")
|
Button::new("button-8", cx)
|
||||||
.label("Danger Button")
|
.label("Danger Button")
|
||||||
.style(ButtonStyle::Danger)
|
.style(ButtonStyle::Danger)
|
||||||
.size(ButtonSize::Small)
|
.size(ButtonSize::Small)
|
||||||
|
|
@ -107,21 +107,21 @@ impl Render for ButtonStory {
|
||||||
.child(
|
.child(
|
||||||
section("XSmall Size", cx)
|
section("XSmall Size", cx)
|
||||||
.child(
|
.child(
|
||||||
Button::new("button-xs-1")
|
Button::new("button-xs-1", cx)
|
||||||
.label("Primary Button")
|
.label("Primary Button")
|
||||||
.style(ButtonStyle::Primary)
|
.style(ButtonStyle::Primary)
|
||||||
.size(ButtonSize::XSmall)
|
.size(ButtonSize::XSmall)
|
||||||
.on_click(Self::on_click),
|
.on_click(Self::on_click),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("button-xs-2")
|
Button::new("button-xs-2", cx)
|
||||||
.label("Secondary Button")
|
.label("Secondary Button")
|
||||||
.style(ButtonStyle::Secondary)
|
.style(ButtonStyle::Secondary)
|
||||||
.size(ButtonSize::XSmall)
|
.size(ButtonSize::XSmall)
|
||||||
.on_click(Self::on_click),
|
.on_click(Self::on_click),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("button-xs-3")
|
Button::new("button-xs-3", cx)
|
||||||
.label("Danger Button")
|
.label("Danger Button")
|
||||||
.style(ButtonStyle::Danger)
|
.style(ButtonStyle::Danger)
|
||||||
.size(ButtonSize::XSmall)
|
.size(ButtonSize::XSmall)
|
||||||
|
|
@ -135,21 +135,21 @@ impl Render for ButtonStory {
|
||||||
.child(
|
.child(
|
||||||
section("Disabled Button", cx)
|
section("Disabled Button", cx)
|
||||||
.child(
|
.child(
|
||||||
Button::new("button-disabled1")
|
Button::new("button-disabled1", cx)
|
||||||
.label("Disabled Button")
|
.label("Disabled Button")
|
||||||
.style(ButtonStyle::Primary)
|
.style(ButtonStyle::Primary)
|
||||||
.on_click(Self::on_click)
|
.on_click(Self::on_click)
|
||||||
.disabled(true),
|
.disabled(true),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("button-disabled1")
|
Button::new("button-disabled1", cx)
|
||||||
.label("Disabled Button")
|
.label("Disabled Button")
|
||||||
.style(ButtonStyle::Secondary)
|
.style(ButtonStyle::Secondary)
|
||||||
.on_click(Self::on_click)
|
.on_click(Self::on_click)
|
||||||
.disabled(true),
|
.disabled(true),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("button-disabled1")
|
Button::new("button-disabled1", cx)
|
||||||
.label("Disabled Button")
|
.label("Disabled Button")
|
||||||
.style(ButtonStyle::Danger)
|
.style(ButtonStyle::Danger)
|
||||||
.on_click(Self::on_click)
|
.on_click(Self::on_click)
|
||||||
|
|
@ -159,19 +159,19 @@ impl Render for ButtonStory {
|
||||||
.child(
|
.child(
|
||||||
section("Selected Style", cx)
|
section("Selected Style", cx)
|
||||||
.child(
|
.child(
|
||||||
Button::new("button-selected-1")
|
Button::new("button-selected-1", cx)
|
||||||
.label("Selected Button")
|
.label("Selected Button")
|
||||||
.style(ButtonStyle::Primary)
|
.style(ButtonStyle::Primary)
|
||||||
.selected(true),
|
.selected(true),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("button-selected-2")
|
Button::new("button-selected-2", cx)
|
||||||
.label("Selected Button")
|
.label("Selected Button")
|
||||||
.style(ButtonStyle::Secondary)
|
.style(ButtonStyle::Secondary)
|
||||||
.selected(true),
|
.selected(true),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("button-selected-3")
|
Button::new("button-selected-3", cx)
|
||||||
.label("Selected Button")
|
.label("Selected Button")
|
||||||
.style(ButtonStyle::Danger)
|
.style(ButtonStyle::Danger)
|
||||||
.selected(true),
|
.selected(true),
|
||||||
|
|
@ -181,46 +181,46 @@ impl Render for ButtonStory {
|
||||||
.child(
|
.child(
|
||||||
section("Icon Button", cx)
|
section("Icon Button", cx)
|
||||||
.child(
|
.child(
|
||||||
Button::new("icon-button-0")
|
Button::new("icon-button-0", cx)
|
||||||
.icon(IconName::Search)
|
.icon(IconName::Search)
|
||||||
.style(ButtonStyle::Primary),
|
.style(ButtonStyle::Primary),
|
||||||
)
|
)
|
||||||
.child(Button::new("icon-button-1").icon(IconName::Info))
|
.child(Button::new("icon-button-1", cx).icon(IconName::Info))
|
||||||
.child(
|
.child(
|
||||||
Button::new("icon-button-2")
|
Button::new("icon-button-2", cx)
|
||||||
.icon(IconName::Close)
|
.icon(IconName::Close)
|
||||||
.style(ButtonStyle::Danger),
|
.style(ButtonStyle::Danger),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("icon-button-3")
|
Button::new("icon-button-3", cx)
|
||||||
.icon(IconName::Search)
|
.icon(IconName::Search)
|
||||||
.size(ButtonSize::Small)
|
.size(ButtonSize::Small)
|
||||||
.style(ButtonStyle::Primary),
|
.style(ButtonStyle::Primary),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("icon-button-4")
|
Button::new("icon-button-4", cx)
|
||||||
.icon(IconName::Info)
|
.icon(IconName::Info)
|
||||||
.size(ButtonSize::Small),
|
.size(ButtonSize::Small),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("icon-button-5")
|
Button::new("icon-button-5", cx)
|
||||||
.icon(IconName::Close)
|
.icon(IconName::Close)
|
||||||
.size(ButtonSize::Small)
|
.size(ButtonSize::Small)
|
||||||
.style(ButtonStyle::Danger),
|
.style(ButtonStyle::Danger),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("icon-button-6")
|
Button::new("icon-button-6", cx)
|
||||||
.icon(IconName::Search)
|
.icon(IconName::Search)
|
||||||
.size(ButtonSize::XSmall)
|
.size(ButtonSize::XSmall)
|
||||||
.style(ButtonStyle::Primary),
|
.style(ButtonStyle::Primary),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("icon-button-7")
|
Button::new("icon-button-7", cx)
|
||||||
.icon(IconName::Info)
|
.icon(IconName::Info)
|
||||||
.size(ButtonSize::XSmall),
|
.size(ButtonSize::XSmall),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("icon-button-8")
|
Button::new("icon-button-8", cx)
|
||||||
.icon(IconName::Close)
|
.icon(IconName::Close)
|
||||||
.size(ButtonSize::XSmall)
|
.size(ButtonSize::XSmall)
|
||||||
.style(ButtonStyle::Danger),
|
.style(ButtonStyle::Danger),
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,24 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
ClickEvent, IntoElement, ParentElement as _, Render, Styled as _, View, ViewContext,
|
actions, AppContext, ClickEvent, FocusHandle, FocusableView, InteractiveElement, IntoElement,
|
||||||
VisualContext, WindowContext,
|
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;
|
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 {
|
pub struct InputStory {
|
||||||
|
focus_handle: FocusHandle,
|
||||||
input1: View<TextInput>,
|
input1: View<TextInput>,
|
||||||
input2: View<TextInput>,
|
input2: View<TextInput>,
|
||||||
mash_input: View<TextInput>,
|
mash_input: View<TextInput>,
|
||||||
|
|
@ -54,6 +65,7 @@ impl InputStory {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
input1,
|
input1,
|
||||||
input2: cx.new_view(|cx| {
|
input2: cx.new_view(|cx| {
|
||||||
let mut input = TextInput::new(cx);
|
let mut input = TextInput::new(cx);
|
||||||
|
|
@ -77,11 +89,46 @@ impl InputStory {
|
||||||
fn on_change(ev: &ClickEvent, cx: &mut WindowContext) {
|
fn on_change(ev: &ClickEvent, cx: &mut WindowContext) {
|
||||||
println!("Input changed: {:?}", ev);
|
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 {
|
impl Render for InputStory {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
v_flex()
|
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()
|
.size_full()
|
||||||
.p_4()
|
.p_4()
|
||||||
.justify_start()
|
.justify_start()
|
||||||
|
|
@ -102,5 +149,23 @@ impl Render for InputStory {
|
||||||
.child(self.both_input1.clone())
|
.child(self.both_input1.clone())
|
||||||
.child(self.suffix_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;
|
pub use tooltip_story::TooltipStory;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, Div, Element,
|
div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, Div, EventEmitter,
|
||||||
EventEmitter, FocusableView, InteractiveElement, IntoElement, ParentElement, Pixels, Render,
|
FocusableView, InteractiveElement, IntoElement, ParentElement, Pixels, Render, ScrollHandle,
|
||||||
ScrollHandle, SharedString, StatefulInteractiveElement, Styled as _, Task, View, ViewContext,
|
SharedString, StatefulInteractiveElement, Styled as _, Task, View, ViewContext, VisualContext,
|
||||||
VisualContext, WindowContext,
|
WindowContext,
|
||||||
};
|
};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
dock::{DockPosition, Panel, PanelEvent},
|
dock::{DockPosition, Panel, PanelEvent},
|
||||||
|
|
@ -33,7 +33,11 @@ use workspace::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
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(
|
pub fn story_case(
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
|
|
@ -286,7 +290,6 @@ impl Panel for MyPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_size(&mut self, size: Option<gpui::Pixels>, cx: &mut ViewContext<Self>) {
|
fn set_size(&mut self, size: Option<gpui::Pixels>, cx: &mut ViewContext<Self>) {
|
||||||
dbg!("================ set_size", size);
|
|
||||||
if let Some(size) = size {
|
if let Some(size) = size {
|
||||||
self.update_size(size)
|
self.update_size(size)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ use core::time;
|
||||||
|
|
||||||
use fake::Fake;
|
use fake::Fake;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, prelude::FluentBuilder as _, px, ElementId, FocusHandle, FocusableView,
|
actions, div, px, ElementId, FocusHandle, FocusableView, InteractiveElement, IntoElement,
|
||||||
InteractiveElement, IntoElement, ParentElement, Render, RenderOnce, Styled, Timer, View,
|
ParentElement, Render, RenderOnce, Styled, Timer, View, ViewContext, VisualContext,
|
||||||
ViewContext, VisualContext, WindowContext,
|
WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ui::{
|
use ui::{
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@ impl Render for PickerStory {
|
||||||
.gap_6()
|
.gap_6()
|
||||||
.child(
|
.child(
|
||||||
v_flex().items_start().child(
|
v_flex().items_start().child(
|
||||||
Button::new("show-picker")
|
Button::new("show-picker", cx)
|
||||||
.label("Show Picker...")
|
.label("Show Picker...")
|
||||||
.icon(IconName::Search)
|
.icon(IconName::Search)
|
||||||
.style(ButtonStyle::Primary)
|
.style(ButtonStyle::Primary)
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ impl Render for Form {
|
||||||
.child("This is a form container.")
|
.child("This is a form container.")
|
||||||
.child(self.input1.clone())
|
.child(self.input1.clone())
|
||||||
.child(
|
.child(
|
||||||
Button::primary("submit", "Submit")
|
Button::primary("submit", "Submit", cx)
|
||||||
.on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
|
.on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +62,7 @@ impl PopoverStory {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for 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();
|
let form = self.form.clone();
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
|
|
@ -76,15 +76,15 @@ impl Render for PopoverStory {
|
||||||
.child(
|
.child(
|
||||||
v_flex().gap_4().child(
|
v_flex().gap_4().child(
|
||||||
Popover::new("info-top-left")
|
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| {
|
.content(|cx| {
|
||||||
PopoverContent::new(cx, |_| {
|
PopoverContent::new(cx, |cx| {
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_4()
|
.gap_4()
|
||||||
.child("Hello, this is a Popover.")
|
.child("Hello, this is a Popover.")
|
||||||
.child(Divider::horizontal())
|
.child(Divider::horizontal())
|
||||||
.child(
|
.child(
|
||||||
Button::new("info1")
|
Button::new("info1", cx)
|
||||||
.label("Yes")
|
.label("Yes")
|
||||||
.width(px(80.))
|
.width(px(80.))
|
||||||
.size(ButtonSize::Small),
|
.size(ButtonSize::Small),
|
||||||
|
|
@ -97,15 +97,15 @@ impl Render for PopoverStory {
|
||||||
.child(
|
.child(
|
||||||
Popover::new("info-top-right")
|
Popover::new("info-top-right")
|
||||||
.anchor(AnchorCorner::TopRight)
|
.anchor(AnchorCorner::TopRight)
|
||||||
.trigger(Button::new("info-top-right").label("Top Right"))
|
.trigger(Button::new("info-top-right", cx).label("Top Right"))
|
||||||
.content(|cx| {
|
.content(|cx| {
|
||||||
PopoverContent::new(cx, |_| {
|
PopoverContent::new(cx, |cx| {
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_4()
|
.gap_4()
|
||||||
.child("Hello, this is a Popover on the Top Right.")
|
.child("Hello, this is a Popover on the Top Right.")
|
||||||
.child(Divider::horizontal())
|
.child(Divider::horizontal())
|
||||||
.child(
|
.child(
|
||||||
Button::new("info1")
|
Button::new("info1", cx)
|
||||||
.label("Yes")
|
.label("Yes")
|
||||||
.width(px(80.))
|
.width(px(80.))
|
||||||
.size(ButtonSize::Small),
|
.size(ButtonSize::Small),
|
||||||
|
|
@ -124,7 +124,9 @@ impl Render for PopoverStory {
|
||||||
Popover::new("info-bottom-left")
|
Popover::new("info-bottom-left")
|
||||||
.anchor(AnchorCorner::BottomLeft)
|
.anchor(AnchorCorner::BottomLeft)
|
||||||
.trigger(
|
.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()),
|
.content(move |_| form.clone()),
|
||||||
)
|
)
|
||||||
|
|
@ -133,18 +135,18 @@ impl Render for PopoverStory {
|
||||||
.anchor(AnchorCorner::BottomRight)
|
.anchor(AnchorCorner::BottomRight)
|
||||||
.mouse_button(MouseButton::Right)
|
.mouse_button(MouseButton::Right)
|
||||||
.trigger(
|
.trigger(
|
||||||
Button::new("pop")
|
Button::new("pop", cx)
|
||||||
.label("Mouse Right Click")
|
.label("Mouse Right Click")
|
||||||
.width(px(300.)),
|
.width(px(300.)),
|
||||||
)
|
)
|
||||||
.content(|cx| {
|
.content(|cx| {
|
||||||
PopoverContent::new(cx, |_| {
|
PopoverContent::new(cx, |cx| {
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_4()
|
.gap_4()
|
||||||
.child("Hello, this is a Popover on the Bottom Right.")
|
.child("Hello, this is a Popover on the Bottom Right.")
|
||||||
.child(Divider::horizontal())
|
.child(Divider::horizontal())
|
||||||
.child(
|
.child(
|
||||||
Button::new("info1")
|
Button::new("info1", cx)
|
||||||
.label("Yes")
|
.label("Yes")
|
||||||
.width(px(80.))
|
.width(px(80.))
|
||||||
.size(ButtonSize::Small),
|
.size(ButtonSize::Small),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, px, CursorStyle, InteractiveElement, ParentElement, Render, StatefulInteractiveElement,
|
div, CursorStyle, InteractiveElement, ParentElement, Render, StatefulInteractiveElement,
|
||||||
Styled, View, VisualContext as _, WindowContext,
|
Styled, View, VisualContext as _, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -25,7 +25,7 @@ impl TooltipStory {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for 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()
|
v_flex()
|
||||||
.p_4()
|
.p_4()
|
||||||
.gap_5()
|
.gap_5()
|
||||||
|
|
@ -33,7 +33,7 @@ impl Render for TooltipStory {
|
||||||
div()
|
div()
|
||||||
.cursor(CursorStyle::PointingHand)
|
.cursor(CursorStyle::PointingHand)
|
||||||
.child(
|
.child(
|
||||||
Button::new("button")
|
Button::new("button", cx)
|
||||||
.label("Hover me")
|
.label("Hover me")
|
||||||
.style(ButtonStyle::Primary),
|
.style(ButtonStyle::Primary),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
h_flex,
|
h_flex,
|
||||||
theme::{ActiveTheme, Colorize as _, ThemeMode},
|
theme::{ActiveTheme, Colorize as _, ThemeMode},
|
||||||
Clickable, Disableable, Icon, Selectable,
|
Clickable, Disableable, Icon, Selectable, StyledExt,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::FluentBuilder as _, px, ClickEvent, DefiniteLength, Div, ElementId, Hsla,
|
div, prelude::FluentBuilder as _, px, ClickEvent, DefiniteLength, Div, ElementId, FocusHandle,
|
||||||
InteractiveElement, IntoElement, MouseButton, ParentElement, RenderOnce, SharedString,
|
Focusable, Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement, Render,
|
||||||
StatefulInteractiveElement as _, Styled, WindowContext,
|
RenderOnce, SharedString, StatefulInteractiveElement as _, Styled, View, ViewContext,
|
||||||
|
VisualContext, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub enum ButtonRounded {
|
pub enum ButtonRounded {
|
||||||
|
|
@ -34,6 +35,7 @@ pub enum ButtonStyle {
|
||||||
pub struct Button {
|
pub struct Button {
|
||||||
pub base: Div,
|
pub base: Div,
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
|
focus_handle: FocusHandle,
|
||||||
icon: Option<Icon>,
|
icon: Option<Icon>,
|
||||||
label: Option<SharedString>,
|
label: Option<SharedString>,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
|
|
@ -48,9 +50,10 @@ pub struct Button {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Button {
|
impl Button {
|
||||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
pub fn new(id: impl Into<ElementId>, cx: &mut WindowContext) -> Self {
|
||||||
Self {
|
Self {
|
||||||
base: div(),
|
base: div(),
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
icon: None,
|
icon: None,
|
||||||
label: None,
|
label: None,
|
||||||
|
|
@ -66,16 +69,28 @@ impl Button {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn primary(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
|
pub fn primary(
|
||||||
Self::new(id).label(label).style(ButtonStyle::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 {
|
pub fn danger(
|
||||||
Self::new(id).label(label).style(ButtonStyle::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 {
|
pub fn small(
|
||||||
Self::new(id).label(label).size(ButtonSize::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 {
|
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 {
|
impl RenderOnce for Button {
|
||||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let style: ButtonStyle = self.style;
|
let style: ButtonStyle = self.style;
|
||||||
let normal_style = style.normal(cx);
|
let normal_style = style.normal(cx);
|
||||||
|
let focused = self.focus_handle.is_focused(cx);
|
||||||
|
|
||||||
self.base
|
self.base
|
||||||
.id(self.id)
|
.id(self.id)
|
||||||
|
.track_focus(&self.focus_handle)
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
|
|
@ -192,6 +215,9 @@ impl RenderOnce for Button {
|
||||||
.border_color(normal_style.border)
|
.border_color(normal_style.border)
|
||||||
.bg(normal_style.bg)
|
.bg(normal_style.bg)
|
||||||
})
|
})
|
||||||
|
.when(focused, |this| {
|
||||||
|
this.border_color(cx.theme().ring).debug_blue()
|
||||||
|
})
|
||||||
.when_some(
|
.when_some(
|
||||||
self.on_click.filter(|_| !self.disabled),
|
self.on_click.filter(|_| !self.disabled),
|
||||||
|this, on_click| {
|
|this, on_click| {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ use crate::{
|
||||||
list::ListItem,
|
list::ListItem,
|
||||||
picker::{self, Picker, PickerDelegate},
|
picker::{self, Picker, PickerDelegate},
|
||||||
theme::ActiveTheme,
|
theme::ActiveTheme,
|
||||||
Icon, IconName,
|
Icon, IconName, StyledExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A trait for items that can be displayed in a dropdown.
|
/// A trait for items that can be displayed in a dropdown.
|
||||||
|
|
@ -221,13 +221,12 @@ where
|
||||||
D: DropdownDelegate + 'static,
|
D: DropdownDelegate + 'static,
|
||||||
{
|
{
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
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 title = self.title.clone().unwrap_or_else(|| "Select...".into());
|
||||||
let focused = self.focus_handle.is_focused(cx);
|
let focused = self.focus_handle.is_focused(cx);
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.key_context("Dropdown")
|
.key_context("Dropdown")
|
||||||
.group(group_id.clone())
|
.group(format!("dropdown-group:{}", self.id))
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
.on_action(cx.listener(Self::up))
|
.on_action(cx.listener(Self::up))
|
||||||
.on_action(cx.listener(Self::down))
|
.on_action(cx.listener(Self::down))
|
||||||
|
|
@ -248,9 +247,9 @@ where
|
||||||
.border_color(cx.theme().input)
|
.border_color(cx.theme().input)
|
||||||
.rounded(px(cx.theme().radius))
|
.rounded(px(cx.theme().radius))
|
||||||
.shadow_sm()
|
.shadow_sm()
|
||||||
|
.when(focused, |this| this.outline(cx))
|
||||||
.px_3()
|
.px_3()
|
||||||
.py_2()
|
.py_2()
|
||||||
.when(focused, |this| this.border_color(cx.theme().ring))
|
|
||||||
.on_click(cx.listener(|this, _, cx| {
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
this.open = !this.open;
|
this.open = !this.open;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
@ -280,20 +279,12 @@ struct DropdownMenuElement<D: DropdownDelegate + 'static> {
|
||||||
dropdown: View<Dropdown<D>>,
|
dropdown: View<Dropdown<D>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
struct DropdownMenuElementState {
|
struct DropdownMenuElementState {
|
||||||
menu_element: Option<AnyElement>,
|
menu_element: Option<AnyElement>,
|
||||||
layout_id: Option<LayoutId>,
|
layout_id: Option<LayoutId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DropdownMenuElementState {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
menu_element: None,
|
|
||||||
layout_id: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D> IntoElement for DropdownMenuElement<D>
|
impl<D> IntoElement for DropdownMenuElement<D>
|
||||||
where
|
where
|
||||||
D: DropdownDelegate + 'static,
|
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::event::InterativeElementExt as _;
|
||||||
use crate::theme::ActiveTheme;
|
use crate::theme::ActiveTheme;
|
||||||
|
use crate::StyledExt as _;
|
||||||
use blink_cursor::BlinkCursor;
|
use blink_cursor::BlinkCursor;
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
use prelude::FluentBuilder as _;
|
use prelude::FluentBuilder as _;
|
||||||
|
|
@ -679,14 +680,11 @@ impl Render for TextInput {
|
||||||
.h_10()
|
.h_10()
|
||||||
.when(self.appearance, |this| {
|
.when(self.appearance, |this| {
|
||||||
this.bg(cx.theme().input)
|
this.bg(cx.theme().input)
|
||||||
.border_color(if focused {
|
.border_color(cx.theme().input)
|
||||||
cx.theme().ring
|
|
||||||
} else {
|
|
||||||
cx.theme().input
|
|
||||||
})
|
|
||||||
.border_1()
|
.border_1()
|
||||||
.rounded(px(cx.theme().radius))
|
.rounded(px(cx.theme().radius))
|
||||||
.shadow_sm()
|
.shadow_sm()
|
||||||
|
.when(focused, |this| this.outline(cx))
|
||||||
.px_3()
|
.px_3()
|
||||||
.bg(if self.disabled {
|
.bg(if self.disabled {
|
||||||
cx.theme().muted
|
cx.theme().muted
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
mod clickable;
|
mod clickable;
|
||||||
mod disableable;
|
mod disableable;
|
||||||
mod event;
|
mod event;
|
||||||
|
mod focusable;
|
||||||
mod icon;
|
mod icon;
|
||||||
mod scrollbar;
|
mod scrollbar;
|
||||||
mod selectable;
|
mod selectable;
|
||||||
|
|
@ -28,6 +29,7 @@ pub mod tooltip;
|
||||||
pub use clickable::Clickable;
|
pub use clickable::Clickable;
|
||||||
pub use disableable::Disableable;
|
pub use disableable::Disableable;
|
||||||
pub use event::InterativeElementExt;
|
pub use event::InterativeElementExt;
|
||||||
|
pub use focusable::FocusableCycle;
|
||||||
pub use selectable::{Selectable, Selection};
|
pub use selectable::{Selectable, Selection};
|
||||||
|
|
||||||
pub use icon::*;
|
pub use icon::*;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::theme::{hsl, ActiveTheme};
|
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};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
pub enum ElevationIndex {
|
pub enum ElevationIndex {
|
||||||
|
|
@ -96,6 +96,18 @@ pub trait StyledExt: Styled + Sized {
|
||||||
fn debug_pink(self) -> Self {
|
fn debug_pink(self) -> Self {
|
||||||
self.border_1().border_color(hsl(300., 100., 47.))
|
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 {}
|
impl<E: Styled> StyledExt for E {}
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ impl Colors {
|
||||||
accent_foreground: hsl(240.0, 5.9, 10.0),
|
accent_foreground: hsl(240.0, 5.9, 10.0),
|
||||||
border: hsl(240.0, 5.9, 90.0),
|
border: hsl(240.0, 5.9, 90.0),
|
||||||
input: 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),
|
selection: hsl(211.0, 97.0, 85.0),
|
||||||
scrollbar: Hsla::transparent_black(),
|
scrollbar: Hsla::transparent_black(),
|
||||||
scrollbar_thumb: hsl(240.0, 5.9, 90.0).opacity(0.7),
|
scrollbar_thumb: hsl(240.0, 5.9, 90.0).opacity(0.7),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue