Primitives · raise: what opens inside a dialog lies over it
gpui 0.2.2 asserts on a deferred draw inside another, and every dialog, sheet and popover draws inside one, so opening a date field in the snooze or schedule dialog panicked, as would any list, menu or picker opened in an overlay. primitives::raise replaces each deferred draw: at the top it defers as before; inside something raised it leaves a placeholder in the tree, and its layer lays it out on its own, then draws it last. check.sh refuses gpui's deferred anywhere else. The time dialog's rule is now times::ahead, tested for a moment behind, now itself, a moment ahead and another zone; a gpui test picks yesterday in the snooze dialog, sees that it has passed and that nothing is sent, then picks two days ahead and hears that moment.
This commit is contained in:
1 parent
ccbc0970e5
commit
341420d1e4
20 files changed
+405
-49
No files matched your search
@@ -2,11 +2,11 @@ use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
Animation, AnimationExt, AnyElement, App, ElementId, InteractiveElement, IntoElement,
|
||||
MouseButton, ParentElement, Point, RenderOnce, Styled, Window, anchored, deferred, div,
|
||||
prelude::*,
|
||||
MouseButton, ParentElement, Point, RenderOnce, Styled, Window, anchored, div, prelude::*,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use super::raise;
|
||||
use crate::{motion, theme::ActiveTheme};
|
||||
|
||||
type Dismiss = Rc<dyn Fn(&mut Window, &mut App)>;
|
||||
@@ -81,7 +81,7 @@ impl RenderOnce for Backdrop {
|
||||
|scrim, t| scrim.opacity(t),
|
||||
);
|
||||
|
||||
deferred(
|
||||
raise(
|
||||
anchored().position(Point::default()).child(
|
||||
div()
|
||||
.relative()
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
use std::{cell::RefCell, panic::Location};
|
||||
|
||||
use gpui::{
|
||||
AnyElement, App, AvailableSpace, Bounds, Element, ElementId, GlobalElementId,
|
||||
InspectorElementId, IntoElement, LayoutId, Pixels, Point, Position, Style, Window, size,
|
||||
};
|
||||
|
||||
/// What was raised while a layer prepaints its content, each with where it sits and its priority.
|
||||
type Queue = Vec<(AnyElement, Point<Pixels>, usize)>;
|
||||
|
||||
thread_local! {
|
||||
static LAYERS: RefCell<Vec<Queue>> = const { RefCell::new(Vec::new()) };
|
||||
}
|
||||
|
||||
fn nested() -> bool {
|
||||
LAYERS.with_borrow(|layers| !layers.is_empty())
|
||||
}
|
||||
|
||||
/// Draws `child` over the page, as gpui's `deferred` does. gpui cannot defer a draw inside a deferred one, so what is raised inside something raised is laid out and drawn last within it: a list opened in a dialog lies over the dialog.
|
||||
pub fn raise(child: impl IntoElement) -> Raised {
|
||||
let layer = Layer {
|
||||
child: child.into_any_element(),
|
||||
};
|
||||
Raised {
|
||||
child: Some(layer.into_any_element()),
|
||||
priority: 0,
|
||||
nested: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// An element drawn over the page; see [`raise`].
|
||||
pub struct Raised {
|
||||
child: Option<AnyElement>,
|
||||
priority: usize,
|
||||
nested: bool,
|
||||
}
|
||||
|
||||
impl Raised {
|
||||
/// A higher priority draws later, over the lower.
|
||||
pub fn with_priority(mut self, priority: usize) -> Self {
|
||||
self.priority = priority;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoElement for Raised {
|
||||
type Element = Self;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Raised {
|
||||
type RequestLayoutState = ();
|
||||
type PrepaintState = ();
|
||||
|
||||
fn id(&self) -> Option<ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn source_location(&self) -> Option<&'static Location<'static>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<&GlobalElementId>,
|
||||
_: Option<&InspectorElementId>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (LayoutId, ()) {
|
||||
self.nested = nested();
|
||||
if self.nested {
|
||||
let style = Style {
|
||||
position: Position::Absolute,
|
||||
..Style::default()
|
||||
};
|
||||
return (window.request_layout(style, [], cx), ());
|
||||
}
|
||||
let child = self.child.as_mut().expect("a raised element lays out once");
|
||||
(child.request_layout(window, cx), ())
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
_: Option<&GlobalElementId>,
|
||||
_: Option<&InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_: &mut (),
|
||||
window: &mut Window,
|
||||
_: &mut App,
|
||||
) {
|
||||
let child = self.child.take().expect("a raised element prepaints once");
|
||||
if !self.nested {
|
||||
window.defer_draw(child, window.element_offset(), self.priority);
|
||||
return;
|
||||
}
|
||||
LAYERS.with_borrow_mut(|layers| {
|
||||
let queue = layers
|
||||
.last_mut()
|
||||
.expect("a nested element prepaints in its layer");
|
||||
queue.push((child, bounds.origin, self.priority));
|
||||
});
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: Option<&GlobalElementId>,
|
||||
_: Option<&InspectorElementId>,
|
||||
_: Bounds<Pixels>,
|
||||
_: &mut (),
|
||||
_: &mut (),
|
||||
_: &mut Window,
|
||||
_: &mut App,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
/// What a raised element draws: its content, then what was raised inside it, each laid out on its own and the higher priority last.
|
||||
struct Layer {
|
||||
child: AnyElement,
|
||||
}
|
||||
|
||||
impl IntoElement for Layer {
|
||||
type Element = Self;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs `f` with a fresh queue on the stack, and hands back what was raised meanwhile.
|
||||
fn collect(f: impl FnOnce()) -> Queue {
|
||||
LAYERS.with_borrow_mut(|layers| layers.push(Vec::new()));
|
||||
f();
|
||||
LAYERS
|
||||
.with_borrow_mut(|layers| layers.pop())
|
||||
.expect("a layer pops what it pushed")
|
||||
}
|
||||
|
||||
impl Element for Layer {
|
||||
type RequestLayoutState = ();
|
||||
type PrepaintState = Vec<AnyElement>;
|
||||
|
||||
fn id(&self) -> Option<ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn source_location(&self) -> Option<&'static Location<'static>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<&GlobalElementId>,
|
||||
_: Option<&InspectorElementId>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (LayoutId, ()) {
|
||||
let mut layout = None;
|
||||
collect(|| layout = Some(self.child.request_layout(window, cx)));
|
||||
(layout.expect("the child laid out"), ())
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
_: Option<&GlobalElementId>,
|
||||
_: Option<&InspectorElementId>,
|
||||
_: Bounds<Pixels>,
|
||||
_: &mut (),
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Vec<AnyElement> {
|
||||
let mut raised = collect(|| {
|
||||
self.child.prepaint(window, cx);
|
||||
});
|
||||
raised.sort_by_key(|(_, _, priority)| *priority);
|
||||
let viewport = window.viewport_size();
|
||||
let space = size(
|
||||
AvailableSpace::Definite(viewport.width),
|
||||
AvailableSpace::Definite(viewport.height),
|
||||
);
|
||||
raised
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(ix, (mut element, at, _))| {
|
||||
window.with_element_namespace(("raised", ix), |window| {
|
||||
element.prepaint_as_root(at, space, window, cx)
|
||||
});
|
||||
element
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: Option<&GlobalElementId>,
|
||||
_: Option<&InspectorElementId>,
|
||||
_: Bounds<Pixels>,
|
||||
_: &mut (),
|
||||
raised: &mut Vec<AnyElement>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
self.child.paint(window, cx);
|
||||
for (ix, element) in raised.iter_mut().enumerate() {
|
||||
window.with_element_namespace(("raised", ix), |window| element.paint(window, cx));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,15 @@ mod focus;
|
||||
mod ghost;
|
||||
mod icon;
|
||||
mod image;
|
||||
mod layer;
|
||||
mod measure;
|
||||
mod pressable;
|
||||
mod severity;
|
||||
mod tooltip;
|
||||
|
||||
#[cfg(all(test, feature = "test-support"))]
|
||||
mod tests;
|
||||
|
||||
pub use backdrop::{Backdrop, Place};
|
||||
pub use disclosure::Disclosure;
|
||||
pub use divider::Divider;
|
||||
@@ -21,6 +25,7 @@ pub use ghost::DragGhost;
|
||||
pub use icon::{Icon, IconName};
|
||||
pub use image::Image;
|
||||
pub(crate) use image::{checked_ratio, framed};
|
||||
pub use layer::{Raised, raise};
|
||||
pub use measure::{IntersectionObserver, Measure};
|
||||
pub use pressable::Pressable;
|
||||
pub use severity::Severity;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
use gpui::{
|
||||
Context, IntoElement, Modifiers, ParentElement, Render, Styled, TestAppContext, Window,
|
||||
anchored, div, point, prelude::*, px,
|
||||
};
|
||||
|
||||
use super::raise;
|
||||
use crate::theme::Theme;
|
||||
|
||||
/// A raised square holding a raised button, under a cover drawn after it in the same layer.
|
||||
struct Stack {
|
||||
heard: Vec<&'static str>,
|
||||
}
|
||||
|
||||
impl Render for Stack {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let (inner, cover) = (cx.entity(), cx.entity());
|
||||
let button = div()
|
||||
.id("inner")
|
||||
.size(px(100.0))
|
||||
.occlude()
|
||||
.on_click(move |_, _, cx| inner.update(cx, |stack, _| stack.heard.push("inner")));
|
||||
let sheet = div()
|
||||
.id("cover")
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.size(px(200.0))
|
||||
.on_click(move |_, _, cx| cover.update(cx, |stack, _| stack.heard.push("cover")));
|
||||
div().size_full().child(raise(
|
||||
anchored().position(point(px(0.0), px(0.0))).child(
|
||||
div()
|
||||
.relative()
|
||||
.size(px(200.0))
|
||||
.child(raise(
|
||||
anchored().position(point(px(50.0), px(50.0))).child(button),
|
||||
))
|
||||
.child(sheet),
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn what_is_raised_inside_a_raise_lies_over_it(cx: &mut TestAppContext) {
|
||||
cx.update(Theme::init);
|
||||
let (view, cx) = cx.add_window_view(|_, _| Stack { heard: Vec::new() });
|
||||
cx.run_until_parked();
|
||||
cx.simulate_click(point(px(100.0), px(100.0)), Modifiers::none());
|
||||
assert_eq!(
|
||||
view.read_with(cx, |stack, _| stack.heard.clone()),
|
||||
["inner"]
|
||||
);
|
||||
}
|
||||
@@ -3,10 +3,11 @@ use std::{rc::Rc, time::Duration};
|
||||
use gpui::{
|
||||
Animation, AnimationExt, AnyElement, AnyView, App, AppContext, Context, Div, ElementId,
|
||||
InteractiveElement, IntoElement, ParentElement, Pixels, Point, Render, RenderOnce,
|
||||
SharedString, StatefulInteractiveElement, Styled, Task, Window, anchored, deferred, div, point,
|
||||
SharedString, StatefulInteractiveElement, Styled, Task, Window, anchored, div, point,
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
use super::raise;
|
||||
use crate::{
|
||||
motion,
|
||||
theme::{ActiveTheme, Elevation, Radius, TextSize},
|
||||
@@ -175,7 +176,7 @@ impl RenderOnce for TooltipTrigger {
|
||||
.when(visible, |trigger| {
|
||||
let body = content(window, cx);
|
||||
let below = cx.theme().cursor_offset().to_pixels(window.rem_size());
|
||||
trigger.child(deferred(
|
||||
trigger.child(raise(
|
||||
anchored()
|
||||
.position(mouse + point(Pixels::ZERO, below))
|
||||
.snap_to_window()
|
||||
|
||||
Reference in new issue
Block a user