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:
parent
b934ff76e6
commit
70bc526fbb
2 changed files with 69 additions and 158 deletions
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue