modal: Improve Modal and Panel focus handle. (#238)
- modal: Improve modal focus take, when modal close, make sure focus back. - modal: Fix hover close button will cross to back modal on multiple modal layer. - panel: Add focus handle to Panel and TabPanel. - panel: Update title method to return `AnyElement`.
This commit is contained in:
parent
9388b88f59
commit
ce87c3a001
23 changed files with 436 additions and 246 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
px, ClickEvent, IntoElement, ParentElement as _, Render, Styled as _, View, ViewContext,
|
px, ClickEvent, FocusableView, IntoElement, ParentElement as _, Render, Styled as _, View,
|
||||||
VisualContext as _, WindowContext,
|
ViewContext, VisualContext as _, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ui::{
|
use ui::{
|
||||||
|
|
@ -16,6 +16,7 @@ use ui::{
|
||||||
use crate::section;
|
use crate::section;
|
||||||
|
|
||||||
pub struct ButtonStory {
|
pub struct ButtonStory {
|
||||||
|
focus_handle: gpui::FocusHandle,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
loading: bool,
|
loading: bool,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
|
|
@ -24,7 +25,8 @@ pub struct ButtonStory {
|
||||||
|
|
||||||
impl ButtonStory {
|
impl ButtonStory {
|
||||||
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
||||||
cx.new_view(|_| Self {
|
cx.new_view(|cx| Self {
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
disabled: false,
|
disabled: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
selected: false,
|
selected: false,
|
||||||
|
|
@ -50,8 +52,14 @@ impl super::Story for ButtonStory {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
|
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
||||||
Self::view(cx).into()
|
Self::view(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FocusableView for ButtonStory {
|
||||||
|
fn focus_handle(&self, _: &gpui::AppContext) -> gpui::FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ impl super::Story for CalendarStory {
|
||||||
"A date picker and calendar component."
|
"A date picker and calendar component."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
|
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
||||||
Self::view(cx).into()
|
Self::view(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,6 +105,12 @@ impl CalendarStory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl gpui::FocusableView for CalendarStory {
|
||||||
|
fn focus_handle(&self, cx: &gpui::AppContext) -> gpui::FocusHandle {
|
||||||
|
self.date_picker.focus_handle(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Render for CalendarStory {
|
impl Render for CalendarStory {
|
||||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
v_flex()
|
v_flex()
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,14 @@ impl super::Story for DropdownStory {
|
||||||
"Displays a list of options for the user to pick from—triggered by a button."
|
"Displays a list of options for the user to pick from—triggered by a button."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
|
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
||||||
Self::view(cx).into()
|
Self::view(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl gpui::FocusableView for DropdownStory {
|
||||||
|
fn focus_handle(&self, cx: &gpui::AppContext) -> gpui::FocusHandle {
|
||||||
|
self.fruit_dropdown.focus_handle(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
use gpui::{px, rems, ParentElement, Render, Styled, View, VisualContext as _, WindowContext};
|
use gpui::{
|
||||||
|
px, rems, ParentElement, Render, Styled, View, ViewContext, VisualContext as _, WindowContext,
|
||||||
|
};
|
||||||
use ui::{
|
use ui::{
|
||||||
button::{Button, ButtonStyle},
|
button::{Button, ButtonStyle},
|
||||||
h_flex,
|
h_flex,
|
||||||
|
|
@ -6,15 +8,19 @@ use ui::{
|
||||||
v_flex, Icon, IconName,
|
v_flex, Icon, IconName,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct IconStory {}
|
pub struct IconStory {
|
||||||
|
focus_handle: gpui::FocusHandle,
|
||||||
|
}
|
||||||
|
|
||||||
impl IconStory {
|
impl IconStory {
|
||||||
pub fn new(_: &WindowContext) -> Self {
|
fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||||
Self {}
|
Self {
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
||||||
cx.new_view(|cx| Self::new(cx))
|
cx.new_view(Self::new)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,8 +33,14 @@ impl super::Story for IconStory {
|
||||||
"Icon use examples"
|
"Icon use examples"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
|
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
||||||
Self::view(cx).into()
|
Self::view(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl gpui::FocusableView for IconStory {
|
||||||
|
fn focus_handle(&self, _: &gpui::AppContext) -> gpui::FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ const GOOGLE_LOGO: &str = include_str!("./fixtures/google.svg");
|
||||||
const PIE_JSON: &str = include_str!("./fixtures/pie.json");
|
const PIE_JSON: &str = include_str!("./fixtures/pie.json");
|
||||||
|
|
||||||
pub struct ImageStory {
|
pub struct ImageStory {
|
||||||
|
focus_handle: gpui::FocusHandle,
|
||||||
google_logo: SvgImg,
|
google_logo: SvgImg,
|
||||||
pie_chart: SvgImg,
|
pie_chart: SvgImg,
|
||||||
inbox_img: SvgImg,
|
inbox_img: SvgImg,
|
||||||
|
|
@ -15,16 +16,17 @@ impl super::Story for ImageStory {
|
||||||
"Image"
|
"Image"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
|
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
||||||
Self::view(cx).into()
|
Self::view(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImageStory {
|
impl ImageStory {
|
||||||
pub fn new(_: &WindowContext) -> Self {
|
pub fn new(cx: &mut WindowContext) -> Self {
|
||||||
let chart = charts_rs::PieChart::from_json(PIE_JSON).unwrap();
|
let chart = charts_rs::PieChart::from_json(PIE_JSON).unwrap();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
google_logo: svg_img().source(GOOGLE_LOGO.as_bytes(), px(300.), px(300.)),
|
google_logo: svg_img().source(GOOGLE_LOGO.as_bytes(), px(300.), px(300.)),
|
||||||
pie_chart: svg_img().source(chart.svg().unwrap().as_bytes(), px(400.), px(400.)),
|
pie_chart: svg_img().source(chart.svg().unwrap().as_bytes(), px(400.), px(400.)),
|
||||||
inbox_img: svg_img().source("icons/inbox.svg", px(300.), px(300.)),
|
inbox_img: svg_img().source("icons/inbox.svg", px(300.), px(300.)),
|
||||||
|
|
@ -36,6 +38,12 @@ impl ImageStory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl gpui::FocusableView for ImageStory {
|
||||||
|
fn focus_handle(&self, _: &gpui::AppContext) -> gpui::FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Render for ImageStory {
|
impl Render for ImageStory {
|
||||||
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()
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,8 @@ impl super::Story for InputStory {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
|
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
||||||
Self::view(cx).into()
|
Self::view(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -220,6 +220,11 @@ impl FocusableCycle for InputStory {
|
||||||
.to_vec()
|
.to_vec()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl gpui::FocusableView for InputStory {
|
||||||
|
fn focus_handle(&self, cx: &gpui::AppContext) -> gpui::FocusHandle {
|
||||||
|
self.input1.focus_handle(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,10 @@ pub use tooltip_story::TooltipStory;
|
||||||
pub use webview_story::WebViewStory;
|
pub use webview_story::WebViewStory;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, prelude::FluentBuilder as _, px, AnyView, AppContext, Div, EventEmitter,
|
actions, div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, Div,
|
||||||
FocusableView, Hsla, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
|
EventEmitter, FocusableView, Hsla, InteractiveElement, IntoElement, ParentElement, Render,
|
||||||
StatefulInteractiveElement, Styled as _, View, ViewContext, VisualContext, WindowContext,
|
SharedString, StatefulInteractiveElement, Styled as _, View, ViewContext, VisualContext,
|
||||||
|
WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ui::{
|
use ui::{
|
||||||
|
|
@ -112,7 +113,7 @@ pub enum ContainerEvent {
|
||||||
Close,
|
Close,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Story {
|
pub trait Story: FocusableView {
|
||||||
fn klass() -> &'static str {
|
fn klass() -> &'static str {
|
||||||
std::any::type_name::<Self>().split("::").last().unwrap()
|
std::any::type_name::<Self>().split("::").last().unwrap()
|
||||||
}
|
}
|
||||||
|
|
@ -127,7 +128,7 @@ pub trait Story {
|
||||||
fn title_bg() -> Option<Hsla> {
|
fn title_bg() -> Option<Hsla> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
fn new_view(cx: &mut WindowContext) -> AnyView;
|
fn new_view(cx: &mut WindowContext) -> View<impl FocusableView>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<ContainerEvent> for StoryContainer {}
|
impl EventEmitter<ContainerEvent> for StoryContainer {}
|
||||||
|
|
@ -155,9 +156,11 @@ impl StoryContainer {
|
||||||
let description = S::description();
|
let description = S::description();
|
||||||
let story = S::new_view(cx);
|
let story = S::new_view(cx);
|
||||||
let story_klass = S::klass();
|
let story_klass = S::klass();
|
||||||
|
let focus_handle = story.focus_handle(cx);
|
||||||
|
|
||||||
let view = cx.new_view(|cx| {
|
let view = cx.new_view(|cx| {
|
||||||
let mut story = Self::new(cx).story(story, story_klass);
|
let mut story = Self::new(cx).story(story.into(), story_klass);
|
||||||
|
story.focus_handle = focus_handle;
|
||||||
story.closeable = S::closeable();
|
story.closeable = S::closeable();
|
||||||
story.name = name.into();
|
story.name = name.into();
|
||||||
story.description = description.into();
|
story.description = description.into();
|
||||||
|
|
@ -249,8 +252,8 @@ impl Panel for StoryContainer {
|
||||||
"StoryContainer"
|
"StoryContainer"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title(&self, _cx: &WindowContext) -> SharedString {
|
fn title(&self, _cx: &WindowContext) -> AnyElement {
|
||||||
self.name.clone()
|
self.name.clone().into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title_style(&self, cx: &WindowContext) -> Option<TitleStyle> {
|
fn title_style(&self, cx: &WindowContext) -> Option<TitleStyle> {
|
||||||
|
|
|
||||||
|
|
@ -205,8 +205,8 @@ impl super::Story for ListStory {
|
||||||
"A list displays a series of items."
|
"A list displays a series of items."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
|
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
||||||
Self::view(cx).into()
|
Self::view(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
use fake::Fake;
|
use fake::Fake;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::FluentBuilder as _, px, FocusHandle, FocusableView, IntoElement, ParentElement,
|
actions, div, prelude::FluentBuilder as _, px, FocusHandle, FocusableView,
|
||||||
Render, SharedString, Styled, Task, Timer, View, ViewContext, VisualContext as _, WeakView,
|
InteractiveElement as _, IntoElement, ParentElement, Render, SharedString, Styled, Task, Timer,
|
||||||
WindowContext,
|
View, ViewContext, VisualContext as _, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ui::{
|
use ui::{
|
||||||
|
|
@ -19,6 +19,8 @@ use ui::{
|
||||||
v_flex, ContextModal as _, Icon, IconName, Placement,
|
v_flex, ContextModal as _, Icon, IconName, Placement,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
actions!(modal_story, [TestAction]);
|
||||||
|
|
||||||
pub struct ListItemDeletegate {
|
pub struct ListItemDeletegate {
|
||||||
story: WeakView<ModalStory>,
|
story: WeakView<ModalStory>,
|
||||||
confirmed_index: Option<usize>,
|
confirmed_index: Option<usize>,
|
||||||
|
|
@ -145,6 +147,7 @@ pub struct ModalStory {
|
||||||
selected_value: Option<SharedString>,
|
selected_value: Option<SharedString>,
|
||||||
list: View<List<ListItemDeletegate>>,
|
list: View<List<ListItemDeletegate>>,
|
||||||
input1: View<TextInput>,
|
input1: View<TextInput>,
|
||||||
|
input2: View<TextInput>,
|
||||||
date_picker: View<DatePicker>,
|
date_picker: View<DatePicker>,
|
||||||
modal_overlay: bool,
|
modal_overlay: bool,
|
||||||
model_show_close: bool,
|
model_show_close: bool,
|
||||||
|
|
@ -160,8 +163,8 @@ impl super::Story for ModalStory {
|
||||||
"Modal & Drawer use examples"
|
"Modal & Drawer use examples"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
|
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
||||||
Self::view(cx).into()
|
Self::view(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,6 +244,7 @@ impl ModalStory {
|
||||||
});
|
});
|
||||||
|
|
||||||
let input1 = cx.new_view(|cx| TextInput::new(cx).placeholder("Your Name"));
|
let input1 = cx.new_view(|cx| TextInput::new(cx).placeholder("Your Name"));
|
||||||
|
let input2 = cx.new_view(|cx| TextInput::new(cx).placeholder("Input on the Window"));
|
||||||
let date_picker =
|
let date_picker =
|
||||||
cx.new_view(|cx| DatePicker::new("birthday-picker", cx).placeholder("Date of Birth"));
|
cx.new_view(|cx| DatePicker::new("birthday-picker", cx).placeholder("Date of Birth"));
|
||||||
|
|
||||||
|
|
@ -250,6 +254,7 @@ impl ModalStory {
|
||||||
selected_value: None,
|
selected_value: None,
|
||||||
list,
|
list,
|
||||||
input1,
|
input1,
|
||||||
|
input2,
|
||||||
date_picker,
|
date_picker,
|
||||||
modal_overlay: true,
|
modal_overlay: true,
|
||||||
model_show_close: true,
|
model_show_close: true,
|
||||||
|
|
@ -268,7 +273,6 @@ impl ModalStory {
|
||||||
};
|
};
|
||||||
|
|
||||||
let overlay = self.modal_overlay;
|
let overlay = self.modal_overlay;
|
||||||
input.focus_handle(cx).focus(cx);
|
|
||||||
cx.open_drawer(move |this, cx| {
|
cx.open_drawer(move |this, cx| {
|
||||||
this.margin_top(px(33.))
|
this.margin_top(px(33.))
|
||||||
.placement(placement)
|
.placement(placement)
|
||||||
|
|
@ -320,9 +324,8 @@ impl ModalStory {
|
||||||
let date_picker = self.date_picker.clone();
|
let date_picker = self.date_picker.clone();
|
||||||
let view = cx.view().clone();
|
let view = cx.view().clone();
|
||||||
|
|
||||||
input1.focus_handle(cx).focus(cx);
|
|
||||||
|
|
||||||
cx.open_modal(move |modal, cx| {
|
cx.open_modal(move |modal, cx| {
|
||||||
|
input1.focus_handle(cx).focus(cx);
|
||||||
modal
|
modal
|
||||||
.title("Form Modal")
|
.title("Form Modal")
|
||||||
.overlay(overlay)
|
.overlay(overlay)
|
||||||
|
|
@ -386,6 +389,10 @@ impl ModalStory {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_action_test_action(&mut self, _: &TestAction, cx: &mut ViewContext<Self>) {
|
||||||
|
cx.push_notification("You have clicked the TestAction.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FocusableView for ModalStory {
|
impl FocusableView for ModalStory {
|
||||||
|
|
@ -396,7 +403,12 @@ impl FocusableView for ModalStory {
|
||||||
|
|
||||||
impl Render for ModalStory {
|
impl Render for ModalStory {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
div().size_full().child(
|
div()
|
||||||
|
.id("modal-story")
|
||||||
|
.track_focus(&self.focus_handle)
|
||||||
|
.on_action(cx.listener(Self::on_action_test_action))
|
||||||
|
.size_full()
|
||||||
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_6()
|
.gap_6()
|
||||||
.child(
|
.child(
|
||||||
|
|
@ -431,6 +443,19 @@ impl Render for ModalStory {
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.child("Test Focus Back")
|
||||||
|
.child(self.input2.clone())
|
||||||
|
.child(
|
||||||
|
Button::new("test-action", cx)
|
||||||
|
.label("Test Dispatch Action")
|
||||||
|
.on_click(|_, cx| {
|
||||||
|
cx.dispatch_action(Box::new(TestAction));
|
||||||
|
}).tooltip("This button for test dispatch action, to make sure when Modal close,\nthis still can handle the action."),
|
||||||
|
),
|
||||||
|
)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.items_start()
|
.items_start()
|
||||||
|
|
@ -485,7 +510,9 @@ impl Render for ModalStory {
|
||||||
Button::new("show-notify-info", cx)
|
Button::new("show-notify-info", cx)
|
||||||
.label("Info Notify...")
|
.label("Info Notify...")
|
||||||
.on_click(cx.listener(|_, _, cx| {
|
.on_click(cx.listener(|_, _, cx| {
|
||||||
cx.push_notification("You have been saved file successfully.")
|
cx.push_notification(
|
||||||
|
"You have been saved file successfully.",
|
||||||
|
)
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
|
|
|
||||||
|
|
@ -88,8 +88,8 @@ impl super::Story for PopupStory {
|
||||||
"A popup displays content on top of the main page."
|
"A popup displays content on top of the main page."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
|
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
||||||
Self::view(cx).into()
|
Self::view(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ use ui::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ProgressStory {
|
pub struct ProgressStory {
|
||||||
|
focus_handle: gpui::FocusHandle,
|
||||||
value: f32,
|
value: f32,
|
||||||
slider1: View<Slider>,
|
slider1: View<Slider>,
|
||||||
slider1_value: f32,
|
slider1_value: f32,
|
||||||
|
|
@ -26,8 +27,8 @@ impl super::Story for ProgressStory {
|
||||||
"Progress"
|
"Progress"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
|
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
||||||
Self::view(cx).into()
|
Self::view(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,6 +63,7 @@ impl ProgressStory {
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
value: 50.,
|
value: 50.,
|
||||||
slider1_value: 15.,
|
slider1_value: 15.,
|
||||||
slider2_value: 1.,
|
slider2_value: 1.,
|
||||||
|
|
@ -75,6 +77,12 @@ impl ProgressStory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl gpui::FocusableView for ProgressStory {
|
||||||
|
fn focus_handle(&self, _: &gpui::AppContext) -> gpui::FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Render for ProgressStory {
|
impl Render for ProgressStory {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
v_flex()
|
v_flex()
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use ui::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ResizableStory {
|
pub struct ResizableStory {
|
||||||
|
focus_handle: gpui::FocusHandle,
|
||||||
group1: View<ResizablePanelGroup>,
|
group1: View<ResizablePanelGroup>,
|
||||||
group2: View<ResizablePanelGroup>,
|
group2: View<ResizablePanelGroup>,
|
||||||
}
|
}
|
||||||
|
|
@ -22,8 +23,14 @@ impl super::Story for ResizableStory {
|
||||||
"The resizable panels."
|
"The resizable panels."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
|
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
||||||
Self::view(cx).into()
|
Self::view(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl gpui::FocusableView for ResizableStory {
|
||||||
|
fn focus_handle(&self, _: &gpui::AppContext) -> gpui::FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,7 +104,11 @@ impl ResizableStory {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
Self { group1, group2 }
|
Self {
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
|
group1,
|
||||||
|
group2,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use ui::theme::ActiveTheme;
|
||||||
use ui::{h_flex, v_flex, StyledExt as _};
|
use ui::{h_flex, v_flex, StyledExt as _};
|
||||||
|
|
||||||
pub struct ScrollableStory {
|
pub struct ScrollableStory {
|
||||||
|
focus_handle: gpui::FocusHandle,
|
||||||
scroll_handle: ScrollHandle,
|
scroll_handle: ScrollHandle,
|
||||||
scroll_size: gpui::Size<Pixels>,
|
scroll_size: gpui::Size<Pixels>,
|
||||||
scroll_state: Rc<Cell<ScrollbarState>>,
|
scroll_state: Rc<Cell<ScrollbarState>>,
|
||||||
|
|
@ -21,8 +22,9 @@ pub struct ScrollableStory {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScrollableStory {
|
impl ScrollableStory {
|
||||||
fn new() -> Self {
|
fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
scroll_handle: ScrollHandle::new(),
|
scroll_handle: ScrollHandle::new(),
|
||||||
scroll_state: Rc::new(Cell::new(ScrollbarState::default())),
|
scroll_state: Rc::new(Cell::new(ScrollbarState::default())),
|
||||||
scroll_size: gpui::Size::default(),
|
scroll_size: gpui::Size::default(),
|
||||||
|
|
@ -33,7 +35,7 @@ impl ScrollableStory {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
||||||
cx.new_view(|_| Self::new())
|
cx.new_view(Self::new)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn change_test_cases(&mut self, n: usize, cx: &mut ViewContext<Self>) {
|
pub fn change_test_cases(&mut self, n: usize, cx: &mut ViewContext<Self>) {
|
||||||
|
|
@ -69,8 +71,14 @@ impl super::Story for ScrollableStory {
|
||||||
"Add vertical or horizontal, or both scrollbars to a container."
|
"Add vertical or horizontal, or both scrollbars to a container."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
|
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
||||||
Self::view(cx).into()
|
Self::view(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl gpui::FocusableView for ScrollableStory {
|
||||||
|
fn focus_handle(&self, _: &gpui::AppContext) -> gpui::FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ use ui::{
|
||||||
v_flex, Disableable as _, Sizable, StyledExt,
|
v_flex, Disableable as _, Sizable, StyledExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct SwitchStory {
|
pub struct SwitchStory {
|
||||||
|
focus_handle: gpui::FocusHandle,
|
||||||
switch1: bool,
|
switch1: bool,
|
||||||
switch2: bool,
|
switch2: bool,
|
||||||
switch3: bool,
|
switch3: bool,
|
||||||
|
|
@ -27,18 +27,19 @@ impl super::Story for SwitchStory {
|
||||||
"A control that allows the user to toggle between two states."
|
"A control that allows the user to toggle between two states."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
|
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
||||||
Self::view(cx).into()
|
Self::view(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SwitchStory {
|
impl SwitchStory {
|
||||||
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
||||||
cx.new_view(|cx| Self::new(cx))
|
cx.new_view(Self::new)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(_: &mut WindowContext) -> Self {
|
fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
switch1: true,
|
switch1: true,
|
||||||
switch2: false,
|
switch2: false,
|
||||||
switch3: true,
|
switch3: true,
|
||||||
|
|
@ -46,6 +47,12 @@ impl SwitchStory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl gpui::FocusableView for SwitchStory {
|
||||||
|
fn focus_handle(&self, _: &gpui::AppContext) -> gpui::FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Render for SwitchStory {
|
impl Render for SwitchStory {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
|
|
|
||||||
|
|
@ -371,8 +371,14 @@ impl super::Story for TableStory {
|
||||||
"A complex data table with selection, sorting, column moving, and loading more."
|
"A complex data table with selection, sorting, column moving, and loading more."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
|
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
||||||
Self::view(cx).into()
|
Self::view(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl gpui::FocusableView for TableStory {
|
||||||
|
fn focus_handle(&self, cx: &gpui::AppContext) -> gpui::FocusHandle {
|
||||||
|
self.table.focus_handle(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ use ui::{
|
||||||
use crate::section;
|
use crate::section;
|
||||||
|
|
||||||
pub struct TextStory {
|
pub struct TextStory {
|
||||||
|
focus_handle: gpui::FocusHandle,
|
||||||
check1: bool,
|
check1: bool,
|
||||||
check2: bool,
|
check2: bool,
|
||||||
check3: bool,
|
check3: bool,
|
||||||
|
|
@ -34,14 +35,15 @@ impl super::Story for TextStory {
|
||||||
"The text render testing and examples"
|
"The text render testing and examples"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
|
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
||||||
Self::view(cx).into()
|
Self::view(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextStory {
|
impl TextStory {
|
||||||
pub(crate) fn new(_cx: &mut WindowContext) -> Self {
|
pub(crate) fn new(cx: &mut WindowContext) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
check1: false,
|
check1: false,
|
||||||
check2: false,
|
check2: false,
|
||||||
check3: true,
|
check3: true,
|
||||||
|
|
@ -60,7 +62,11 @@ impl TextStory {
|
||||||
println!("Check value changed: {}", checked);
|
println!("Check value changed: {}", checked);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl gpui::FocusableView for TextStory {
|
||||||
|
fn focus_handle(&self, _: &gpui::AppContext) -> gpui::FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
impl Render for TextStory {
|
impl Render for TextStory {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
v_flex()
|
v_flex()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, CursorStyle, InteractiveElement, ParentElement, Render, StatefulInteractiveElement,
|
div, CursorStyle, InteractiveElement, ParentElement, Render, StatefulInteractiveElement,
|
||||||
Styled, View, VisualContext as _, WindowContext,
|
Styled, View, ViewContext, VisualContext as _, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ui::{
|
use ui::{
|
||||||
|
|
@ -12,15 +12,19 @@ use ui::{
|
||||||
v_flex,
|
v_flex,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct TooltipStory;
|
pub struct TooltipStory {
|
||||||
|
focus_handle: gpui::FocusHandle,
|
||||||
|
}
|
||||||
|
|
||||||
impl TooltipStory {
|
impl TooltipStory {
|
||||||
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
||||||
cx.new_view(|cx| Self::new(cx))
|
cx.new_view(Self::new)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(_: &mut WindowContext) -> Self {
|
fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||||
Self {}
|
Self {
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,11 +33,15 @@ impl super::Story for TooltipStory {
|
||||||
"Tooltip"
|
"Tooltip"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
|
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
||||||
Self::view(cx).into()
|
Self::view(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl gpui::FocusableView for TooltipStory {
|
||||||
|
fn focus_handle(&self, _: &gpui::AppContext) -> gpui::FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ impl super::Story for WebViewStory {
|
||||||
"WebView"
|
"WebView"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_view(cx: &mut WindowContext) -> gpui::AnyView {
|
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
||||||
Self::view(cx).into()
|
Self::view(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use crate::popup_menu::PopupMenu;
|
use crate::popup_menu::PopupMenu;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyView, AppContext, Axis, EventEmitter, FocusableView, Global, Hsla, Pixels, SharedString,
|
AnyElement, AnyView, AppContext, Axis, EventEmitter, FocusHandle, FocusableView, Global, Hsla,
|
||||||
View, VisualContext, WeakView, WindowContext,
|
IntoElement, Pixels, SharedString, View, VisualContext, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
|
|
@ -29,9 +29,9 @@ pub trait Panel: EventEmitter<PanelEvent> + FocusableView {
|
||||||
/// Once you have defined a panel name, this must not be changed.
|
/// Once you have defined a panel name, this must not be changed.
|
||||||
fn panel_name(&self) -> &'static str;
|
fn panel_name(&self) -> &'static str;
|
||||||
|
|
||||||
/// The title of the panel, default is `None`.
|
/// The title of the panel
|
||||||
fn title(&self, _cx: &WindowContext) -> SharedString {
|
fn title(&self, _cx: &WindowContext) -> AnyElement {
|
||||||
t!("Dock.Unnamed").into()
|
SharedString::from(t!("Dock.Unnamed")).into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The theme of the panel title, default is `None`.
|
/// The theme of the panel title, default is `None`.
|
||||||
|
|
@ -56,7 +56,7 @@ pub trait Panel: EventEmitter<PanelEvent> + FocusableView {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait PanelView: 'static + Send + Sync {
|
pub trait PanelView: 'static + Send + Sync {
|
||||||
fn title(&self, _cx: &WindowContext) -> SharedString;
|
fn title(&self, _cx: &WindowContext) -> AnyElement;
|
||||||
|
|
||||||
fn title_style(&self, _cx: &WindowContext) -> Option<TitleStyle>;
|
fn title_style(&self, _cx: &WindowContext) -> Option<TitleStyle>;
|
||||||
|
|
||||||
|
|
@ -66,11 +66,13 @@ pub trait PanelView: 'static + Send + Sync {
|
||||||
|
|
||||||
fn view(&self) -> AnyView;
|
fn view(&self) -> AnyView;
|
||||||
|
|
||||||
|
fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
|
||||||
|
|
||||||
fn dump(&self, cx: &AppContext) -> DockItemState;
|
fn dump(&self, cx: &AppContext) -> DockItemState;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Panel> PanelView for View<T> {
|
impl<T: Panel> PanelView for View<T> {
|
||||||
fn title(&self, cx: &WindowContext) -> SharedString {
|
fn title(&self, cx: &WindowContext) -> AnyElement {
|
||||||
self.read(cx).title(cx)
|
self.read(cx).title(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,6 +92,10 @@ impl<T: Panel> PanelView for View<T> {
|
||||||
self.clone().into()
|
self.clone().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||||
|
self.read(cx).focus_handle(cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn dump(&self, cx: &AppContext) -> DockItemState {
|
fn dump(&self, cx: &AppContext) -> DockItemState {
|
||||||
self.read(cx).dump(cx)
|
self.read(cx).dump(cx)
|
||||||
}
|
}
|
||||||
|
|
@ -284,10 +290,9 @@ where
|
||||||
.insert(panel_name.to_string(), Arc::new(deserialize));
|
.insert(panel_name.to_string(), Arc::new(deserialize));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
#[test]
|
#[test]
|
||||||
fn test_deserialize_item_state() {
|
fn test_deserialize_item_state() {
|
||||||
let json = include_str!("../../tests/fixtures/layout.json");
|
let json = include_str!("../../tests/fixtures/layout.json");
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ impl Panel for StackPanel {
|
||||||
"StackPanel"
|
"StackPanel"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title(&self, _cx: &gpui::WindowContext) -> gpui::SharedString {
|
fn title(&self, _cx: &gpui::WindowContext) -> gpui::AnyElement {
|
||||||
"StackPanel".into()
|
"StackPanel".into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dump(&self, cx: &AppContext) -> DockItemState {
|
fn dump(&self, cx: &AppContext) -> DockItemState {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ use crate::{
|
||||||
popup_menu::{PopupMenu, PopupMenuExt},
|
popup_menu::{PopupMenu, PopupMenuExt},
|
||||||
tab::{Tab, TabBar},
|
tab::{Tab, TabBar},
|
||||||
theme::ActiveTheme,
|
theme::ActiveTheme,
|
||||||
tooltip::Tooltip,
|
|
||||||
v_flex, AxisExt, IconName, Placement, Selectable, Sizable,
|
v_flex, AxisExt, IconName, Placement, Selectable, Sizable,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -74,10 +73,10 @@ impl Panel for TabPanel {
|
||||||
"TabPanel"
|
"TabPanel"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title(&self, cx: &WindowContext) -> gpui::SharedString {
|
fn title(&self, cx: &WindowContext) -> gpui::AnyElement {
|
||||||
self.active_panel()
|
self.active_panel()
|
||||||
.map(|panel| panel.title(cx))
|
.map(|panel| panel.title(cx))
|
||||||
.unwrap_or("Empty Tab".into())
|
.unwrap_or("Empty Tab".into_any_element())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn closeable(&self, cx: &WindowContext) -> bool {
|
fn closeable(&self, cx: &WindowContext) -> bool {
|
||||||
|
|
@ -134,6 +133,7 @@ impl TabPanel {
|
||||||
fn set_active_ix(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
|
fn set_active_ix(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
|
||||||
self.active_ix = ix;
|
self.active_ix = ix;
|
||||||
self.tab_bar_scroll_handle.scroll_to_item(ix);
|
self.tab_bar_scroll_handle.scroll_to_item(ix);
|
||||||
|
self.focus_active_panel(cx);
|
||||||
cx.emit(PanelEvent::LayoutChanged);
|
cx.emit(PanelEvent::LayoutChanged);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
@ -278,7 +278,6 @@ impl TabPanel {
|
||||||
|
|
||||||
if self.panels.len() == 1 {
|
if self.panels.len() == 1 {
|
||||||
let panel = self.panels.get(0).unwrap();
|
let panel = self.panels.get(0).unwrap();
|
||||||
let title = panel.title(cx);
|
|
||||||
let title_style = panel.title_style(cx);
|
let title_style = panel.title_style(cx);
|
||||||
|
|
||||||
return h_flex()
|
return h_flex()
|
||||||
|
|
@ -297,8 +296,7 @@ impl TabPanel {
|
||||||
.min_w_16()
|
.min_w_16()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.text_ellipsis()
|
.text_ellipsis()
|
||||||
.child(title.clone())
|
.child(panel.title(cx))
|
||||||
.tooltip(move |cx| Tooltip::new(title.clone(), cx))
|
|
||||||
.on_drag(
|
.on_drag(
|
||||||
DragPanel {
|
DragPanel {
|
||||||
panel: panel.clone(),
|
panel: panel.clone(),
|
||||||
|
|
@ -576,6 +574,12 @@ impl TabPanel {
|
||||||
cx.emit(PanelEvent::LayoutChanged);
|
cx.emit(PanelEvent::LayoutChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn focus_active_panel(&self, cx: &mut ViewContext<Self>) {
|
||||||
|
if let Some(active_panel) = self.active_panel() {
|
||||||
|
active_panel.focus_handle(cx).focus(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn on_action_toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
|
fn on_action_toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
|
||||||
self.is_zoomed = !self.is_zoomed;
|
self.is_zoomed = !self.is_zoomed;
|
||||||
if self.is_zoomed {
|
if self.is_zoomed {
|
||||||
|
|
@ -593,17 +597,22 @@ impl TabPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FocusableView for TabPanel {
|
impl FocusableView for TabPanel {
|
||||||
fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
|
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
|
||||||
|
if let Some(active_panel) = self.active_panel() {
|
||||||
|
active_panel.focus_handle(cx)
|
||||||
|
} else {
|
||||||
self.focus_handle.clone()
|
self.focus_handle.clone()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl EventEmitter<DismissEvent> for TabPanel {}
|
impl EventEmitter<DismissEvent> for TabPanel {}
|
||||||
impl EventEmitter<PanelEvent> for TabPanel {}
|
impl EventEmitter<PanelEvent> for TabPanel {}
|
||||||
impl Render for TabPanel {
|
impl Render for TabPanel {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl gpui::IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl gpui::IntoElement {
|
||||||
|
let focus_handle = self.focus_handle(cx);
|
||||||
v_flex()
|
v_flex()
|
||||||
.id("tab-panel")
|
.id("tab-panel")
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&focus_handle)
|
||||||
.on_action(cx.listener(Self::on_action_toggle_zoom))
|
.on_action(cx.listener(Self::on_action_toggle_zoom))
|
||||||
.on_action(cx.listener(Self::on_action_close_panel))
|
.on_action(cx.listener(Self::on_action_close_panel))
|
||||||
.size_full()
|
.size_full()
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ use std::{rc::Rc, time::Duration};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, anchored, div, hsla, prelude::FluentBuilder, px, relative, Animation,
|
actions, anchored, div, hsla, prelude::FluentBuilder, px, relative, Animation,
|
||||||
AnimationExt as _, AnyElement, AppContext, Bounds, ClickEvent, Div, Hsla, InteractiveElement,
|
AnimationExt as _, AnyElement, AppContext, Bounds, ClickEvent, Div, FocusHandle, Hsla,
|
||||||
IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point, RenderOnce, Styled,
|
InteractiveElement, IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point,
|
||||||
WindowContext,
|
RenderOnce, SharedString, Styled, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -28,11 +28,14 @@ pub struct Modal {
|
||||||
width: Pixels,
|
width: Pixels,
|
||||||
max_width: Option<Pixels>,
|
max_width: Option<Pixels>,
|
||||||
margin_top: Option<Pixels>,
|
margin_top: Option<Pixels>,
|
||||||
/// Used to offset the modal from the top when modal is a sub-modal.
|
|
||||||
pub(crate) offset_top: Pixels,
|
|
||||||
on_close: Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>,
|
on_close: Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>,
|
||||||
show_close: bool,
|
show_close: bool,
|
||||||
overlay: bool,
|
overlay: bool,
|
||||||
|
|
||||||
|
/// This will be change when open the modal, the focus handle is create when open the modal.
|
||||||
|
pub(crate) focus_handle: FocusHandle,
|
||||||
|
pub(crate) layer_ix: usize,
|
||||||
pub(crate) overlay_visible: bool,
|
pub(crate) overlay_visible: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,14 +65,15 @@ impl Modal {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
base,
|
base,
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
title: None,
|
title: None,
|
||||||
footer: None,
|
footer: None,
|
||||||
content: v_flex(),
|
content: v_flex(),
|
||||||
margin_top: None,
|
margin_top: None,
|
||||||
offset_top: px(0.),
|
|
||||||
width: px(480.),
|
width: px(480.),
|
||||||
max_width: None,
|
max_width: None,
|
||||||
overlay: true,
|
overlay: true,
|
||||||
|
layer_ix: 0,
|
||||||
overlay_visible: true,
|
overlay_visible: true,
|
||||||
on_close: Rc::new(|_, _| {}),
|
on_close: Rc::new(|_, _| {}),
|
||||||
show_close: true,
|
show_close: true,
|
||||||
|
|
@ -146,13 +150,15 @@ impl Styled for Modal {
|
||||||
|
|
||||||
impl RenderOnce for Modal {
|
impl RenderOnce for Modal {
|
||||||
fn render(self, cx: &mut WindowContext) -> impl gpui::IntoElement {
|
fn render(self, cx: &mut WindowContext) -> impl gpui::IntoElement {
|
||||||
|
let layer_ix = self.layer_ix;
|
||||||
let on_close = self.on_close.clone();
|
let on_close = self.on_close.clone();
|
||||||
let view_size = cx.viewport_size();
|
let view_size = cx.viewport_size();
|
||||||
let bounds = Bounds {
|
let bounds = Bounds {
|
||||||
origin: Point::default(),
|
origin: Point::default(),
|
||||||
size: view_size,
|
size: view_size,
|
||||||
};
|
};
|
||||||
let y = self.margin_top.unwrap_or(view_size.height / 10.) + self.offset_top;
|
let offset_top = px(layer_ix as f32 * 16.);
|
||||||
|
let y = self.margin_top.unwrap_or(view_size.height / 10.) + offset_top;
|
||||||
let x = bounds.center().x - self.width / 2.;
|
let x = bounds.center().x - self.width / 2.;
|
||||||
|
|
||||||
anchored().snap_to_window().child(
|
anchored().snap_to_window().child(
|
||||||
|
|
@ -174,8 +180,9 @@ impl RenderOnce for Modal {
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
self.base
|
self.base
|
||||||
.id("modal")
|
.id(SharedString::from(format!("modal-{layer_ix}")))
|
||||||
.key_context(CONTEXT)
|
.key_context(CONTEXT)
|
||||||
|
.track_focus(&self.focus_handle)
|
||||||
.on_action({
|
.on_action({
|
||||||
let on_close = self.on_close.clone();
|
let on_close = self.on_close.clone();
|
||||||
move |_: &Escape, cx| {
|
move |_: &Escape, cx| {
|
||||||
|
|
@ -199,7 +206,10 @@ impl RenderOnce for Modal {
|
||||||
})
|
})
|
||||||
.when(self.show_close, |this| {
|
.when(self.show_close, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
Button::new("close", cx)
|
Button::new(
|
||||||
|
SharedString::from(format!("modal-close-{layer_ix}")),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
.absolute()
|
.absolute()
|
||||||
.top_2()
|
.top_2()
|
||||||
.right_2()
|
.right_2()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, px, AnyView, FocusHandle, InteractiveElement, IntoElement, ParentElement as _, Render,
|
div, AnyView, FocusHandle, InteractiveElement, IntoElement, ParentElement as _, Render, Styled,
|
||||||
Styled, View, ViewContext, VisualContext as _, WindowContext,
|
View, ViewContext, VisualContext as _, WindowContext,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
|
|
@ -54,7 +54,9 @@ impl<'a> ContextModal for WindowContext<'a> {
|
||||||
F: Fn(Drawer, &mut WindowContext) -> Drawer + 'static,
|
F: Fn(Drawer, &mut WindowContext) -> Drawer + 'static,
|
||||||
{
|
{
|
||||||
Root::update(self, move |root, cx| {
|
Root::update(self, move |root, cx| {
|
||||||
|
if root.active_drawer.is_none() {
|
||||||
root.previous_focus_handle = cx.focused();
|
root.previous_focus_handle = cx.focused();
|
||||||
|
}
|
||||||
root.active_drawer = Some(Rc::new(build));
|
root.active_drawer = Some(Rc::new(build));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
|
|
@ -77,8 +79,19 @@ impl<'a> ContextModal for WindowContext<'a> {
|
||||||
F: Fn(Modal, &mut WindowContext) -> Modal + 'static,
|
F: Fn(Modal, &mut WindowContext) -> Modal + 'static,
|
||||||
{
|
{
|
||||||
Root::update(self, move |root, cx| {
|
Root::update(self, move |root, cx| {
|
||||||
|
// Only save focus handle if there are no active modals.
|
||||||
|
// This is used to restore focus when all modals are closed.
|
||||||
|
if root.active_modals.len() == 0 {
|
||||||
root.previous_focus_handle = cx.focused();
|
root.previous_focus_handle = cx.focused();
|
||||||
root.active_modals.push(Rc::new(build));
|
}
|
||||||
|
|
||||||
|
let focus_handle = cx.focus_handle();
|
||||||
|
focus_handle.focus(cx);
|
||||||
|
|
||||||
|
root.active_modals.push(ActiveModal {
|
||||||
|
focus_handle,
|
||||||
|
builder: Rc::new(build),
|
||||||
|
});
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -90,7 +103,9 @@ impl<'a> ContextModal for WindowContext<'a> {
|
||||||
fn close_modal(&mut self) {
|
fn close_modal(&mut self) {
|
||||||
Root::update(self, move |root, cx| {
|
Root::update(self, move |root, cx| {
|
||||||
root.active_modals.pop();
|
root.active_modals.pop();
|
||||||
|
if root.active_modals.len() == 0 {
|
||||||
root.focus_back(cx);
|
root.focus_back(cx);
|
||||||
|
}
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -180,11 +195,17 @@ pub struct Root {
|
||||||
/// When the Modal, Drawer closes, we will focus back to the previous view.
|
/// When the Modal, Drawer closes, we will focus back to the previous view.
|
||||||
previous_focus_handle: Option<FocusHandle>,
|
previous_focus_handle: Option<FocusHandle>,
|
||||||
active_drawer: Option<Rc<dyn Fn(Drawer, &mut WindowContext) -> Drawer + 'static>>,
|
active_drawer: Option<Rc<dyn Fn(Drawer, &mut WindowContext) -> Drawer + 'static>>,
|
||||||
active_modals: Vec<Rc<dyn Fn(Modal, &mut WindowContext) -> Modal + 'static>>,
|
active_modals: Vec<ActiveModal>,
|
||||||
pub notification: View<NotificationList>,
|
pub notification: View<NotificationList>,
|
||||||
child: AnyView,
|
child: AnyView,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct ActiveModal {
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
builder: Rc<dyn Fn(Modal, &mut WindowContext) -> Modal + 'static>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Root {
|
impl Root {
|
||||||
pub fn new(child: AnyView, cx: &mut ViewContext<Self>) -> Self {
|
pub fn new(child: AnyView, cx: &mut ViewContext<Self>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -220,7 +241,7 @@ impl Root {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn focus_back(&mut self, cx: &mut WindowContext) {
|
fn focus_back(&mut self, cx: &mut WindowContext) {
|
||||||
if let Some(handle) = self.previous_focus_handle.take() {
|
if let Some(handle) = self.previous_focus_handle.clone() {
|
||||||
cx.focus(&handle);
|
cx.focus(&handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -267,11 +288,27 @@ impl Root {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let modals_len = active_modals.len();
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
div().children(active_modals.iter().enumerate().map(|(i, builder)| {
|
div().children(active_modals.iter().enumerate().map(|(i, active_modal)| {
|
||||||
let mut modal = Modal::new(cx);
|
let mut modal = Modal::new(cx);
|
||||||
modal = builder(modal, cx);
|
|
||||||
modal.offset_top = px(i as f32 * 16.);
|
modal = (active_modal.builder)(modal, cx);
|
||||||
|
modal.layer_ix = i;
|
||||||
|
// Give the modal the focus handle, because `modal` is a temporary value, is not possible to
|
||||||
|
// keep the focus handle in the modal.
|
||||||
|
//
|
||||||
|
// So we keep the focus handle in the `active_modal`, this is owned by the `Root`.
|
||||||
|
modal.focus_handle = active_modal.focus_handle.clone();
|
||||||
|
|
||||||
|
// Focus to the top modal.
|
||||||
|
if i == modals_len - 1 {
|
||||||
|
// Check to avoid focus, when the modal is already focused.
|
||||||
|
if !modal.focus_handle.contains_focused(cx) {
|
||||||
|
cx.focus(&modal.focus_handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Keep only have one overlay, we only render the first modal with overlay.
|
// Keep only have one overlay, we only render the first modal with overlay.
|
||||||
if has_overlay {
|
if has_overlay {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue