Incorporate ElementId as part of the Element::id trait method and expose GlobalId (#11101)

We're planning to associate "selection sources" with global element ids
to allow arbitrary UI text to be selected in GPUI. Previously, global
ids were not exposed outside the framework and we entangled management
of the element id stack with element state access. This was more
acceptable when element state was the only place we used global element
ids, but now that we're planning to use them more places, it makes sense
to deal with element identity as a first-class part of the element
system. We now ensure that the stack of element ids which forms the
current global element id is correctly managed in every phase of element
layout and paint and make the global id available to each element
method. In a subsequent PR, we'll use the global element id as part of
implementing arbitrary selection for UI text.

Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
Nathan Sobo 2024-04-28 12:59:21 -07:00 committed by GitHub
parent 8b55494351
commit 39fb1d567d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 825 additions and 497 deletions

View file

@ -2,9 +2,9 @@ use std::{cell::RefCell, rc::Rc};
use gpui::{
anchored, deferred, div, point, prelude::FluentBuilder, px, AnchorCorner, AnyElement, Bounds,
DismissEvent, DispatchPhase, Element, ElementId, HitboxId, InteractiveElement, IntoElement,
LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext,
WindowContext,
DismissEvent, DispatchPhase, Element, ElementId, GlobalElementId, HitboxId, InteractiveElement,
IntoElement, LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View,
VisualContext, WindowContext,
};
use crate::prelude::*;
@ -109,21 +109,6 @@ impl<M: ManagedView> PopoverMenu<M> {
}
})
}
fn with_element_state<R>(
&mut self,
cx: &mut WindowContext,
f: impl FnOnce(&mut Self, &mut PopoverMenuElementState<M>, &mut WindowContext) -> R,
) -> R {
cx.with_element_state::<PopoverMenuElementState<M>, _>(
Some(self.id.clone()),
|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))
},
)
}
}
/// Creates a [`PopoverMenu`]
@ -171,101 +156,118 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
type RequestLayoutState = PopoverMenuFrameState;
type PrepaintState = Option<HitboxId>;
fn id(&self) -> Option<ElementId> {
Some(self.id.clone())
}
fn request_layout(
&mut self,
global_id: Option<&GlobalElementId>,
cx: &mut WindowContext,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
self.with_element_state(cx, |this, element_state, cx| {
let mut menu_layout_id = None;
cx.with_element_state(
global_id.unwrap(),
|element_state: Option<PopoverMenuElementState<M>>, cx| {
let element_state = element_state.unwrap_or_default();
let mut menu_layout_id = None;
let menu_element = element_state.menu.borrow_mut().as_mut().map(|menu| {
let mut anchored = anchored().snap_to_window().anchor(this.anchor);
if let Some(child_bounds) = element_state.child_bounds {
anchored = anchored.position(
this.resolved_attach().corner(child_bounds) + this.resolved_offset(cx),
);
}
let mut element = deferred(anchored.child(div().occlude().child(menu.clone())))
.with_priority(1)
.into_any();
let menu_element = element_state.menu.borrow_mut().as_mut().map(|menu| {
let mut anchored = anchored().snap_to_window().anchor(self.anchor);
if let Some(child_bounds) = element_state.child_bounds {
anchored = anchored.position(
self.resolved_attach().corner(child_bounds) + self.resolved_offset(cx),
);
}
let mut element = deferred(anchored.child(div().occlude().child(menu.clone())))
.with_priority(1)
.into_any();
menu_layout_id = Some(element.request_layout(cx));
element
});
menu_layout_id = Some(element.request_layout(cx));
element
});
let mut child_element = this.child_builder.take().map(|child_builder| {
(child_builder)(element_state.menu.clone(), this.menu_builder.clone())
});
let mut child_element = self.child_builder.take().map(|child_builder| {
(child_builder)(element_state.menu.clone(), self.menu_builder.clone())
});
let child_layout_id = child_element
.as_mut()
.map(|child_element| child_element.request_layout(cx));
let child_layout_id = child_element
.as_mut()
.map(|child_element| child_element.request_layout(cx));
let layout_id = cx.request_layout(
&gpui::Style::default(),
menu_layout_id.into_iter().chain(child_layout_id),
);
let layout_id = cx.request_layout(
&gpui::Style::default(),
menu_layout_id.into_iter().chain(child_layout_id),
);
(
layout_id,
PopoverMenuFrameState {
child_element,
child_layout_id,
menu_element,
},
)
})
(
(
layout_id,
PopoverMenuFrameState {
child_element,
child_layout_id,
menu_element,
},
),
element_state,
)
},
)
}
fn prepaint(
&mut self,
global_id: Option<&GlobalElementId>,
_bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
cx: &mut WindowContext,
) -> Option<HitboxId> {
self.with_element_state(cx, |_this, element_state, cx| {
if let Some(child) = request_layout.child_element.as_mut() {
child.prepaint(cx);
}
if let Some(child) = request_layout.child_element.as_mut() {
child.prepaint(cx);
}
if let Some(menu) = request_layout.menu_element.as_mut() {
menu.prepaint(cx);
}
if let Some(menu) = request_layout.menu_element.as_mut() {
menu.prepaint(cx);
}
request_layout.child_layout_id.map(|layout_id| {
let bounds = cx.layout_bounds(layout_id);
let hitbox_id = request_layout.child_layout_id.map(|layout_id| {
let bounds = cx.layout_bounds(layout_id);
cx.with_element_state(global_id.unwrap(), |element_state, _cx| {
let mut element_state: PopoverMenuElementState<M> = element_state.unwrap();
element_state.child_bounds = Some(bounds);
cx.insert_hitbox(bounds, false).id
})
})
((), element_state)
});
cx.insert_hitbox(bounds, false).id
});
hitbox_id
}
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
_: Bounds<gpui::Pixels>,
request_layout: &mut Self::RequestLayoutState,
child_hitbox: &mut Option<HitboxId>,
cx: &mut WindowContext,
) {
self.with_element_state(cx, |_this, _element_state, cx| {
if let Some(mut child) = request_layout.child_element.take() {
child.paint(cx);
}
if let Some(mut child) = request_layout.child_element.take() {
child.paint(cx);
}
if let Some(mut menu) = request_layout.menu_element.take() {
menu.paint(cx);
if let Some(mut menu) = request_layout.menu_element.take() {
menu.paint(cx);
if let Some(child_hitbox) = *child_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 && child_hitbox.is_hovered(cx) {
cx.stop_propagation()
}
})
}
if let Some(child_hitbox) = *child_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 && child_hitbox.is_hovered(cx) {
cx.stop_propagation()
}
})
}
})
}
}
}

View file

@ -2,8 +2,9 @@ use std::{cell::RefCell, rc::Rc};
use gpui::{
anchored, deferred, div, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase,
Element, ElementId, Hitbox, InteractiveElement, IntoElement, LayoutId, ManagedView,
MouseButton, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext, WindowContext,
Element, ElementId, GlobalElementId, Hitbox, InteractiveElement, IntoElement, LayoutId,
ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext,
WindowContext,
};
pub struct RightClickMenu<M: ManagedView> {
@ -40,11 +41,12 @@ impl<M: ManagedView> RightClickMenu<M> {
fn with_element_state<R>(
&mut self,
global_id: &GlobalElementId,
cx: &mut WindowContext,
f: impl FnOnce(&mut Self, &mut MenuHandleElementState<M>, &mut WindowContext) -> R,
) -> R {
cx.with_element_state::<MenuHandleElementState<M>, _>(
Some(self.id.clone()),
cx.with_optional_element_state::<MenuHandleElementState<M>, _>(
Some(global_id),
|element_state, cx| {
let mut element_state = element_state.unwrap().unwrap_or_default();
let result = f(self, &mut element_state, cx);
@ -103,11 +105,16 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
type RequestLayoutState = RequestLayoutState;
type PrepaintState = PrepaintState;
fn id(&self) -> Option<ElementId> {
Some(self.id.clone())
}
fn request_layout(
&mut self,
id: Option<&GlobalElementId>,
cx: &mut WindowContext,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
self.with_element_state(cx, |this, element_state, cx| {
self.with_element_state(id.unwrap(), cx, |this, element_state, cx| {
let mut menu_layout_id = None;
let menu_element = element_state.menu.borrow_mut().as_mut().map(|menu| {
@ -152,38 +159,38 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
cx: &mut WindowContext,
) -> PrepaintState {
cx.with_element_id(Some(self.id.clone()), |cx| {
let hitbox = cx.insert_hitbox(bounds, false);
let hitbox = cx.insert_hitbox(bounds, false);
if let Some(child) = request_layout.child_element.as_mut() {
child.prepaint(cx);
}
if let Some(child) = request_layout.child_element.as_mut() {
child.prepaint(cx);
}
if let Some(menu) = request_layout.menu_element.as_mut() {
menu.prepaint(cx);
}
if let Some(menu) = request_layout.menu_element.as_mut() {
menu.prepaint(cx);
}
PrepaintState {
hitbox,
child_bounds: request_layout
.child_layout_id
.map(|layout_id| cx.layout_bounds(layout_id)),
}
})
PrepaintState {
hitbox,
child_bounds: request_layout
.child_layout_id
.map(|layout_id| cx.layout_bounds(layout_id)),
}
}
fn paint(
&mut self,
id: Option<&GlobalElementId>,
_bounds: Bounds<gpui::Pixels>,
request_layout: &mut Self::RequestLayoutState,
prepaint_state: &mut Self::PrepaintState,
cx: &mut WindowContext,
) {
self.with_element_state(cx, |this, element_state, cx| {
self.with_element_state(id.unwrap(), cx, |this, element_state, cx| {
if let Some(mut child) = request_layout.child_element.take() {
child.paint(cx);
}