Rewrite Popover (#7)
https://github.com/huacnlee/gpui-app/assets/5518/dfe0ba05-ecb7-4604-a057-56daed53c16c
This commit is contained in:
parent
709ee29fe0
commit
93779164f0
5 changed files with 348 additions and 248 deletions
|
|
@ -60,7 +60,6 @@ There have a part of UI components from [Zed](https://github.com/zed-industries/
|
||||||
|
|
||||||
- title_bar
|
- title_bar
|
||||||
- picker
|
- picker
|
||||||
- popover (based on popup_menu)
|
|
||||||
|
|
||||||
> I think we can discuss them with Zed team to change the license to MIT or Apache License for share to community.
|
> I think we can discuss them with Zed team to change the license to MIT or Apache License for share to community.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,73 +1,147 @@
|
||||||
use gpui::{div, px, Element, IntoElement, ParentElement as _, Render, Styled as _, ViewContext};
|
use gpui::{
|
||||||
|
div, px, AnchorCorner, AppContext, DismissEvent, Element, EventEmitter, FocusHandle,
|
||||||
|
FocusableView, IntoElement, MouseButton, ParentElement as _, Render, Styled as _, View,
|
||||||
|
ViewContext, VisualContext, WindowContext,
|
||||||
|
};
|
||||||
use ui::{
|
use ui::{
|
||||||
button::{Button, ButtonSize},
|
button::{Button, ButtonSize},
|
||||||
divider::Divider,
|
divider::Divider,
|
||||||
h_flex,
|
h_flex,
|
||||||
|
input::TextInput,
|
||||||
popover::{Popover, PopoverContent},
|
popover::{Popover, PopoverContent},
|
||||||
v_flex,
|
v_flex, Clickable,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::story_case;
|
use crate::story_case;
|
||||||
|
|
||||||
pub struct PopoverStory {}
|
struct Form {
|
||||||
|
input1: View<TextInput>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Form {
|
||||||
|
fn new(cx: &mut WindowContext) -> View<Self> {
|
||||||
|
cx.new_view(|cx| Self {
|
||||||
|
input1: cx.new_view(|cx| TextInput::new(cx)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FocusableView for Form {
|
||||||
|
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||||
|
self.input1.focus_handle(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<DismissEvent> for Form {}
|
||||||
|
|
||||||
|
impl Render for Form {
|
||||||
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
v_flex()
|
||||||
|
.gap_4()
|
||||||
|
.size_full()
|
||||||
|
.child("This is a form container.")
|
||||||
|
.child(self.input1.clone())
|
||||||
|
.child(
|
||||||
|
Button::primary("submit", "Submit")
|
||||||
|
.on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PopoverStory {
|
||||||
|
form: View<Form>,
|
||||||
|
}
|
||||||
|
|
||||||
impl PopoverStory {
|
impl PopoverStory {
|
||||||
pub fn new(_: &mut ViewContext<Self>) -> Self {
|
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||||
Self {}
|
let form = Form::new(cx);
|
||||||
|
Self { form }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for PopoverStory {
|
impl Render for PopoverStory {
|
||||||
fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
let form = self.form.clone();
|
||||||
|
|
||||||
story_case(
|
story_case(
|
||||||
"Popover",
|
"Popover",
|
||||||
"Displays rich content in a portal, triggered by a button.",
|
"Displays rich content in a portal, triggered by a button.",
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
h_flex().items_center().justify_between().child(
|
h_flex()
|
||||||
v_flex().gap_4().child(
|
.items_center()
|
||||||
Popover::new("info-top-left")
|
.justify_between()
|
||||||
.trigger(Button::new("info", "Top Left").width(px(300.)))
|
.child(
|
||||||
.content(|cx| {
|
v_flex().gap_4().child(
|
||||||
PopoverContent::new(|_| {
|
Popover::new("info-top-left")
|
||||||
v_flex().gap_4().child("Hello, this is a Popover.")
|
.trigger(Button::new("info-top-left", "Top Left"))
|
||||||
|
.content(|cx| {
|
||||||
|
PopoverContent::new(cx, |_| {
|
||||||
|
v_flex()
|
||||||
|
.gap_4()
|
||||||
|
.child("Hello, this is a Popover.")
|
||||||
|
.child(Divider::horizontal())
|
||||||
|
.child(
|
||||||
|
Button::new("info1", "Yes")
|
||||||
|
.width(px(80.))
|
||||||
|
.size(ButtonSize::Small),
|
||||||
|
)
|
||||||
|
.into_any()
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Popover::new("info-top-right")
|
||||||
|
.anchor(AnchorCorner::TopRight)
|
||||||
|
.trigger(Button::new("info-top-right", "Top Right"))
|
||||||
|
.content(|cx| {
|
||||||
|
PopoverContent::new(cx, |_| {
|
||||||
|
v_flex()
|
||||||
|
.gap_4()
|
||||||
|
.child("Hello, this is a Popover on the Top Right.")
|
||||||
.child(Divider::horizontal())
|
.child(Divider::horizontal())
|
||||||
.child(
|
.child(
|
||||||
Button::new("info1", "Yes")
|
Button::new("info1", "Yes")
|
||||||
.width(px(80.))
|
.width(px(80.))
|
||||||
.size(ButtonSize::Small)
|
.size(ButtonSize::Small),
|
||||||
).into_any()
|
)
|
||||||
},
|
.into_any()
|
||||||
cx,
|
})
|
||||||
)
|
}),
|
||||||
})
|
),
|
||||||
).child("This is background text, should be covered by Popover.")
|
|
||||||
).child(
|
|
||||||
Popover::new("info-top-right")
|
|
||||||
.trigger(Button::new("info", "Top Right").width(px(300.)))
|
|
||||||
.content(|cx| {
|
|
||||||
PopoverContent::new(|_|
|
|
||||||
"Hello, this is a Popover.\nYou can click outside to dissmis.".into_any(),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div().absolute().bottom_4().left_0().w_full().h_10().child(
|
div().absolute().bottom_4().left_0().w_full().h_10().child(
|
||||||
h_flex().items_center().justify_between().child(
|
h_flex()
|
||||||
Popover::new("info-bottom-left")
|
.items_center()
|
||||||
.trigger(Button::new("pop", "Bottom Left").width(px(300.)))
|
.justify_between()
|
||||||
.content(|cx| {
|
.child(
|
||||||
PopoverContent::new(|_| "这是另外一个 Popover。\n你可以点击外部来关闭。\nThis popover has position bottom_4().left_0().w_full().h_10().".into_any(), cx)
|
Popover::new("info-bottom-left")
|
||||||
})).child(
|
.anchor(AnchorCorner::BottomLeft)
|
||||||
Popover::new("info-bottom-right")
|
.trigger(Button::new("pop", "Popup with Form").width(px(300.)))
|
||||||
.trigger(Button::new("pop", "Bottom Right").width(px(300.)))
|
.content(move |_| form.clone()),
|
||||||
.content(|cx| {
|
)
|
||||||
PopoverContent::new(|_| "这是另外一个 Popover。\n你可以点击外部来关闭。\nThis popover has position bottom_4().left_0().w_full().h_10().".into_any(), cx)
|
.child(
|
||||||
}))
|
Popover::new("info-bottom-right")
|
||||||
|
.anchor(AnchorCorner::BottomRight)
|
||||||
|
.mouse_button(MouseButton::Right)
|
||||||
|
.trigger(Button::new("pop", "Mouse Right Click").width(px(300.)))
|
||||||
|
.content(|cx| {
|
||||||
|
PopoverContent::new(cx, |_| {
|
||||||
|
v_flex()
|
||||||
|
.gap_4()
|
||||||
|
.child("Hello, this is a Popover on the Bottom Right.")
|
||||||
|
.child(Divider::horizontal())
|
||||||
|
.child(
|
||||||
|
Button::new("info1", "Yes")
|
||||||
|
.width(px(80.))
|
||||||
|
.size(ButtonSize::Small),
|
||||||
|
)
|
||||||
|
.into_any()
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,18 @@ impl Button {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn primary(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
|
||||||
|
Self::new(id, label).style(ButtonStyle::Primary)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn danger(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
|
||||||
|
Self::new(id, label).style(ButtonStyle::Danger)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn small(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
|
||||||
|
Self::new(id, label).size(ButtonSize::Small)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
|
pub fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
|
||||||
self.width = Some(width.into());
|
self.width = Some(width.into());
|
||||||
self
|
self
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,17 @@
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use crate::theme::ActiveTheme;
|
use crate::{theme::ActiveTheme, Selectable, StyledExt as _};
|
||||||
use crate::{Clickable, Selectable, StyledExt};
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, anchored, deferred, div, prelude::FluentBuilder as _, AnchorCorner, AnyElement,
|
actions, anchored, deferred, div, prelude::FluentBuilder, AnchorCorner, AnyElement, AppContext,
|
||||||
AppContext, Bounds, DismissEvent, Element, ElementId, EventEmitter, FocusHandle, FocusableView,
|
Bounds, DismissEvent, DispatchPhase, Element, ElementId, EventEmitter, FocusHandle,
|
||||||
HitboxId, InteractiveElement, IntoElement, LayoutId, ManagedView, ParentElement, Pixels,
|
FocusableView, GlobalElementId, Hitbox, InteractiveElement, IntoElement, LayoutId, ManagedView,
|
||||||
Render, Style, Styled, View, ViewContext, VisualContext, WindowContext,
|
MouseButton, MouseDownEvent, ParentElement as _, Pixels, Point, Render, Style, Styled as _,
|
||||||
|
View, ViewContext, VisualContext, WindowContext,
|
||||||
};
|
};
|
||||||
use gpui::{point, px, DispatchPhase, MouseDownEvent, Point};
|
|
||||||
|
|
||||||
actions!(popover, [Dismiss]);
|
actions!(popover, [Open, Dismiss]);
|
||||||
|
|
||||||
pub trait Triggerable: IntoElement + Clickable + Selectable + 'static {}
|
pub fn init(_cx: &AppContext) {}
|
||||||
impl<T: IntoElement + Clickable + Selectable + 'static> Triggerable for T {}
|
|
||||||
|
|
||||||
pub fn init(_cx: &mut AppContext) {}
|
|
||||||
|
|
||||||
pub struct PopoverContent {
|
pub struct PopoverContent {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
|
|
@ -23,7 +19,7 @@ pub struct PopoverContent {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PopoverContent {
|
impl PopoverContent {
|
||||||
pub fn new<B>(content: B, cx: &mut WindowContext) -> View<Self>
|
pub fn new<B>(cx: &mut WindowContext, content: B) -> View<Self>
|
||||||
where
|
where
|
||||||
B: Fn(&mut WindowContext) -> AnyElement + 'static,
|
B: Fn(&mut WindowContext) -> AnyElement + 'static,
|
||||||
{
|
{
|
||||||
|
|
@ -47,102 +43,72 @@ impl FocusableView for PopoverContent {
|
||||||
|
|
||||||
impl Render for PopoverContent {
|
impl Render for PopoverContent {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
div()
|
div().child(self.content.clone()(cx))
|
||||||
.id("test")
|
|
||||||
// .on_action(cx.listener(Self::dissmiss))
|
|
||||||
.absolute()
|
|
||||||
.mt_2()
|
|
||||||
.elevation_2(cx)
|
|
||||||
.bg(cx.theme().popover)
|
|
||||||
.border_1()
|
|
||||||
.border_color(cx.theme().border)
|
|
||||||
.p_4()
|
|
||||||
.max_w_128()
|
|
||||||
.w_80()
|
|
||||||
.occlude()
|
|
||||||
.child(self.content.clone()(cx))
|
|
||||||
.on_mouse_down_out(cx.listener(|_, _, cx| cx.emit(DismissEvent)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Popover<M: ManagedView> {
|
pub struct Popover<M: ManagedView> {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
anchor: AnchorCorner,
|
anchor: AnchorCorner,
|
||||||
trigger_builder: Option<
|
trigger: Option<Box<dyn FnOnce(&WindowContext) -> AnyElement + 'static>>,
|
||||||
Box<
|
content: Option<Rc<dyn Fn(&mut WindowContext) -> View<M> + 'static>>,
|
||||||
dyn FnOnce(
|
mouse_button: MouseButton,
|
||||||
Rc<RefCell<Option<View<M>>>>,
|
|
||||||
Option<Rc<dyn Fn(&mut WindowContext) -> View<M> + 'static>>,
|
|
||||||
) -> AnyElement
|
|
||||||
+ 'static,
|
|
||||||
>,
|
|
||||||
>,
|
|
||||||
content_builder: Option<Rc<dyn Fn(&mut WindowContext) -> View<M> + 'static>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: ManagedView> Popover<M> {
|
impl<M> Popover<M>
|
||||||
|
where
|
||||||
|
M: ManagedView,
|
||||||
|
{
|
||||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
trigger_builder: None,
|
|
||||||
content_builder: None,
|
|
||||||
anchor: AnchorCorner::TopLeft,
|
anchor: AnchorCorner::TopLeft,
|
||||||
|
trigger: None,
|
||||||
|
content: None,
|
||||||
|
mouse_button: MouseButton::Left,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trigger<T: Triggerable>(mut self, trigger: T) -> Self {
|
|
||||||
self.trigger_builder = Some(Box::new(|popover, builder| {
|
|
||||||
let open = popover.borrow_mut().is_some();
|
|
||||||
trigger
|
|
||||||
.selected(open)
|
|
||||||
.when_some(builder, |this, builder| {
|
|
||||||
this.on_click(move |_, cx| Self::show_popover(&builder, &popover, cx))
|
|
||||||
})
|
|
||||||
.into_any_element()
|
|
||||||
}));
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn content(
|
|
||||||
mut self,
|
|
||||||
content_builder: impl Fn(&mut WindowContext) -> View<M> + 'static,
|
|
||||||
) -> Self {
|
|
||||||
self.content_builder = Some(Rc::new(content_builder));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// anchor defines which corner of the menu to anchor to the attachment point
|
|
||||||
/// (by default the cursor position, but see attach)
|
|
||||||
pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
|
pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
|
||||||
self.anchor = anchor;
|
self.anchor = anchor;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_popover(
|
/// Set the mouse button to trigger the popover, default is `MouseButton::Left`.
|
||||||
builder: &Rc<dyn Fn(&mut WindowContext) -> View<M> + 'static>,
|
pub fn mouse_button(mut self, mouse_button: MouseButton) -> Self {
|
||||||
popover: &Rc<RefCell<Option<View<M>>>>,
|
self.mouse_button = mouse_button;
|
||||||
cx: &mut WindowContext,
|
self
|
||||||
) {
|
}
|
||||||
let new_popover = (builder)(cx);
|
|
||||||
|
|
||||||
let popover_cloned = popover.clone();
|
pub fn trigger<T>(mut self, trigger: T) -> Self
|
||||||
let prev_focus_handle = cx.focused();
|
where
|
||||||
|
T: Selectable + IntoElement + 'static,
|
||||||
|
{
|
||||||
|
self.trigger = Some(Box::new(|_| trigger.into_any_element()));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
cx.subscribe(&new_popover, move |modal, _: &DismissEvent, cx| {
|
/// Set the content of the popover.
|
||||||
if modal.focus_handle(cx).contains_focused(cx) {
|
///
|
||||||
if let Some(prev_focus_handle) = prev_focus_handle.as_ref() {
|
/// The `content` is a closure that returns an `AnyElement`.
|
||||||
cx.focus(prev_focus_handle);
|
pub fn content<C>(mut self, content: C) -> Self
|
||||||
}
|
where
|
||||||
}
|
C: Fn(&mut WindowContext) -> View<M> + 'static,
|
||||||
*popover_cloned.borrow_mut() = None;
|
{
|
||||||
cx.refresh();
|
self.content = Some(Rc::new(content));
|
||||||
})
|
self
|
||||||
.detach();
|
}
|
||||||
|
|
||||||
cx.focus_view(&new_popover);
|
fn render_trigger(&mut self, cx: &mut WindowContext) -> impl IntoElement {
|
||||||
*popover.borrow_mut() = Some(new_popover);
|
let base = div().id("popover-trigger");
|
||||||
cx.refresh();
|
|
||||||
|
if self.trigger.is_none() {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
let trigger = self.trigger.take().unwrap();
|
||||||
|
|
||||||
|
base.child((trigger)(cx)).into_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolved_corner(&self, bounds: Bounds<Pixels>) -> Point<Pixels> {
|
fn resolved_corner(&self, bounds: Bounds<Pixels>) -> Point<Pixels> {
|
||||||
|
|
@ -155,45 +121,27 @@ impl<M: ManagedView> Popover<M> {
|
||||||
.corner(bounds)
|
.corner(bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolved_offset(&self, _cx: &WindowContext) -> Point<Pixels> {
|
fn with_element_state<R>(
|
||||||
let offset = px(0.);
|
&mut self,
|
||||||
match self.anchor {
|
id: &GlobalElementId,
|
||||||
AnchorCorner::TopRight | AnchorCorner::BottomRight => point(offset, px(0.)),
|
cx: &mut WindowContext,
|
||||||
AnchorCorner::TopLeft | AnchorCorner::BottomLeft => point(-offset, px(0.)),
|
f: impl FnOnce(&mut Self, &mut PopoverElementState<M>, &mut WindowContext) -> R,
|
||||||
}
|
) -> R {
|
||||||
|
cx.with_optional_element_state::<PopoverElementState<M>, _>(
|
||||||
|
Some(id),
|
||||||
|
|element_state, cx| {
|
||||||
|
let mut element_state = element_state.unwrap().unwrap_or_default();
|
||||||
|
let result = f(self, &mut element_state, cx);
|
||||||
|
(result, Some(element_state))
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PopoverElementState<M> {
|
impl<M> IntoElement for Popover<M>
|
||||||
trigger_bounds: Option<Bounds<Pixels>>,
|
where
|
||||||
popover: Rc<RefCell<Option<View<M>>>>,
|
M: ManagedView,
|
||||||
}
|
{
|
||||||
|
|
||||||
impl<M> Clone for PopoverElementState<M> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
popover: Rc::clone(&self.popover),
|
|
||||||
trigger_bounds: self.trigger_bounds,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<M> Default for PopoverElementState<M> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
popover: Rc::default(),
|
|
||||||
trigger_bounds: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PopoverFrameState {
|
|
||||||
trigger_layout_id: Option<LayoutId>,
|
|
||||||
trigger_element: Option<AnyElement>,
|
|
||||||
popover_element: Option<AnyElement>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<M: ManagedView> IntoElement for Popover<M> {
|
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
|
|
@ -201,9 +149,37 @@ impl<M: ManagedView> IntoElement for Popover<M> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct PopoverElementState<M> {
|
||||||
|
trigger_layout_id: Option<LayoutId>,
|
||||||
|
popover_element: Option<AnyElement>,
|
||||||
|
trigger_element: Option<AnyElement>,
|
||||||
|
content_view: Rc<RefCell<Option<View<M>>>>,
|
||||||
|
/// Trigger bounds for positioning the popover.
|
||||||
|
trigger_bounds: Option<Bounds<Pixels>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> Default for PopoverElementState<M> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
trigger_layout_id: None,
|
||||||
|
popover_element: None,
|
||||||
|
trigger_element: None,
|
||||||
|
content_view: Rc::new(RefCell::new(None)),
|
||||||
|
trigger_bounds: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PrepaintState {
|
||||||
|
hitbox: Hitbox,
|
||||||
|
/// Trigger bounds for limit a rect to handle mouse click.
|
||||||
|
trigger_bounds: Option<Bounds<Pixels>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl<M: ManagedView> Element for Popover<M> {
|
impl<M: ManagedView> Element for Popover<M> {
|
||||||
type RequestLayoutState = PopoverFrameState;
|
type RequestLayoutState = PopoverElementState<M>;
|
||||||
type PrepaintState = Option<HitboxId>;
|
|
||||||
|
type PrepaintState = PrepaintState;
|
||||||
|
|
||||||
fn id(&self) -> Option<ElementId> {
|
fn id(&self) -> Option<ElementId> {
|
||||||
Some(self.id.clone())
|
Some(self.id.clone())
|
||||||
|
|
@ -211,109 +187,150 @@ impl<M: ManagedView> Element for Popover<M> {
|
||||||
|
|
||||||
fn request_layout(
|
fn request_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
global_id: Option<&gpui::GlobalElementId>,
|
id: Option<&gpui::GlobalElementId>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||||
cx.with_element_state(
|
self.with_element_state(id.unwrap(), cx, |this, element_state, cx| {
|
||||||
global_id.unwrap(),
|
let mut trigger_element = this.render_trigger(cx).into_any_element();
|
||||||
|element_state: Option<PopoverElementState<M>>, cx| {
|
let trigger_layout_id = trigger_element.request_layout(cx);
|
||||||
let element_state = element_state.unwrap_or_default();
|
|
||||||
|
|
||||||
let mut popover_layout_id = None;
|
let mut popover_layout_id = None;
|
||||||
let popover_element = element_state.popover.borrow_mut().as_mut().map(|popover| {
|
let mut popover_element = None;
|
||||||
let mut anchored = anchored().snap_to_window().anchor(self.anchor);
|
|
||||||
if let Some(trigger_bounds) = element_state.trigger_bounds {
|
|
||||||
anchored = anchored.position(
|
|
||||||
self.resolved_corner(trigger_bounds) + self.resolved_offset(cx),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let mut element = deferred(anchored.child(popover.clone()))
|
|
||||||
.with_priority(1)
|
|
||||||
.into_any();
|
|
||||||
|
|
||||||
popover_layout_id = Some(element.request_layout(cx));
|
if let Some(content_view) = element_state.content_view.borrow_mut().as_mut() {
|
||||||
element
|
let content_view_mut = element_state.content_view.clone();
|
||||||
});
|
|
||||||
|
|
||||||
let mut trigger_element = self.trigger_builder.take().map(|builder| {
|
let mut anchored = anchored().snap_to_window().anchor(this.anchor);
|
||||||
builder(element_state.popover.clone(), self.content_builder.clone())
|
if let Some(trigger_bounds) = element_state.trigger_bounds {
|
||||||
});
|
anchored = anchored.position(this.resolved_corner(trigger_bounds));
|
||||||
|
}
|
||||||
|
|
||||||
let trigger_layout_id = trigger_element
|
let mut element = deferred(
|
||||||
.as_mut()
|
anchored.child(
|
||||||
.map(|trigger_element| trigger_element.request_layout(cx));
|
div()
|
||||||
|
.occlude()
|
||||||
let layout_id = cx.request_layout(
|
.map(|d| match this.anchor {
|
||||||
Style::default(),
|
AnchorCorner::TopLeft | AnchorCorner::TopRight => d.mt_2(),
|
||||||
popover_layout_id.into_iter().chain(trigger_layout_id),
|
AnchorCorner::BottomLeft | AnchorCorner::BottomRight => d.mb_2(),
|
||||||
);
|
})
|
||||||
|
.elevation_2(cx)
|
||||||
(
|
.bg(cx.theme().popover)
|
||||||
(
|
.border_1()
|
||||||
layout_id,
|
.border_color(cx.theme().border)
|
||||||
PopoverFrameState {
|
.p_4()
|
||||||
trigger_layout_id,
|
.max_w_128()
|
||||||
trigger_element,
|
.occlude()
|
||||||
popover_element,
|
.on_mouse_down_out(move |_, cx| {
|
||||||
},
|
// Update the element_state.content_view to `None`,
|
||||||
|
// so that the `paint`` method will not paint it.
|
||||||
|
*content_view_mut.borrow_mut() = None;
|
||||||
|
cx.refresh();
|
||||||
|
})
|
||||||
|
.child(content_view.clone()),
|
||||||
),
|
),
|
||||||
element_state,
|
|
||||||
)
|
)
|
||||||
},
|
.with_priority(1)
|
||||||
)
|
.into_any();
|
||||||
|
|
||||||
|
popover_layout_id = Some(element.request_layout(cx));
|
||||||
|
popover_element = Some(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
let layout_id = cx.request_layout(
|
||||||
|
Style::default(),
|
||||||
|
popover_layout_id.into_iter().chain(Some(trigger_layout_id)),
|
||||||
|
);
|
||||||
|
|
||||||
|
(
|
||||||
|
layout_id,
|
||||||
|
PopoverElementState {
|
||||||
|
trigger_layout_id: Some(trigger_layout_id),
|
||||||
|
popover_element: popover_element,
|
||||||
|
trigger_element: Some(trigger_element),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepaint(
|
fn prepaint(
|
||||||
&mut self,
|
&mut self,
|
||||||
global_id: Option<&gpui::GlobalElementId>,
|
_id: Option<&gpui::GlobalElementId>,
|
||||||
_: gpui::Bounds<gpui::Pixels>,
|
_bounds: gpui::Bounds<gpui::Pixels>,
|
||||||
request_layout: &mut Self::RequestLayoutState,
|
request_layout: &mut Self::RequestLayoutState,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Self::PrepaintState {
|
) -> Self::PrepaintState {
|
||||||
if let Some(element) = &mut request_layout.trigger_element {
|
if let Some(element) = &mut request_layout.trigger_element {
|
||||||
element.prepaint(cx);
|
element.prepaint(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(element) = &mut request_layout.popover_element {
|
if let Some(element) = &mut request_layout.popover_element {
|
||||||
element.prepaint(cx);
|
element.prepaint(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
request_layout.trigger_layout_id.map(|layout_id| {
|
let trigger_bounds = request_layout
|
||||||
let bounds = cx.layout_bounds(layout_id);
|
.trigger_layout_id
|
||||||
cx.with_element_state(global_id.unwrap(), |element_state, _cx| {
|
.map(|id| cx.layout_bounds(id));
|
||||||
let mut element_state: PopoverElementState<M> = element_state.unwrap();
|
let hitbox = cx.insert_hitbox(trigger_bounds.unwrap_or_default(), false);
|
||||||
element_state.trigger_bounds = Some(bounds);
|
|
||||||
((), element_state)
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.insert_hitbox(bounds, false).id
|
PrepaintState {
|
||||||
})
|
trigger_bounds,
|
||||||
|
hitbox,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: Option<&gpui::GlobalElementId>,
|
id: Option<&gpui::GlobalElementId>,
|
||||||
_: gpui::Bounds<gpui::Pixels>,
|
_bounds: gpui::Bounds<gpui::Pixels>,
|
||||||
request_layout: &mut Self::RequestLayoutState,
|
request_layout: &mut Self::RequestLayoutState,
|
||||||
trigger_hitbox: &mut Option<HitboxId>,
|
prepaint: &mut Self::PrepaintState,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) {
|
) {
|
||||||
if let Some(element) = &mut request_layout.trigger_element {
|
self.with_element_state(id.unwrap(), cx, |this, element_state, cx| {
|
||||||
element.paint(cx);
|
element_state.trigger_bounds = prepaint.trigger_bounds;
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(element) = &mut request_layout.popover_element {
|
if let Some(mut element) = request_layout.trigger_element.take() {
|
||||||
element.paint(cx);
|
element.paint(cx);
|
||||||
|
|
||||||
if let Some(hitbox) = *trigger_hitbox {
|
|
||||||
// Mouse-downing outside the menu dismisses it, so we don't
|
|
||||||
// want a click on the toggle to re-open it.
|
|
||||||
cx.on_mouse_event(move |_: &MouseDownEvent, phase, cx| {
|
|
||||||
if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
|
|
||||||
cx.stop_propagation()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if let Some(mut element) = request_layout.popover_element.take() {
|
||||||
|
element.paint(cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(content_build) = this.content.take() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let old_content_view = element_state.content_view.clone();
|
||||||
|
let hitbox_id = prepaint.hitbox.id;
|
||||||
|
let mouse_button = this.mouse_button;
|
||||||
|
// When mouse click down in the trigger bounds, open the popover.
|
||||||
|
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
||||||
|
if phase == DispatchPhase::Bubble
|
||||||
|
&& event.button == mouse_button
|
||||||
|
&& hitbox_id.is_hovered(cx)
|
||||||
|
{
|
||||||
|
let new_content_view = (content_build)(cx);
|
||||||
|
let old_content_view1 = old_content_view.clone();
|
||||||
|
|
||||||
|
let previous_focus_handle = cx.focused();
|
||||||
|
cx.subscribe(&new_content_view, move |modal, _: &DismissEvent, cx| {
|
||||||
|
if modal.focus_handle(cx).contains_focused(cx) {
|
||||||
|
if let Some(previous_focus_handle) = previous_focus_handle.as_ref() {
|
||||||
|
cx.focus(previous_focus_handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*old_content_view1.borrow_mut() = None;
|
||||||
|
cx.refresh();
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
cx.focus_view(&new_content_view);
|
||||||
|
*old_content_view.borrow_mut() = Some(new_content_view);
|
||||||
|
cx.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,13 +38,11 @@ impl ElevationIndex {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn elevated<E: Styled>(this: E, cx: &mut WindowContext, index: ElevationIndex) -> E {
|
fn elevated<E: Styled>(this: E, cx: &WindowContext, index: ElevationIndex) -> E {
|
||||||
let theme = cx.theme();
|
this.bg(cx.theme().popover)
|
||||||
|
|
||||||
this.bg(theme.popover)
|
|
||||||
.rounded(px(8.))
|
.rounded(px(8.))
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(theme.border)
|
.border_color(cx.theme().border)
|
||||||
.shadow(index.shadow())
|
.shadow(index.shadow())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,17 +63,17 @@ pub trait StyledExt: Styled + Sized {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Located above the app background
|
/// Located above the app background
|
||||||
fn elevation_1(self, cx: &mut WindowContext) -> Self {
|
fn elevation_1(self, cx: &WindowContext) -> Self {
|
||||||
elevated(self, cx, ElevationIndex::Surface)
|
elevated(self, cx, ElevationIndex::Surface)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Appear above most UI elements
|
/// Appear above most UI elements
|
||||||
fn elevation_2(self, cx: &mut WindowContext) -> Self {
|
fn elevation_2(self, cx: &WindowContext) -> Self {
|
||||||
elevated(self, cx, ElevationIndex::PopoverSurface)
|
elevated(self, cx, ElevationIndex::PopoverSurface)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Above all other UI elements and are located above the wash layer
|
// Above all other UI elements and are located above the wash layer
|
||||||
fn elevation_3(self, cx: &mut WindowContext) -> Self {
|
fn elevation_3(self, cx: &WindowContext) -> Self {
|
||||||
elevated(self, cx, ElevationIndex::ModalSurface)
|
elevated(self, cx, ElevationIndex::ModalSurface)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue