Fix Dropdown and DatePicker anchor to snap to window. (#139)

<img width="786" alt="image"
src="https://github.com/user-attachments/assets/c40fab1d-2cbe-4750-b05e-ed42cb43dd3c">
This commit is contained in:
Jason Lee 2024-08-13 18:10:30 +08:00 committed by GitHub
parent b934ff76e6
commit 70bc526fbb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 69 additions and 158 deletions

View file

@ -1,9 +1,9 @@
use gpui::{ use gpui::{
actions, deferred, div, prelude::FluentBuilder, px, rems, AnyElement, AppContext, ClickEvent, actions, anchored, canvas, deferred, div, prelude::FluentBuilder, px, rems, AnyElement,
DismissEvent, Div, Element, ElementId, EventEmitter, FocusHandle, Focusable, FocusableView, AppContext, Bounds, ClickEvent, DismissEvent, ElementId, EventEmitter, FocusHandle,
InteractiveElement, IntoElement, KeyBinding, LayoutId, Length, ParentElement, Render, FocusableView, InteractiveElement, IntoElement, KeyBinding, Length, ParentElement, Pixels,
SharedString, StatefulInteractiveElement, Styled, Task, View, ViewContext, VisualContext, Render, SharedString, StatefulInteractiveElement, Styled, Task, View, ViewContext,
WeakView, WindowContext, VisualContext, WeakView, WindowContext,
}; };
use crate::{ use crate::{
@ -11,7 +11,7 @@ use crate::{
input::ClearButton, input::ClearButton,
list::{self, List, ListDelegate, ListItem}, list::{self, List, ListDelegate, ListItem},
theme::ActiveTheme, theme::ActiveTheme,
Icon, IconName, Sizable, Size, StyleSized, StyledExt, v_flex, Icon, IconName, Sizable, Size, StyleSized, StyledExt,
}; };
actions!(dropdown, [Up, Down, Enter, Escape]); actions!(dropdown, [Up, Down, Enter, Escape]);
@ -222,6 +222,8 @@ pub struct Dropdown<D: DropdownDelegate + 'static> {
empty: Option<Box<dyn Fn(&WindowContext) -> AnyElement + 'static>>, empty: Option<Box<dyn Fn(&WindowContext) -> AnyElement + 'static>>,
width: Length, width: Length,
menu_width: Length, menu_width: Length,
/// Store the bounds of the input
bounds: Bounds<Pixels>,
} }
pub struct SearchableVec<T> { pub struct SearchableVec<T> {
@ -328,6 +330,7 @@ where
empty: None, empty: None,
width: Length::Auto, width: Length::Auto,
menu_width: Length::Auto, menu_width: Length::Auto,
bounds: Bounds::default(),
}; };
this.set_selected_index(selected_index, cx); this.set_selected_index(selected_index, cx);
this this
@ -468,15 +471,6 @@ where
self.set_selected_index(None, cx) self.set_selected_index(None, cx)
} }
fn render_menu_content(&self, cx: &WindowContext) -> impl IntoElement {
div()
.track_focus(&self.list.focus_handle(cx))
.on_mouse_down_out(|_, cx| {
cx.dispatch_action(Box::new(Escape));
})
.map(|this| with_style(this, cx).child(self.list.clone()))
}
fn display_title(&self, cx: &WindowContext) -> impl IntoElement { fn display_title(&self, cx: &WindowContext) -> impl IntoElement {
if let Some(selected_index) = &self.selected_index(cx) { if let Some(selected_index) = &self.selected_index(cx) {
let title = self let title = self
@ -503,16 +497,6 @@ where
} }
} }
fn with_style(d: Focusable<Div>, cx: &WindowContext) -> Focusable<Div> {
d.absolute()
.mt_1p5()
.bg(cx.theme().background)
.border_1()
.border_color(cx.theme().input)
.rounded(px(cx.theme().radius))
.shadow_md()
}
impl<D> Sizable for Dropdown<D> impl<D> Sizable for Dropdown<D>
where where
D: DropdownDelegate + 'static, D: DropdownDelegate + 'static,
@ -541,6 +525,8 @@ where
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let is_focused = self.focus_handle.is_focused(cx); let is_focused = self.focus_handle.is_focused(cx);
let show_clean = self.cleanable && self.selected_index(cx).is_some(); let show_clean = self.cleanable && self.selected_index(cx).is_some();
let view = cx.view().clone();
let bounds = self.bounds;
div() div()
.id(self.id.clone()) .id(self.id.clone())
@ -606,121 +592,44 @@ where
this.child(Icon::new(icon).text_color(cx.theme().muted_foreground)) this.child(Icon::new(icon).text_color(cx.theme().muted_foreground))
}), }),
)
.child(
canvas(
move |bounds, cx| view.update(cx, |r, _| r.bounds = bounds),
|_, _, _| {},
)
.absolute()
.size_full(),
), ),
) )
.child( .when(self.open, |this| {
div() this.child(
.absolute() deferred(
.overflow_hidden() anchored().snap_to_window().child(
.map(|this| match self.menu_width { div()
Length::Auto => this, .map(|this| match self.menu_width {
Length::Definite(l) => this.w(l), Length::Auto => this.w(bounds.size.width),
}) Length::Definite(w) => this.w(w),
.child(DropdownMenuElement { })
id: "dropdown-menu".into(), .child(
dropdown: cx.view().clone(), v_flex()
}), .track_focus(&self.list.focus_handle(cx))
) .occlude()
} .mt_1p5()
} .bg(cx.theme().background)
.border_1()
struct DropdownMenuElement<D: DropdownDelegate + 'static> { .border_color(cx.theme().border)
id: ElementId, .rounded(px(cx.theme().radius))
dropdown: View<Dropdown<D>>, .shadow_md()
} .on_mouse_down_out(|_, cx| {
cx.dispatch_action(Box::new(Escape));
#[derive(Default)] })
struct DropdownMenuElementState { .child(self.list.clone()),
menu_element: Option<AnyElement>, ),
layout_id: Option<LayoutId>, ),
} )
.with_priority(1),
impl<D> IntoElement for DropdownMenuElement<D>
where
D: DropdownDelegate + 'static,
{
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
impl<D> Element for DropdownMenuElement<D>
where
D: DropdownDelegate + 'static,
{
type RequestLayoutState = DropdownMenuElementState;
type PrepaintState = ();
fn id(&self) -> Option<ElementId> {
Some(self.id.clone())
}
fn request_layout(
&mut self,
id: Option<&gpui::GlobalElementId>,
cx: &mut gpui::WindowContext,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
cx.with_element_state(
id.unwrap(),
|element_state: Option<DropdownMenuElementState>, cx| {
let state = element_state.unwrap_or_default();
let menu = self
.dropdown
.read(cx)
.render_menu_content(cx)
.into_any_element();
let mut element = deferred(menu).with_priority(1).into_any();
let layout_id = element.request_layout(cx);
(
(
layout_id,
DropdownMenuElementState {
layout_id: Some(layout_id),
menu_element: Some(element),
},
),
state,
) )
}, })
)
}
fn prepaint(
&mut self,
_: Option<&gpui::GlobalElementId>,
_: gpui::Bounds<gpui::Pixels>,
request_layout: &mut DropdownMenuElementState,
cx: &mut gpui::WindowContext,
) -> Self::PrepaintState {
if self.dropdown.read(cx).open {
if let Some(element) = &mut request_layout.menu_element {
element.prepaint(cx);
}
if let Some(layout_id) = request_layout.layout_id {
let bounds = cx.layout_bounds(layout_id);
cx.insert_hitbox(bounds, false);
}
}
}
fn paint(
&mut self,
_: Option<&gpui::GlobalElementId>,
_: gpui::Bounds<gpui::Pixels>,
request_layout: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
cx: &mut gpui::WindowContext,
) {
if self.dropdown.read(cx).open {
if let Some(element) = &mut request_layout.menu_element {
element.paint(cx);
}
}
} }
} }

View file

@ -1,5 +1,5 @@
use gpui::{ use gpui::{
deferred, div, prelude::FluentBuilder as _, px, AppContext, ElementId, EventEmitter, anchored, deferred, div, prelude::FluentBuilder as _, px, AppContext, ElementId, EventEmitter,
FocusHandle, FocusableView, InteractiveElement as _, KeyBinding, Length, MouseButton, FocusHandle, FocusableView, InteractiveElement as _, KeyBinding, Length, MouseButton,
ParentElement as _, Render, SharedString, StatefulInteractiveElement as _, Styled as _, View, ParentElement as _, Render, SharedString, StatefulInteractiveElement as _, Styled as _, View,
ViewContext, VisualContext as _, ViewContext, VisualContext as _,
@ -214,25 +214,27 @@ impl Render for DatePicker {
.when(self.open, |this| { .when(self.open, |this| {
this.child( this.child(
deferred( deferred(
div() anchored().snap_to_window().child(
.track_focus(&self.focus_handle) div()
.occlude() .track_focus(&self.focus_handle)
.absolute() .occlude()
.mt_2() .absolute()
.overflow_hidden() .mt_2()
.rounded_lg() .overflow_hidden()
.p_3() .rounded_lg()
.w(px(popover_width)) .p_3()
.border_1() .w(px(popover_width))
.border_color(cx.theme().border) .border_1()
.shadow_lg() .border_color(cx.theme().border)
.rounded_lg() .shadow_lg()
.bg(cx.theme().background) .rounded_lg()
.on_mouse_up_out( .bg(cx.theme().background)
MouseButton::Left, .on_mouse_up_out(
cx.listener(|view, _, cx| view.escape(&Escape, cx)), MouseButton::Left,
) cx.listener(|view, _, cx| view.escape(&Escape, cx)),
.child(self.calendar.clone()), )
.child(self.calendar.clone()),
),
) )
.with_priority(2), .with_priority(2),
) )