Add Drawer (#137)
- Update scrollable to accept EntityId. https://github.com/user-attachments/assets/a6c02890-3a6e-4b82-b5c5-3f0a48f35368
This commit is contained in:
parent
7059165fa6
commit
63f333d8ad
13 changed files with 520 additions and 136 deletions
|
|
@ -3,7 +3,7 @@ use prelude::FluentBuilder as _;
|
||||||
use private::serde::Deserialize;
|
use private::serde::Deserialize;
|
||||||
use story::{
|
use story::{
|
||||||
ButtonStory, CalendarStory, DropdownStory, IconStory, ImageStory, InputStory, ListStory,
|
ButtonStory, CalendarStory, DropdownStory, IconStory, ImageStory, InputStory, ListStory,
|
||||||
PickerStory, PopupStory, ProgressStory, ResizableStory, ScrollableStory, StoryContainer,
|
ModalStory, PopupStory, ProgressStory, ResizableStory, ScrollableStory, StoryContainer,
|
||||||
SwitchStory, TableStory, TextStory, TooltipStory,
|
SwitchStory, TableStory, TextStory, TooltipStory,
|
||||||
};
|
};
|
||||||
use workspace::{TitleBar, Workspace};
|
use workspace::{TitleBar, Workspace};
|
||||||
|
|
@ -14,7 +14,7 @@ use ui::{
|
||||||
popover::Popover,
|
popover::Popover,
|
||||||
popup_menu::PopupMenu,
|
popup_menu::PopupMenu,
|
||||||
theme::{ActiveTheme, Theme},
|
theme::{ActiveTheme, Theme},
|
||||||
IconName, Sizable,
|
IconName, Root, Sizable,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::app_state::AppState;
|
use crate::app_state::AppState;
|
||||||
|
|
@ -96,9 +96,9 @@ impl StoryWorkspace {
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
StoryContainer::add_pane(
|
StoryContainer::add_pane(
|
||||||
"Picker",
|
"Modal",
|
||||||
"Picker is a component that allows the user to select an item from a list of options.",
|
"Modal & Drawer use examples",
|
||||||
PickerStory::view(cx).into(),
|
ModalStory::view(cx).into(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
|
@ -212,7 +212,7 @@ impl StoryWorkspace {
|
||||||
pub fn new_local(
|
pub fn new_local(
|
||||||
app_state: Arc<AppState>,
|
app_state: Arc<AppState>,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Task<anyhow::Result<WindowHandle<Self>>> {
|
) -> Task<anyhow::Result<WindowHandle<Root>>> {
|
||||||
let window_bounds = Bounds::centered(None, size(px(1600.0), px(1200.0)), cx);
|
let window_bounds = Bounds::centered(None, size(px(1600.0), px(1200.0)), cx);
|
||||||
|
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
|
|
@ -233,7 +233,8 @@ impl StoryWorkspace {
|
||||||
|
|
||||||
let window = cx.open_window(options, |cx| {
|
let window = cx.open_window(options, |cx| {
|
||||||
let workspace = cx.new_view(|cx| Workspace::new(None, cx));
|
let workspace = cx.new_view(|cx| Workspace::new(None, cx));
|
||||||
cx.new_view(|cx| Self::new(app_state.clone(), workspace, cx))
|
let story_view = cx.new_view(|cx| Self::new(app_state.clone(), workspace, cx));
|
||||||
|
cx.new_view(|cx| Root::new(story_view.into(), cx))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
window
|
window
|
||||||
|
|
@ -256,14 +257,13 @@ impl StoryWorkspace {
|
||||||
pub fn open_new(
|
pub fn open_new(
|
||||||
app_state: Arc<AppState>,
|
app_state: Arc<AppState>,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
init: impl FnOnce(&mut StoryWorkspace, &mut ViewContext<StoryWorkspace>) + 'static + Send,
|
init: impl FnOnce(&mut Root, &mut ViewContext<Root>) + 'static + Send,
|
||||||
) -> Task<()> {
|
) -> Task<()> {
|
||||||
let task: Task<std::result::Result<WindowHandle<StoryWorkspace>, anyhow::Error>> =
|
let task: Task<std::result::Result<WindowHandle<Root>, anyhow::Error>> =
|
||||||
StoryWorkspace::new_local(app_state, cx);
|
StoryWorkspace::new_local(app_state, cx);
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
if let Some(workspace) = task.await.ok() {
|
if let Some(root) = task.await.ok() {
|
||||||
workspace
|
root.update(&mut cx, |workspace, cx| init(workspace, cx))
|
||||||
.update(&mut cx, |workspace, cx| init(workspace, cx))
|
|
||||||
.expect("failed to init workspace");
|
.expect("failed to init workspace");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ mod icon_story;
|
||||||
mod image_story;
|
mod image_story;
|
||||||
mod input_story;
|
mod input_story;
|
||||||
mod list_story;
|
mod list_story;
|
||||||
mod picker_story;
|
mod modal_story;
|
||||||
mod popup_story;
|
mod popup_story;
|
||||||
mod progress_story;
|
mod progress_story;
|
||||||
mod resizable_story;
|
mod resizable_story;
|
||||||
|
|
@ -23,7 +23,7 @@ pub use icon_story::IconStory;
|
||||||
pub use image_story::ImageStory;
|
pub use image_story::ImageStory;
|
||||||
pub use input_story::InputStory;
|
pub use input_story::InputStory;
|
||||||
pub use list_story::ListStory;
|
pub use list_story::ListStory;
|
||||||
pub use picker_story::PickerStory;
|
pub use modal_story::ModalStory;
|
||||||
pub use popup_story::PopupStory;
|
pub use popup_story::PopupStory;
|
||||||
pub use progress_story::ProgressStory;
|
pub use progress_story::ProgressStory;
|
||||||
pub use resizable_story::ResizableStory;
|
pub use resizable_story::ResizableStory;
|
||||||
|
|
|
||||||
|
|
@ -246,7 +246,6 @@ fn random_company() -> Company {
|
||||||
Company {
|
Company {
|
||||||
name: fake::faker::company::en::CompanyName().fake(),
|
name: fake::faker::company::en::CompanyName().fake(),
|
||||||
industry: fake::faker::company::en::Industry().fake(),
|
industry: fake::faker::company::en::Industry().fake(),
|
||||||
// description: fake::faker::lorem::en::Paragraph(3..5).fake(),
|
|
||||||
last_done,
|
last_done,
|
||||||
prev_close,
|
prev_close,
|
||||||
}
|
}
|
||||||
|
|
@ -260,15 +259,14 @@ impl FocusableView for ListStory {
|
||||||
|
|
||||||
impl Render for ListStory {
|
impl Render for ListStory {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
h_flex()
|
div()
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
.on_action(cx.listener(Self::selected_company))
|
.on_action(cx.listener(Self::selected_company))
|
||||||
.size_full()
|
.size_full()
|
||||||
.gap_4()
|
.gap_4()
|
||||||
.mb_4()
|
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(cx.theme().border)
|
.border_color(cx.theme().border)
|
||||||
.rounded_md()
|
.rounded_md()
|
||||||
.child(v_flex().h_full().w_full().child(self.company_list.clone()))
|
.child(self.company_list.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,22 @@ use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
use fake::Fake;
|
use fake::Fake;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
deferred, div, prelude::FluentBuilder as _, px, Animation, AnimationExt as _, FocusHandle,
|
div, prelude::FluentBuilder as _, px, FocusHandle, FocusableView, IntoElement, ParentElement,
|
||||||
FocusableView, InteractiveElement as _, IntoElement, ParentElement, Render, Styled, Task,
|
Render, Styled, Task, Timer, View, ViewContext, VisualContext as _, WeakView, WindowContext,
|
||||||
Timer, View, ViewContext, VisualContext as _, WeakView, WindowContext,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use ui::{
|
use ui::{
|
||||||
button::{Button, ButtonStyle},
|
button::{Button, ButtonStyle},
|
||||||
|
date_picker::DatePicker,
|
||||||
h_flex,
|
h_flex,
|
||||||
|
input::TextInput,
|
||||||
list::{List, ListDelegate, ListItem},
|
list::{List, ListDelegate, ListItem},
|
||||||
theme::ActiveTheme as _,
|
theme::ActiveTheme as _,
|
||||||
v_flex, Icon, IconName, StyledExt,
|
v_flex, ContextModal as _, Icon, IconName, Placement,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ListItemDeletegate {
|
pub struct ListItemDeletegate {
|
||||||
story: WeakView<PickerStory>,
|
story: WeakView<ModalStory>,
|
||||||
confirmed_index: Option<usize>,
|
confirmed_index: Option<usize>,
|
||||||
selected_index: Option<usize>,
|
selected_index: Option<usize>,
|
||||||
items: Vec<Arc<String>>,
|
items: Vec<Arc<String>>,
|
||||||
|
|
@ -108,10 +109,7 @@ impl ListDelegate for ListItemDeletegate {
|
||||||
|
|
||||||
fn cancel(&mut self, cx: &mut ViewContext<List<Self>>) {
|
fn cancel(&mut self, cx: &mut ViewContext<List<Self>>) {
|
||||||
if let Some(story) = self.story.upgrade() {
|
if let Some(story) = self.story.upgrade() {
|
||||||
cx.update_view(&story, |story, cx| {
|
cx.update_view(&story, |story, cx| story.close_drawer(cx));
|
||||||
story.open = false;
|
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,8 +122,7 @@ impl ListDelegate for ListItemDeletegate {
|
||||||
story.selected_value = Some(item.clone());
|
story.selected_value = Some(item.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
story.open = false;
|
cx.close_drawer();
|
||||||
cx.notify();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -139,13 +136,16 @@ impl ListDelegate for ListItemDeletegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PickerStory {
|
pub struct ModalStory {
|
||||||
list: View<List<ListItemDeletegate>>,
|
focus_handle: FocusHandle,
|
||||||
open: bool,
|
drawer_placement: Option<Placement>,
|
||||||
selected_value: Option<Arc<String>>,
|
selected_value: Option<Arc<String>>,
|
||||||
|
list: View<List<ListItemDeletegate>>,
|
||||||
|
input1: View<TextInput>,
|
||||||
|
date_picker: View<DatePicker>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PickerStory {
|
impl ModalStory {
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
@ -220,79 +220,126 @@ impl PickerStory {
|
||||||
list
|
list
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let input1 = cx.new_view(|cx| TextInput::new(cx).placeholder("Your Name"));
|
||||||
|
let date_picker =
|
||||||
|
cx.new_view(|cx| DatePicker::new("birthday-picker", cx).placeholder("Date of Birth"));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
list,
|
focus_handle: cx.focus_handle(),
|
||||||
open: false,
|
drawer_placement: None,
|
||||||
selected_value: None,
|
selected_value: None,
|
||||||
|
list,
|
||||||
|
input1,
|
||||||
|
date_picker,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl FocusableView for PickerStory {
|
fn open_drawer_at(&mut self, placement: Placement, cx: &mut ViewContext<Self>) {
|
||||||
fn focus_handle(&self, cx: &gpui::AppContext) -> FocusHandle {
|
let input = self.input1.clone();
|
||||||
self.list.focus_handle(cx)
|
let date_picker = self.date_picker.clone();
|
||||||
}
|
let list = self.list.clone();
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for PickerStory {
|
let list_h = match placement {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
Placement::Left | Placement::Right => px(400.),
|
||||||
v_flex()
|
Placement::Top | Placement::Bottom => px(160.),
|
||||||
.gap_6()
|
};
|
||||||
.child(
|
|
||||||
v_flex().items_start().child(
|
cx.open_drawer(move |this, cx| {
|
||||||
Button::new("show-picker", cx)
|
this.margin_top(px(33.))
|
||||||
.label("Show Picker...")
|
.placement(placement)
|
||||||
.icon(IconName::Search)
|
.size(px(400.))
|
||||||
.on_click(cx.listener(|this, _, cx| {
|
.title("Drawer Title")
|
||||||
this.open = !this.open;
|
.gap_4()
|
||||||
this.list.focus_handle(cx).focus(cx);
|
.child(input.clone())
|
||||||
cx.notify();
|
.child(date_picker.clone())
|
||||||
})),
|
.child(
|
||||||
),
|
|
||||||
)
|
|
||||||
.when_some(self.selected_value.clone(), |this, selected_value| {
|
|
||||||
this.child(
|
|
||||||
h_flex().gap_1().child("You have selected:").child(
|
|
||||||
div()
|
|
||||||
.child(selected_value.to_string())
|
|
||||||
.text_color(gpui::red()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.when(self.open, |this| {
|
|
||||||
this.child(deferred(
|
|
||||||
div()
|
div()
|
||||||
.absolute()
|
.border_1()
|
||||||
|
.border_color(cx.theme().border)
|
||||||
|
.rounded_md()
|
||||||
.size_full()
|
.size_full()
|
||||||
.top_0()
|
.flex_1()
|
||||||
.left_0()
|
.h(list_h)
|
||||||
|
.child(list.clone()),
|
||||||
|
)
|
||||||
|
.footer(
|
||||||
|
h_flex()
|
||||||
|
.gap_6()
|
||||||
|
.items_center()
|
||||||
.child(
|
.child(
|
||||||
v_flex().flex().flex_col().items_center().child(
|
Button::new("confirm", cx)
|
||||||
v_flex()
|
.primary()
|
||||||
.occlude()
|
.label("Confirm")
|
||||||
.w(px(450.))
|
.on_click(|_, cx| {
|
||||||
.h(px(350.))
|
cx.close_drawer();
|
||||||
.bg(cx.theme().popover)
|
}),
|
||||||
.border_1()
|
|
||||||
.border_color(cx.theme().border)
|
|
||||||
.shadow_lg()
|
|
||||||
.rounded_lg()
|
|
||||||
.child(self.list.clone())
|
|
||||||
.on_mouse_down_out(cx.listener(|this, _, cx| {
|
|
||||||
this.open = false;
|
|
||||||
cx.notify();
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.with_animation(
|
.child(Button::new("cancel", cx).label("Cancel").on_click(|_, cx| {
|
||||||
"slide-down",
|
cx.close_drawer();
|
||||||
Animation::new(Duration::from_secs_f64(0.15)),
|
})),
|
||||||
move |this, delta| {
|
)
|
||||||
let y = px(-10.) + delta * px(10.);
|
});
|
||||||
this.top(y).opacity((1.0 * delta + 0.3).min(1.0))
|
}
|
||||||
},
|
|
||||||
),
|
fn close_drawer(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
))
|
self.drawer_placement = None;
|
||||||
})
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FocusableView for ModalStory {
|
||||||
|
fn focus_handle(&self, _cx: &gpui::AppContext) -> FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for ModalStory {
|
||||||
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
div().child(
|
||||||
|
v_flex()
|
||||||
|
.gap_6()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.items_start()
|
||||||
|
.gap_3()
|
||||||
|
.child(
|
||||||
|
Button::new("show-drawer-left", cx)
|
||||||
|
.label("Left Drawer...")
|
||||||
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
|
this.open_drawer_at(Placement::Left, cx)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("show-drawer-top", cx)
|
||||||
|
.label("Top Drawer...")
|
||||||
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
|
this.open_drawer_at(Placement::Top, cx)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("show-drawer", cx)
|
||||||
|
.label("Right Drawer...")
|
||||||
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
|
this.open_drawer_at(Placement::Right, cx)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("show-drawer", cx)
|
||||||
|
.label("Bottom Drawer...")
|
||||||
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
|
this.open_drawer_at(Placement::Bottom, cx)
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.when_some(self.selected_value.clone(), |this, selected_value| {
|
||||||
|
this.child(
|
||||||
|
h_flex().gap_1().child("You have selected:").child(
|
||||||
|
div()
|
||||||
|
.child(selected_value.to_string())
|
||||||
|
.text_color(gpui::red()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ use std::cell::Cell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
canvas, div, px, InteractiveElement, ParentElement, Pixels, Render, ScrollHandle,
|
canvas, div, px, Entity, InteractiveElement, ParentElement, Pixels, Render, ScrollHandle,
|
||||||
StatefulInteractiveElement as _, Styled, View, ViewContext, VisualContext, WindowContext,
|
StatefulInteractiveElement as _, Styled, View, ViewContext, VisualContext, WindowContext,
|
||||||
};
|
};
|
||||||
use ui::button::Button;
|
use ui::button::Button;
|
||||||
|
|
@ -65,6 +65,7 @@ impl Render for ScrollableStory {
|
||||||
let view = cx.view().clone();
|
let view = cx.view().clone();
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
|
.size_full()
|
||||||
.gap_4()
|
.gap_4()
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
|
@ -170,7 +171,7 @@ impl Render for ScrollableStory {
|
||||||
.bottom_0()
|
.bottom_0()
|
||||||
.child(
|
.child(
|
||||||
Scrollbar::both(
|
Scrollbar::both(
|
||||||
view,
|
view.entity_id(),
|
||||||
self.scroll_state.clone(),
|
self.scroll_state.clone(),
|
||||||
self.scroll_handle.clone(),
|
self.scroll_handle.clone(),
|
||||||
self.scroll_size,
|
self.scroll_size,
|
||||||
|
|
@ -189,11 +190,12 @@ impl Render for ScrollableStory {
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(cx.theme().border)
|
.border_color(cx.theme().border)
|
||||||
.w_full()
|
.w_full()
|
||||||
.h(px(200.))
|
.flex_1()
|
||||||
|
.overflow_hidden()
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.id("test-1")
|
.id("test-1")
|
||||||
.scrollable(cx.view().clone(), ScrollbarAxis::Vertical)
|
.scrollable(cx.view().entity_id(), ScrollbarAxis::Vertical)
|
||||||
.focusable()
|
.focusable()
|
||||||
.p_3()
|
.p_3()
|
||||||
.w(test_width)
|
.w(test_width)
|
||||||
|
|
|
||||||
220
crates/ui/src/drawer.rs
Normal file
220
crates/ui/src/drawer.rs
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
use std::{rc::Rc, time::Duration};
|
||||||
|
|
||||||
|
use gpui::{
|
||||||
|
anchored, div, hsla, point, prelude::FluentBuilder as _, px, Animation, AnimationExt as _,
|
||||||
|
AnyElement, ClickEvent, DefiniteLength, DismissEvent, Div, EventEmitter, FocusHandle,
|
||||||
|
InteractiveElement as _, IntoElement, MouseButton, ParentElement, Pixels, RenderOnce, Styled,
|
||||||
|
WindowContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
button::Button, h_flex, root::ContextModal as _, scroll::ScrollbarAxis, theme::ActiveTheme,
|
||||||
|
v_flex, IconName, Placement, Sizable, StyledExt as _,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct Drawer {
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
placement: Placement,
|
||||||
|
size: DefiniteLength,
|
||||||
|
resizable: bool,
|
||||||
|
on_close: Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>,
|
||||||
|
title: Option<AnyElement>,
|
||||||
|
footer: Option<AnyElement>,
|
||||||
|
content: Div,
|
||||||
|
margin_top: Pixels,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drawer {
|
||||||
|
pub fn new(cx: &mut WindowContext) -> Self {
|
||||||
|
Self {
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
|
placement: Placement::Right,
|
||||||
|
size: DefiniteLength::Absolute(px(350.).into()),
|
||||||
|
resizable: true,
|
||||||
|
title: None,
|
||||||
|
footer: None,
|
||||||
|
content: v_flex(),
|
||||||
|
margin_top: px(0.),
|
||||||
|
on_close: Rc::new(|_, _| {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the title of the drawer.
|
||||||
|
pub fn title(mut self, title: impl IntoElement) -> Self {
|
||||||
|
self.title = Some(title.into_any_element());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the footer of the drawer.
|
||||||
|
pub fn footer(mut self, footer: impl IntoElement) -> Self {
|
||||||
|
self.footer = Some(footer.into_any_element());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the size of the drawer, default is 350px.
|
||||||
|
pub fn size(mut self, size: impl Into<DefiniteLength>) -> Self {
|
||||||
|
self.size = size.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the margin top of the drawer, default is 0px.
|
||||||
|
///
|
||||||
|
/// This is used to let Drawer be placed below a Windows Title, you can give the height of the title bar.
|
||||||
|
pub fn margin_top(mut self, top: Pixels) -> Self {
|
||||||
|
self.margin_top = top;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the placement of the drawer, default is `Placement::Right`.
|
||||||
|
pub fn placement(mut self, placement: Placement) -> Self {
|
||||||
|
self.placement = placement;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the placement of the drawer, default is `Placement::Right`.
|
||||||
|
pub fn set_placement(&mut self, placement: Placement) {
|
||||||
|
self.placement = placement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets whether the drawer is resizable, default is `true`.
|
||||||
|
pub fn resizable(mut self, resizable: bool) -> Self {
|
||||||
|
self.resizable = resizable;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Listen to the close event of the drawer.
|
||||||
|
pub fn on_close(
|
||||||
|
mut self,
|
||||||
|
on_close: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
||||||
|
) -> Self {
|
||||||
|
self.on_close = Rc::new(on_close);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<DismissEvent> for Drawer {}
|
||||||
|
impl ParentElement for Drawer {
|
||||||
|
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||||
|
self.content.extend(elements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Styled for Drawer {
|
||||||
|
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
||||||
|
self.content.style()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for Drawer {
|
||||||
|
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||||
|
let focus_handle = self.focus_handle.clone();
|
||||||
|
let placement = self.placement;
|
||||||
|
let titlebar_height = self.margin_top;
|
||||||
|
let size = cx.viewport_size();
|
||||||
|
let on_close = self.on_close.clone();
|
||||||
|
|
||||||
|
let overlay_color = if cx.theme().mode.is_dark() {
|
||||||
|
hsla(0., 1., 1., 0.06)
|
||||||
|
} else {
|
||||||
|
hsla(0., 0., 0., 0.06)
|
||||||
|
};
|
||||||
|
|
||||||
|
anchored()
|
||||||
|
.position(point(px(0.), titlebar_height))
|
||||||
|
.snap_to_window()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.occlude()
|
||||||
|
.w(size.width)
|
||||||
|
.h(size.height - titlebar_height)
|
||||||
|
.bg(overlay_color)
|
||||||
|
.on_mouse_down(MouseButton::Left, {
|
||||||
|
let on_close = self.on_close.clone();
|
||||||
|
move |_, cx| {
|
||||||
|
on_close(&ClickEvent::default(), cx);
|
||||||
|
cx.close_drawer();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.id("")
|
||||||
|
.track_focus(&focus_handle)
|
||||||
|
.absolute()
|
||||||
|
.occlude()
|
||||||
|
.bg(cx.theme().background)
|
||||||
|
.border_color(cx.theme().border)
|
||||||
|
.shadow_xl()
|
||||||
|
.map(|this| {
|
||||||
|
// Set the size of the drawer.
|
||||||
|
if placement.is_vertical() {
|
||||||
|
this.h_full().w(self.size)
|
||||||
|
} else {
|
||||||
|
this.w_full().h(self.size)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|this| match self.placement {
|
||||||
|
Placement::Top => this.top_0().left_0().right_0().border_b_1(),
|
||||||
|
Placement::Right => this.top_0().right_0().bottom_0().border_l_1(),
|
||||||
|
Placement::Bottom => {
|
||||||
|
this.bottom_0().left_0().right_0().border_t_1()
|
||||||
|
}
|
||||||
|
Placement::Left => this.top_0().left_0().bottom_0().border_r_1(),
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
// TitleBar
|
||||||
|
h_flex()
|
||||||
|
.justify_between()
|
||||||
|
.px_4()
|
||||||
|
.py_3()
|
||||||
|
.w_full()
|
||||||
|
.child(self.title.unwrap_or(div().into_any_element()))
|
||||||
|
.child(
|
||||||
|
Button::new("close", cx)
|
||||||
|
.small()
|
||||||
|
.ghost()
|
||||||
|
.icon(IconName::Close)
|
||||||
|
.on_click(move |_, cx| {
|
||||||
|
on_close(&ClickEvent::default(), cx);
|
||||||
|
cx.close_drawer();
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div().flex_1().overflow_hidden().child(
|
||||||
|
v_flex()
|
||||||
|
.p_4()
|
||||||
|
.pt_0()
|
||||||
|
.scrollable(
|
||||||
|
cx.parent_view_id().unwrap_or_default(),
|
||||||
|
ScrollbarAxis::Vertical,
|
||||||
|
)
|
||||||
|
.child(self.content),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.when_some(self.footer, |this, footer| {
|
||||||
|
this.child(
|
||||||
|
h_flex()
|
||||||
|
.justify_between()
|
||||||
|
.px_4()
|
||||||
|
.py_3()
|
||||||
|
.w_full()
|
||||||
|
.child(footer),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.with_animation(
|
||||||
|
"slide",
|
||||||
|
Animation::new(Duration::from_secs_f64(0.15)),
|
||||||
|
move |this, delta| {
|
||||||
|
let y = px(-100.) + delta * px(100.);
|
||||||
|
this.map(|this| match placement {
|
||||||
|
Placement::Top => this.top(y),
|
||||||
|
Placement::Right => this.right(y),
|
||||||
|
Placement::Bottom => this.bottom(y),
|
||||||
|
Placement::Left => this.left(y),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ mod event;
|
||||||
mod focusable;
|
mod focusable;
|
||||||
mod icon;
|
mod icon;
|
||||||
|
|
||||||
|
mod root;
|
||||||
mod styled;
|
mod styled;
|
||||||
mod svg_img;
|
mod svg_img;
|
||||||
mod time;
|
mod time;
|
||||||
|
|
@ -12,6 +13,7 @@ pub mod checkbox;
|
||||||
pub mod clipboard;
|
pub mod clipboard;
|
||||||
pub mod context_menu;
|
pub mod context_menu;
|
||||||
pub mod divider;
|
pub mod divider;
|
||||||
|
pub mod drawer;
|
||||||
pub mod dropdown;
|
pub mod dropdown;
|
||||||
pub mod indicator;
|
pub mod indicator;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
|
|
@ -34,14 +36,13 @@ pub mod theme;
|
||||||
pub mod tooltip;
|
pub mod tooltip;
|
||||||
pub mod webview;
|
pub mod webview;
|
||||||
|
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
// re-export
|
// re-export
|
||||||
pub use wry;
|
pub use wry;
|
||||||
|
|
||||||
pub use crate::Disableable;
|
pub use crate::Disableable;
|
||||||
pub use event::InteractiveElementExt;
|
pub use event::InteractiveElementExt;
|
||||||
pub use focusable::FocusableCycle;
|
pub use focusable::FocusableCycle;
|
||||||
|
pub use root::{ContextModal, Root};
|
||||||
pub use styled::*;
|
pub use styled::*;
|
||||||
pub use time::*;
|
pub use time::*;
|
||||||
|
|
||||||
|
|
@ -49,8 +50,6 @@ pub use colors::*;
|
||||||
pub use icon::*;
|
pub use icon::*;
|
||||||
pub use svg_img::*;
|
pub use svg_img::*;
|
||||||
|
|
||||||
rust_i18n::i18n!("locales", fallback = "en");
|
|
||||||
|
|
||||||
/// Initialize the UI module.
|
/// Initialize the UI module.
|
||||||
pub fn init(cx: &mut gpui::AppContext) {
|
pub fn init(cx: &mut gpui::AppContext) {
|
||||||
input::init(cx);
|
input::init(cx);
|
||||||
|
|
@ -64,6 +63,8 @@ pub fn init(cx: &mut gpui::AppContext) {
|
||||||
webview::init(cx)
|
webview::init(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rust_i18n::i18n!("locales", fallback = "en");
|
||||||
|
use std::ops::Deref;
|
||||||
pub fn locale() -> impl Deref<Target = str> {
|
pub fn locale() -> impl Deref<Target = str> {
|
||||||
rust_i18n::locale()
|
rust_i18n::locale()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use gpui::{
|
||||||
InteractiveElement, IntoElement, KeyBinding, Length, ListSizingBehavior, MouseButton,
|
InteractiveElement, IntoElement, KeyBinding, Length, ListSizingBehavior, MouseButton,
|
||||||
ParentElement, Render, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext,
|
ParentElement, Render, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext,
|
||||||
};
|
};
|
||||||
use gpui::{SharedString, WindowContext};
|
use gpui::{Entity, SharedString, WindowContext};
|
||||||
use smol::Timer;
|
use smol::Timer;
|
||||||
|
|
||||||
actions!(list, [Cancel, Confirm, SelectPrev, SelectNext]);
|
actions!(list, [Cancel, Confirm, SelectPrev, SelectNext]);
|
||||||
|
|
@ -167,7 +167,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(Scrollbar::uniform_scroll(
|
Some(Scrollbar::uniform_scroll(
|
||||||
cx.view().clone(),
|
cx.view().entity_id(),
|
||||||
self.scrollbar_state.clone(),
|
self.scrollbar_state.clone(),
|
||||||
self.vertical_scroll_handle.clone(),
|
self.vertical_scroll_handle.clone(),
|
||||||
self.delegate.items_count(),
|
self.delegate.items_count(),
|
||||||
|
|
|
||||||
89
crates/ui/src/root.rs
Normal file
89
crates/ui/src/root.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
use gpui::{
|
||||||
|
div, prelude::FluentBuilder as _, AnyView, ParentElement as _, Render, Styled, ViewContext,
|
||||||
|
WindowContext,
|
||||||
|
};
|
||||||
|
use std::{ops::DerefMut, rc::Rc};
|
||||||
|
|
||||||
|
use crate::{drawer::Drawer, theme::ActiveTheme};
|
||||||
|
|
||||||
|
/// Extension trait for [`WindowContext`] and [`ViewContext`] to add drawer functionality.
|
||||||
|
pub trait ContextModal: Sized {
|
||||||
|
/// Opens a drawer.
|
||||||
|
fn open_drawer<F>(&mut self, build: F)
|
||||||
|
where
|
||||||
|
F: Fn(Drawer, &mut WindowContext) -> Drawer + 'static;
|
||||||
|
|
||||||
|
/// Closes the active drawer.
|
||||||
|
fn close_drawer(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ContextModal for WindowContext<'a> {
|
||||||
|
fn open_drawer<F>(&mut self, build: F)
|
||||||
|
where
|
||||||
|
F: Fn(Drawer, &mut WindowContext) -> Drawer + 'static,
|
||||||
|
{
|
||||||
|
Root::update_root(self, move |root, cx| {
|
||||||
|
root.active_drawer = Some(Rc::new(build));
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close_drawer(&mut self) {
|
||||||
|
Root::update_root(self, |root, cx| {
|
||||||
|
root.active_drawer = None;
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, V> ContextModal for ViewContext<'a, V> {
|
||||||
|
fn open_drawer<F>(&mut self, build: F)
|
||||||
|
where
|
||||||
|
F: Fn(Drawer, &mut WindowContext) -> Drawer + 'static,
|
||||||
|
{
|
||||||
|
self.deref_mut().open_drawer(build)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close_drawer(&mut self) {
|
||||||
|
self.deref_mut().close_drawer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Root {
|
||||||
|
active_drawer: Option<Rc<dyn Fn(Drawer, &mut WindowContext) -> Drawer + 'static>>,
|
||||||
|
root_view: AnyView,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Root {
|
||||||
|
pub fn new(root_view: AnyView, _cx: &mut ViewContext<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
active_drawer: None,
|
||||||
|
root_view,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_root<F>(cx: &mut WindowContext, f: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Self, &mut ViewContext<Self>) + 'static,
|
||||||
|
{
|
||||||
|
let root = cx
|
||||||
|
.window_handle()
|
||||||
|
.downcast::<Root>()
|
||||||
|
.and_then(|w| w.root_view(cx).ok())
|
||||||
|
.expect("The window root view should be of type `ui::Root`.");
|
||||||
|
|
||||||
|
root.update(cx, |root, cx| f(root, cx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for Root {
|
||||||
|
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.text_color(cx.theme().foreground)
|
||||||
|
.child(self.root_view.clone())
|
||||||
|
.when_some(self.active_drawer.clone(), |this, build| {
|
||||||
|
let drawer = Drawer::new(cx);
|
||||||
|
this.child(build(drawer, cx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ use std::{cell::Cell, rc::Rc};
|
||||||
|
|
||||||
use super::{Scrollbar, ScrollbarAxis, ScrollbarState};
|
use super::{Scrollbar, ScrollbarAxis, ScrollbarState};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
canvas, div, relative, AnyElement, AnyView, Div, Element, ElementId, GlobalElementId,
|
canvas, div, relative, AnyElement, Div, Element, ElementId, EntityId, GlobalElementId,
|
||||||
InteractiveElement, IntoElement, ParentElement, Pixels, Position, ScrollHandle, SharedString,
|
InteractiveElement, IntoElement, ParentElement, Pixels, Position, ScrollHandle, SharedString,
|
||||||
Size, Stateful, StatefulInteractiveElement, Style, StyleRefinement, Styled, WindowContext,
|
Size, Stateful, StatefulInteractiveElement, Style, StyleRefinement, Styled, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
@ -11,7 +11,7 @@ use gpui::{
|
||||||
pub struct Scrollable<E> {
|
pub struct Scrollable<E> {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
element: Option<E>,
|
element: Option<E>,
|
||||||
view: AnyView,
|
view_id: EntityId,
|
||||||
axis: ScrollbarAxis,
|
axis: ScrollbarAxis,
|
||||||
/// This is a fake element to handle Styled, InteractiveElement, not used.
|
/// This is a fake element to handle Styled, InteractiveElement, not used.
|
||||||
_element: Stateful<Div>,
|
_element: Stateful<Div>,
|
||||||
|
|
@ -21,11 +21,10 @@ impl<E> Scrollable<E>
|
||||||
where
|
where
|
||||||
E: Element,
|
E: Element,
|
||||||
{
|
{
|
||||||
pub(crate) fn new(element: E, view: impl Into<AnyView>, axis: ScrollbarAxis) -> Self {
|
pub(crate) fn new(view_id: EntityId, element: E, axis: ScrollbarAxis) -> Self {
|
||||||
let view: AnyView = view.into();
|
|
||||||
let id = ElementId::Name(SharedString::from(format!(
|
let id = ElementId::Name(SharedString::from(format!(
|
||||||
"ScrollView:{}-{:?}",
|
"ScrollView:{}-{:?}",
|
||||||
view.entity_id(),
|
view_id,
|
||||||
element.id(),
|
element.id(),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
|
|
@ -33,7 +32,7 @@ where
|
||||||
element: Some(element),
|
element: Some(element),
|
||||||
_element: div().id("fake"),
|
_element: div().id("fake"),
|
||||||
id,
|
id,
|
||||||
view,
|
view_id,
|
||||||
axis,
|
axis,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -158,7 +157,7 @@ where
|
||||||
style.size.height = relative(1.0).into();
|
style.size.height = relative(1.0).into();
|
||||||
|
|
||||||
let axis = self.axis;
|
let axis = self.axis;
|
||||||
let view = self.view.clone();
|
let view_id = self.view_id;
|
||||||
|
|
||||||
let scroll_id = self.id.clone();
|
let scroll_id = self.id.clone();
|
||||||
let content = self.element.take().map(|c| c.into_any_element());
|
let content = self.element.take().map(|c| c.into_any_element());
|
||||||
|
|
@ -171,6 +170,7 @@ where
|
||||||
let mut element = div()
|
let mut element = div()
|
||||||
.relative()
|
.relative()
|
||||||
.size_full()
|
.size_full()
|
||||||
|
.overflow_hidden()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.id(scroll_id)
|
.id(scroll_id)
|
||||||
|
|
@ -193,7 +193,7 @@ where
|
||||||
.right_0()
|
.right_0()
|
||||||
.bottom_0()
|
.bottom_0()
|
||||||
.child(
|
.child(
|
||||||
Scrollbar::both(view, state, handle.clone(), scroll_size.get())
|
Scrollbar::both(view_id, state, handle.clone(), scroll_size.get())
|
||||||
.axis(axis),
|
.axis(axis),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::{cell::Cell, rc::Rc};
|
||||||
|
|
||||||
use crate::theme::ActiveTheme;
|
use crate::theme::ActiveTheme;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
fill, point, px, relative, size, AnyView, Bounds, ContentMask, Edges, Element, Hitbox,
|
fill, point, px, relative, size, Bounds, ContentMask, Edges, Element, EntityId, Hitbox,
|
||||||
IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point, Position,
|
IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point, Position,
|
||||||
ScrollHandle, Style, UniformListScrollHandle,
|
ScrollHandle, Style, UniformListScrollHandle,
|
||||||
};
|
};
|
||||||
|
|
@ -140,7 +140,7 @@ impl ScrollbarAxis {
|
||||||
|
|
||||||
/// Scrollbar control for scroll-area or a uniform-list.
|
/// Scrollbar control for scroll-area or a uniform-list.
|
||||||
pub struct Scrollbar {
|
pub struct Scrollbar {
|
||||||
view: AnyView,
|
view_id: EntityId,
|
||||||
axis: ScrollbarAxis,
|
axis: ScrollbarAxis,
|
||||||
/// When is vertical, this is the height of the scrollbar.
|
/// When is vertical, this is the height of the scrollbar.
|
||||||
width: Pixels,
|
width: Pixels,
|
||||||
|
|
@ -151,14 +151,14 @@ pub struct Scrollbar {
|
||||||
|
|
||||||
impl Scrollbar {
|
impl Scrollbar {
|
||||||
fn new(
|
fn new(
|
||||||
view: AnyView,
|
view_id: EntityId,
|
||||||
state: Rc<Cell<ScrollbarState>>,
|
state: Rc<Cell<ScrollbarState>>,
|
||||||
axis: ScrollbarAxis,
|
axis: ScrollbarAxis,
|
||||||
scroll_handle: impl ScrollHandleOffsetable + 'static,
|
scroll_handle: impl ScrollHandleOffsetable + 'static,
|
||||||
scroll_size: gpui::Size<Pixels>,
|
scroll_size: gpui::Size<Pixels>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
view,
|
view_id,
|
||||||
state,
|
state,
|
||||||
axis,
|
axis,
|
||||||
scroll_size,
|
scroll_size,
|
||||||
|
|
@ -169,13 +169,13 @@ impl Scrollbar {
|
||||||
|
|
||||||
/// Create with vertical and horizontal scrollbar.
|
/// Create with vertical and horizontal scrollbar.
|
||||||
pub fn both(
|
pub fn both(
|
||||||
view: impl Into<AnyView>,
|
view_id: EntityId,
|
||||||
state: Rc<Cell<ScrollbarState>>,
|
state: Rc<Cell<ScrollbarState>>,
|
||||||
scroll_handle: impl ScrollHandleOffsetable + 'static,
|
scroll_handle: impl ScrollHandleOffsetable + 'static,
|
||||||
scroll_size: gpui::Size<Pixels>,
|
scroll_size: gpui::Size<Pixels>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::new(
|
Self::new(
|
||||||
view.into(),
|
view_id,
|
||||||
state,
|
state,
|
||||||
ScrollbarAxis::Both,
|
ScrollbarAxis::Both,
|
||||||
scroll_handle,
|
scroll_handle,
|
||||||
|
|
@ -185,13 +185,13 @@ impl Scrollbar {
|
||||||
|
|
||||||
/// Create with horizontal scrollbar.
|
/// Create with horizontal scrollbar.
|
||||||
pub fn horizontal(
|
pub fn horizontal(
|
||||||
view: impl Into<AnyView>,
|
view_id: EntityId,
|
||||||
state: Rc<Cell<ScrollbarState>>,
|
state: Rc<Cell<ScrollbarState>>,
|
||||||
scroll_handle: impl ScrollHandleOffsetable + 'static,
|
scroll_handle: impl ScrollHandleOffsetable + 'static,
|
||||||
scroll_size: gpui::Size<Pixels>,
|
scroll_size: gpui::Size<Pixels>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::new(
|
Self::new(
|
||||||
view.into(),
|
view_id,
|
||||||
state,
|
state,
|
||||||
ScrollbarAxis::Horizontal,
|
ScrollbarAxis::Horizontal,
|
||||||
scroll_handle,
|
scroll_handle,
|
||||||
|
|
@ -201,13 +201,13 @@ impl Scrollbar {
|
||||||
|
|
||||||
/// Create with vertical scrollbar.
|
/// Create with vertical scrollbar.
|
||||||
pub fn vertical(
|
pub fn vertical(
|
||||||
view: impl Into<AnyView>,
|
view_id: EntityId,
|
||||||
state: Rc<Cell<ScrollbarState>>,
|
state: Rc<Cell<ScrollbarState>>,
|
||||||
scroll_handle: impl ScrollHandleOffsetable + 'static,
|
scroll_handle: impl ScrollHandleOffsetable + 'static,
|
||||||
scroll_size: gpui::Size<Pixels>,
|
scroll_size: gpui::Size<Pixels>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::new(
|
Self::new(
|
||||||
view.into(),
|
view_id,
|
||||||
state,
|
state,
|
||||||
ScrollbarAxis::Vertical,
|
ScrollbarAxis::Vertical,
|
||||||
scroll_handle,
|
scroll_handle,
|
||||||
|
|
@ -217,7 +217,7 @@ impl Scrollbar {
|
||||||
|
|
||||||
/// Create vertical scrollbar for uniform list.
|
/// Create vertical scrollbar for uniform list.
|
||||||
pub fn uniform_scroll(
|
pub fn uniform_scroll(
|
||||||
view: impl Into<AnyView>,
|
view_id: EntityId,
|
||||||
state: Rc<Cell<ScrollbarState>>,
|
state: Rc<Cell<ScrollbarState>>,
|
||||||
scroll_handle: UniformListScrollHandle,
|
scroll_handle: UniformListScrollHandle,
|
||||||
items_count: usize,
|
items_count: usize,
|
||||||
|
|
@ -227,7 +227,7 @@ impl Scrollbar {
|
||||||
let scroll_size = size(px(0.), max_height);
|
let scroll_size = size(px(0.), max_height);
|
||||||
|
|
||||||
Self::new(
|
Self::new(
|
||||||
view.into(),
|
view_id,
|
||||||
state,
|
state,
|
||||||
ScrollbarAxis::Vertical,
|
ScrollbarAxis::Vertical,
|
||||||
scroll_handle,
|
scroll_handle,
|
||||||
|
|
@ -436,7 +436,7 @@ impl Element for Scrollbar {
|
||||||
|
|
||||||
cx.on_mouse_event({
|
cx.on_mouse_event({
|
||||||
let state = self.state.clone();
|
let state = self.state.clone();
|
||||||
let view_id = self.view.entity_id();
|
let view_id = self.view_id;
|
||||||
let scroll_handle = self.scroll_handle.clone();
|
let scroll_handle = self.scroll_handle.clone();
|
||||||
|
|
||||||
move |event: &MouseDownEvent, phase, cx| {
|
move |event: &MouseDownEvent, phase, cx| {
|
||||||
|
|
@ -448,6 +448,7 @@ impl Element for Scrollbar {
|
||||||
let pos = event.position - thumb_bounds.origin;
|
let pos = event.position - thumb_bounds.origin;
|
||||||
|
|
||||||
state.set(state.get().with_drag_pos(axis, pos));
|
state.set(state.get().with_drag_pos(axis, pos));
|
||||||
|
|
||||||
cx.notify(view_id);
|
cx.notify(view_id);
|
||||||
} else {
|
} else {
|
||||||
// click on the scrollbar, jump to the position
|
// click on the scrollbar, jump to the position
|
||||||
|
|
@ -481,18 +482,20 @@ impl Element for Scrollbar {
|
||||||
cx.on_mouse_event({
|
cx.on_mouse_event({
|
||||||
let scroll_handle = self.scroll_handle.clone();
|
let scroll_handle = self.scroll_handle.clone();
|
||||||
let state = self.state.clone();
|
let state = self.state.clone();
|
||||||
let view_id = self.view.entity_id();
|
let view_id = self.view_id;
|
||||||
|
|
||||||
move |event: &MouseMoveEvent, _, cx| {
|
move |event: &MouseMoveEvent, _, cx| {
|
||||||
if bounds.contains(&event.position) {
|
if bounds.contains(&event.position) {
|
||||||
if state.get().hovered_axis != Some(axis) {
|
if state.get().hovered_axis != Some(axis) {
|
||||||
state.set(state.get().with_hovered(Some(axis)));
|
state.set(state.get().with_hovered(Some(axis)));
|
||||||
|
|
||||||
cx.notify(view_id);
|
cx.notify(view_id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if state.get().hovered_axis == Some(axis) {
|
if state.get().hovered_axis == Some(axis) {
|
||||||
if state.get().hovered_axis.is_some() {
|
if state.get().hovered_axis.is_some() {
|
||||||
state.set(state.get().with_hovered(None));
|
state.set(state.get().with_hovered(None));
|
||||||
|
|
||||||
cx.notify(view_id);
|
cx.notify(view_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -541,12 +544,13 @@ impl Element for Scrollbar {
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.on_mouse_event({
|
cx.on_mouse_event({
|
||||||
let view_id = self.view.entity_id();
|
let view_id = self.view_id;
|
||||||
let state = self.state.clone();
|
let state = self.state.clone();
|
||||||
|
|
||||||
move |_event: &MouseUpEvent, phase, cx| {
|
move |_event: &MouseUpEvent, phase, cx| {
|
||||||
if phase.bubble() {
|
if phase.bubble() {
|
||||||
state.set(state.get().with_unset_drag_pos());
|
state.set(state.get().with_unset_drag_pos());
|
||||||
|
|
||||||
cx.notify(view_id);
|
cx.notify(view_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use crate::{
|
||||||
theme::{ActiveTheme, Colorize},
|
theme::{ActiveTheme, Colorize},
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, px, rems, AnyView, Axis, Div, Element, Fill, FocusHandle, Pixels, Styled, WindowContext,
|
div, px, rems, Axis, Div, Element, EntityId, Fill, FocusHandle, Pixels, Styled, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Returns a `Div` as horizontal flex layout.
|
/// Returns a `Div` as horizontal flex layout.
|
||||||
|
|
@ -103,11 +103,11 @@ pub trait StyledExt: Styled + Sized {
|
||||||
/// Wraps the element in a ScrollView.
|
/// Wraps the element in a ScrollView.
|
||||||
///
|
///
|
||||||
/// Current this is only have a vertical scrollbar.
|
/// Current this is only have a vertical scrollbar.
|
||||||
fn scrollable(self, view: impl Into<AnyView>, axis: ScrollbarAxis) -> Scrollable<Self>
|
fn scrollable(self, view_id: EntityId, axis: ScrollbarAxis) -> Scrollable<Self>
|
||||||
where
|
where
|
||||||
Self: Element,
|
Self: Element,
|
||||||
{
|
{
|
||||||
Scrollable::new(self, view, axis)
|
Scrollable::new(view_id, self, axis)
|
||||||
}
|
}
|
||||||
|
|
||||||
font_weight!(font_thin, THIN);
|
font_weight!(font_thin, THIN);
|
||||||
|
|
@ -313,3 +313,27 @@ impl AxisExt for Axis {
|
||||||
self == Axis::Vertical
|
self == Axis::Vertical
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub enum Placement {
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Placement {
|
||||||
|
pub fn is_horizontal(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Placement::Top | Placement::Bottom => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_vertical(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Placement::Left | Placement::Right => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, canvas, div, prelude::FluentBuilder, px, uniform_list, AppContext, Bounds, Div,
|
actions, canvas, div, prelude::FluentBuilder, px, uniform_list, AppContext, Bounds, Div,
|
||||||
DragMoveEvent, EntityId, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
|
DragMoveEvent, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
|
||||||
IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point, Render, ScrollHandle,
|
IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point, Render, ScrollHandle,
|
||||||
SharedString, StatefulInteractiveElement as _, Styled, UniformListScrollHandle, ViewContext,
|
SharedString, StatefulInteractiveElement as _, Styled, UniformListScrollHandle, ViewContext,
|
||||||
VisualContext as _, WindowContext,
|
VisualContext as _, WindowContext,
|
||||||
|
|
@ -339,7 +339,6 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_scrollbar(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
|
fn render_scrollbar(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
|
||||||
let view = cx.view().clone();
|
|
||||||
let state = self.scrollbar_state.clone();
|
let state = self.scrollbar_state.clone();
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
|
|
@ -350,7 +349,7 @@ where
|
||||||
.right_0()
|
.right_0()
|
||||||
.bottom_0()
|
.bottom_0()
|
||||||
.child(Scrollbar::uniform_scroll(
|
.child(Scrollbar::uniform_scroll(
|
||||||
view,
|
cx.view().entity_id(),
|
||||||
state,
|
state,
|
||||||
self.vertical_scroll_handle.clone(),
|
self.vertical_scroll_handle.clone(),
|
||||||
self.delegate.rows_count(),
|
self.delegate.rows_count(),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue