Introduce autoscroll support for elements (#10889)

This pull request introduces the new
`ElementContext::request_autoscroll(bounds)` and
`ElementContext::take_autoscroll()` methods in GPUI. These new APIs
enable container elements such as `List` to change their scroll position
if one of their children requested an autoscroll. We plan to use this in
the revamped assistant.

As a drive-by, we also:

- Renamed `Element::before_layout` to `Element::request_layout`
- Renamed `Element::after_layout` to `Element::prepaint`
- Introduced a new `List::splice_focusable` method to splice focusable
elements into the list, which enables rendering offscreen elements that
are focused.

Release Notes:

- N/A

---------

Co-authored-by: Nathan <nathan@zed.dev>
This commit is contained in:
Antonio Scandurra 2024-04-23 15:14:22 +02:00 committed by GitHub
parent efcd31c254
commit bcbf2f2fd3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 780 additions and 513 deletions

View file

@ -168,10 +168,13 @@ pub struct PopoverMenuFrameState {
}
impl<M: ManagedView> Element for PopoverMenu<M> {
type BeforeLayout = PopoverMenuFrameState;
type AfterLayout = Option<HitboxId>;
type RequestLayoutState = PopoverMenuFrameState;
type PrepaintState = Option<HitboxId>;
fn before_layout(&mut self, cx: &mut ElementContext) -> (gpui::LayoutId, Self::BeforeLayout) {
fn request_layout(
&mut self,
cx: &mut ElementContext,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
self.with_element_state(cx, |this, element_state, cx| {
let mut menu_layout_id = None;
@ -186,7 +189,7 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
.with_priority(1)
.into_any();
menu_layout_id = Some(element.before_layout(cx));
menu_layout_id = Some(element.request_layout(cx));
element
});
@ -196,7 +199,7 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
let child_layout_id = child_element
.as_mut()
.map(|child_element| child_element.before_layout(cx));
.map(|child_element| child_element.request_layout(cx));
let layout_id = cx.request_layout(
&gpui::Style::default(),
@ -214,22 +217,22 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
})
}
fn after_layout(
fn prepaint(
&mut self,
_bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
request_layout: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
) -> Option<HitboxId> {
self.with_element_state(cx, |_this, element_state, cx| {
if let Some(child) = before_layout.child_element.as_mut() {
child.after_layout(cx);
if let Some(child) = request_layout.child_element.as_mut() {
child.prepaint(cx);
}
if let Some(menu) = before_layout.menu_element.as_mut() {
menu.after_layout(cx);
if let Some(menu) = request_layout.menu_element.as_mut() {
menu.prepaint(cx);
}
before_layout.child_layout_id.map(|layout_id| {
request_layout.child_layout_id.map(|layout_id| {
let bounds = cx.layout_bounds(layout_id);
element_state.child_bounds = Some(bounds);
cx.insert_hitbox(bounds, false).id
@ -240,16 +243,16 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
fn paint(
&mut self,
_: Bounds<gpui::Pixels>,
before_layout: &mut Self::BeforeLayout,
request_layout: &mut Self::RequestLayoutState,
child_hitbox: &mut Option<HitboxId>,
cx: &mut ElementContext,
) {
self.with_element_state(cx, |_this, _element_state, cx| {
if let Some(mut child) = before_layout.child_element.take() {
if let Some(mut child) = request_layout.child_element.take() {
child.paint(cx);
}
if let Some(mut menu) = before_layout.menu_element.take() {
if let Some(mut menu) = request_layout.menu_element.take() {
menu.paint(cx);
if let Some(child_hitbox) = *child_hitbox {

View file

@ -96,10 +96,13 @@ pub struct MenuHandleFrameState {
}
impl<M: ManagedView> Element for RightClickMenu<M> {
type BeforeLayout = MenuHandleFrameState;
type AfterLayout = Hitbox;
type RequestLayoutState = MenuHandleFrameState;
type PrepaintState = Hitbox;
fn before_layout(&mut self, cx: &mut ElementContext) -> (gpui::LayoutId, Self::BeforeLayout) {
fn request_layout(
&mut self,
cx: &mut ElementContext,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
self.with_element_state(cx, |this, element_state, cx| {
let mut menu_layout_id = None;
@ -114,7 +117,7 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
.with_priority(1)
.into_any();
menu_layout_id = Some(element.before_layout(cx));
menu_layout_id = Some(element.request_layout(cx));
element
});
@ -125,7 +128,7 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
let child_layout_id = child_element
.as_mut()
.map(|child_element| child_element.before_layout(cx));
.map(|child_element| child_element.request_layout(cx));
let layout_id = cx.request_layout(
&gpui::Style::default(),
@ -143,21 +146,21 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
})
}
fn after_layout(
fn prepaint(
&mut self,
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
request_layout: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
) -> Hitbox {
cx.with_element_id(Some(self.id.clone()), |cx| {
let hitbox = cx.insert_hitbox(bounds, false);
if let Some(child) = before_layout.child_element.as_mut() {
child.after_layout(cx);
if let Some(child) = request_layout.child_element.as_mut() {
child.prepaint(cx);
}
if let Some(menu) = before_layout.menu_element.as_mut() {
menu.after_layout(cx);
if let Some(menu) = request_layout.menu_element.as_mut() {
menu.prepaint(cx);
}
hitbox
@ -167,16 +170,16 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
fn paint(
&mut self,
_bounds: Bounds<gpui::Pixels>,
before_layout: &mut Self::BeforeLayout,
hitbox: &mut Self::AfterLayout,
request_layout: &mut Self::RequestLayoutState,
hitbox: &mut Self::PrepaintState,
cx: &mut ElementContext,
) {
self.with_element_state(cx, |this, element_state, cx| {
if let Some(mut child) = before_layout.child_element.take() {
if let Some(mut child) = request_layout.child_element.take() {
child.paint(cx);
}
if let Some(mut menu) = before_layout.menu_element.take() {
if let Some(mut menu) = request_layout.menu_element.take() {
menu.paint(cx);
return;
}
@ -188,7 +191,7 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
let attach = this.attach;
let menu = element_state.menu.clone();
let position = element_state.position.clone();
let child_layout_id = before_layout.child_layout_id;
let child_layout_id = request_layout.child_layout_id;
let child_bounds = cx.layout_bounds(child_layout_id.unwrap());
let hitbox_id = hitbox.id;