#[cfg(any(feature = "inspector", debug_assertions))]
use crate::InspectorElementPath;
use crate::{
A11ySubtreeBuilder, App, ArenaBox, AvailableSpace, Bounds, Context, DispatchNodeId, ElementId,
FocusHandle, InspectorElementId, LayoutId, Pixels, Point, Size, Style, Window,
util::FluentBuilder, window::with_element_arena,
};
use std::{
any::Any,
fmt::{self, Debug, Display},
mem, panic,
sync::Arc,
};
pub trait Element: 'static + IntoElement {
type RequestLayoutState: 'static;
type PrepaintState: 'static;
fn id(&self) -> Option<ElementId>;
fn source_location(&self) -> Option<&'static panic::Location<'static>>;
fn request_layout(
&mut self,
id: Option<&GlobalElementId>,
inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState);
fn prepaint(
&mut self,
id: Option<&GlobalElementId>,
inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
cx: &mut App,
) -> Self::PrepaintState;
fn paint(
&mut self,
id: Option<&GlobalElementId>,
inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
window: &mut Window,
cx: &mut App,
);
fn a11y_role(&self) -> Option<accesskit::Role> {
None
}
fn write_a11y_info(&self, _node: &mut accesskit::Node) {}
fn a11y_synthetic_children(
&mut self,
_prepaint: &mut Self::PrepaintState,
_builder: &mut A11ySubtreeBuilder,
) {
}
fn into_any(self) -> AnyElement {
AnyElement::new(self)
}
}
pub trait IntoElement: Sized {
type Element: Element;
fn into_element(self) -> Self::Element;
fn into_any_element(self) -> AnyElement {
self.into_element().into_any()
}
}
impl<T: IntoElement> FluentBuilder for T {}
pub trait Render: 'static + Sized {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement;
}
impl Render for Empty {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
Empty
}
}
pub trait RenderOnce: 'static {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement;
}
pub trait ParentElement {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>);
fn child(mut self, child: impl IntoElement) -> Self
where
Self: Sized,
{
self.extend(std::iter::once(child.into_any_element()));
self
}
fn children(mut self, children: impl IntoIterator<Item = impl IntoElement>) -> Self
where
Self: Sized,
{
self.extend(children.into_iter().map(|child| child.into_any_element()));
self
}
}
#[derive(Clone, Debug)]
pub struct GlobalElementId {
ids: Arc<[ElementId]>,
hash: u64,
}
pub(crate) const EMPTY_PATH_HASH: u64 = 0;
pub(crate) fn extend_path_hash(prefix: u64, id: &ElementId) -> u64 {
use std::hash::{Hash, Hasher};
let mut hasher = collections::FxHasher::with_seed(prefix as usize);
id.hash(&mut hasher);
hasher.finish()
}
impl GlobalElementId {
pub(crate) fn new(ids: &[ElementId]) -> Self {
let hash = ids.iter().fold(EMPTY_PATH_HASH, extend_path_hash);
Self::with_hash(ids, hash)
}
pub(crate) fn with_hash(ids: &[ElementId], hash: u64) -> Self {
debug_assert_eq!(hash, ids.iter().fold(EMPTY_PATH_HASH, extend_path_hash));
Self {
ids: Arc::from(ids),
hash,
}
}
pub(crate) fn accesskit_node_id(&self) -> accesskit::NodeId {
accesskit::NodeId(self.hash)
}
}
impl Default for GlobalElementId {
fn default() -> Self {
Self::new(&[])
}
}
impl std::ops::Deref for GlobalElementId {
type Target = [ElementId];
fn deref(&self) -> &Self::Target {
&self.ids
}
}
impl PartialEq for GlobalElementId {
fn eq(&self, other: &Self) -> bool {
self.hash == other.hash && (Arc::ptr_eq(&self.ids, &other.ids) || self.ids == other.ids)
}
}
impl Eq for GlobalElementId {}
impl std::hash::Hash for GlobalElementId {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_u64(self.hash);
}
}
impl Display for GlobalElementId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, element_id) in self.ids.iter().enumerate() {
if i > 0 {
write!(f, ".")?;
}
write!(f, "{}", element_id)?;
}
Ok(())
}
}
trait ElementObject {
fn inner_element(&mut self) -> &mut dyn Any;
fn request_layout(&mut self, window: &mut Window, cx: &mut App) -> LayoutId;
fn prepaint(&mut self, window: &mut Window, cx: &mut App);
fn paint(&mut self, window: &mut Window, cx: &mut App);
fn layout_as_root(
&mut self,
available_space: Size<AvailableSpace>,
window: &mut Window,
cx: &mut App,
) -> Size<Pixels>;
}
pub struct Drawable<E: Element> {
pub element: E,
phase: ElementDrawPhase<E::RequestLayoutState, E::PrepaintState>,
}
#[derive(Default)]
enum ElementDrawPhase<RequestLayoutState, PrepaintState> {
#[default]
Start,
RequestLayout {
layout_id: LayoutId,
global_id: Option<GlobalElementId>,
inspector_id: Option<InspectorElementId>,
request_layout: RequestLayoutState,
},
LayoutComputed {
layout_id: LayoutId,
global_id: Option<GlobalElementId>,
inspector_id: Option<InspectorElementId>,
available_space: Size<AvailableSpace>,
request_layout: RequestLayoutState,
},
Prepaint {
node_id: DispatchNodeId,
global_id: Option<GlobalElementId>,
inspector_id: Option<InspectorElementId>,
bounds: Bounds<Pixels>,
request_layout: RequestLayoutState,
prepaint: PrepaintState,
},
Painted,
}
impl<E: Element> Drawable<E> {
pub(crate) fn new(element: E) -> Self {
Drawable {
element,
phase: ElementDrawPhase::Start,
}
}
fn request_layout(&mut self, window: &mut Window, cx: &mut App) -> LayoutId {
match mem::take(&mut self.phase) {
ElementDrawPhase::Start => {
let global_id = self
.element
.id()
.map(|element_id| prepare_element_id(element_id, window));
let inspector_id;
#[cfg(any(feature = "inspector", debug_assertions))]
{
inspector_id = if window.inspector_enabled() {
self.element
.source_location()
.map(|source| prepare_inspector_id(source, window))
} else {
None
};
}
#[cfg(not(any(feature = "inspector", debug_assertions)))]
{
inspector_id = None;
}
let (layout_id, request_layout) = self.element.request_layout(
global_id.as_ref(),
inspector_id.as_ref(),
window,
cx,
);
if global_id.is_some() {
window.element_id_stack.pop();
}
self.phase = ElementDrawPhase::RequestLayout {
layout_id,
global_id,
inspector_id,
request_layout,
};
layout_id
}
_ => panic!("must call request_layout only once"),
}
}
pub(crate) fn prepaint(&mut self, window: &mut Window, cx: &mut App) {
match mem::take(&mut self.phase) {
ElementDrawPhase::RequestLayout {
layout_id,
global_id,
inspector_id,
mut request_layout,
}
| ElementDrawPhase::LayoutComputed {
layout_id,
global_id,
inspector_id,
mut request_layout,
..
} => {
if let Some(element_id) = self.element.id() {
window.element_id_stack.push(element_id);
debug_assert_eq!(&**global_id.as_ref().unwrap(), &*window.element_id_stack);
}
let bounds = window.layout_bounds(layout_id);
let mut pushed_a11y_node = false;
if window.a11y.is_active() {
if let Some(global_id) = global_id.as_ref() {
if let Some(role) = self.element.a11y_role() {
let node_id = global_id.accesskit_node_id();
let mut node = accesskit::Node::new(role);
let scale = window.scale_factor();
node.set_bounds(accesskit::Rect {
x0: (bounds.origin.x.0 * scale) as f64,
y0: (bounds.origin.y.0 * scale) as f64,
x1: ((bounds.origin.x.0 + bounds.size.width.0) * scale) as f64,
y1: ((bounds.origin.y.0 + bounds.size.height.0) * scale) as f64,
});
self.element.write_a11y_info(&mut node);
window.a11y.node_bounds.insert(node_id, bounds);
pushed_a11y_node = window.a11y.nodes.push(node_id, node);
#[cfg(debug_assertions)]
if pushed_a11y_node {
let view = window
.a11y
.view_type_names
.get(&window.current_view())
.copied();
let source_location = self.element.source_location();
window.a11y.nodes.record_node_info(
node_id,
crate::window::a11y::debug::NodeDebugInfo {
synthetic: false,
view,
element_id: global_id.last().map(|id| format!("{id:?}")),
source_location,
},
);
}
}
}
}
let node_id = window.next_frame.dispatch_tree.push_node();
let mut prepaint = self.element.prepaint(
global_id.as_ref(),
inspector_id.as_ref(),
bounds,
&mut request_layout,
window,
cx,
);
window.next_frame.dispatch_tree.pop_node();
if pushed_a11y_node {
if let Some(global_id) = global_id.as_ref() {
#[cfg(debug_assertions)]
let creator = crate::window::a11y::debug::NodeCreator {
view: window
.a11y
.view_type_names
.get(&window.current_view())
.copied(),
element_id: global_id.last().map(|id| format!("{id:?}")),
source_location: self.element.source_location(),
};
let mut builder = A11ySubtreeBuilder::new(
global_id.accesskit_node_id(),
&mut window.a11y.nodes,
);
#[cfg(debug_assertions)]
{
builder = builder.with_creator(creator);
}
self.element
.a11y_synthetic_children(&mut prepaint, &mut builder);
}
window.a11y.nodes.pop();
}
if global_id.is_some() {
window.element_id_stack.pop();
}
self.phase = ElementDrawPhase::Prepaint {
node_id,
global_id,
inspector_id,
bounds,
request_layout,
prepaint,
};
}
_ => panic!("must call request_layout before prepaint"),
}
}
pub(crate) fn paint(
&mut self,
window: &mut Window,
cx: &mut App,
) -> (E::RequestLayoutState, E::PrepaintState) {
match mem::take(&mut self.phase) {
ElementDrawPhase::Prepaint {
node_id,
global_id,
inspector_id,
bounds,
mut request_layout,
mut prepaint,
..
} => {
if let Some(element_id) = self.element.id() {
window.element_id_stack.push(element_id);
debug_assert_eq!(&**global_id.as_ref().unwrap(), &*window.element_id_stack);
}
window.next_frame.dispatch_tree.set_active_node(node_id);
self.element.paint(
global_id.as_ref(),
inspector_id.as_ref(),
bounds,
&mut request_layout,
&mut prepaint,
window,
cx,
);
if global_id.is_some() {
window.element_id_stack.pop();
}
self.phase = ElementDrawPhase::Painted;
(request_layout, prepaint)
}
_ => panic!("must call prepaint before paint"),
}
}
pub(crate) fn layout_as_root(
&mut self,
available_space: Size<AvailableSpace>,
window: &mut Window,
cx: &mut App,
) -> Size<Pixels> {
if matches!(&self.phase, ElementDrawPhase::Start) {
self.request_layout(window, cx);
}
let layout_id = match mem::take(&mut self.phase) {
ElementDrawPhase::RequestLayout {
layout_id,
global_id,
inspector_id,
request_layout,
} => {
window.compute_layout(layout_id, available_space, cx);
self.phase = ElementDrawPhase::LayoutComputed {
layout_id,
global_id,
inspector_id,
available_space,
request_layout,
};
layout_id
}
ElementDrawPhase::LayoutComputed {
layout_id,
global_id,
inspector_id,
available_space: prev_available_space,
request_layout,
} => {
if available_space != prev_available_space {
window.compute_layout(layout_id, available_space, cx);
}
self.phase = ElementDrawPhase::LayoutComputed {
layout_id,
global_id,
inspector_id,
available_space,
request_layout,
};
layout_id
}
_ => panic!("cannot measure after painting"),
};
window.layout_bounds(layout_id).size
}
}
impl<E> ElementObject for Drawable<E>
where
E: Element,
E::RequestLayoutState: 'static,
{
fn inner_element(&mut self) -> &mut dyn Any {
&mut self.element
}
#[inline]
fn request_layout(&mut self, window: &mut Window, cx: &mut App) -> LayoutId {
Drawable::request_layout(self, window, cx)
}
#[inline]
fn prepaint(&mut self, window: &mut Window, cx: &mut App) {
Drawable::prepaint(self, window, cx);
}
#[inline]
fn paint(&mut self, window: &mut Window, cx: &mut App) {
Drawable::paint(self, window, cx);
}
#[inline]
fn layout_as_root(
&mut self,
available_space: Size<AvailableSpace>,
window: &mut Window,
cx: &mut App,
) -> Size<Pixels> {
Drawable::layout_as_root(self, available_space, window, cx)
}
}
pub struct AnyElement(ArenaBox<dyn ElementObject>);
impl AnyElement {
pub(crate) fn new<E>(element: E) -> Self
where
E: 'static + Element,
E::RequestLayoutState: Any,
{
let element = with_element_arena(|arena| arena.alloc(|| Drawable::new(element)))
.map(|element| element as &mut dyn ElementObject);
AnyElement(element)
}
pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.0.inner_element().downcast_mut::<T>()
}
pub fn request_layout(&mut self, window: &mut Window, cx: &mut App) -> LayoutId {
self.0.request_layout(window, cx)
}
pub fn prepaint(&mut self, window: &mut Window, cx: &mut App) -> Option<FocusHandle> {
let focus_assigned = window.next_frame.focus.is_some();
self.0.prepaint(window, cx);
if !focus_assigned && let Some(focus_id) = window.next_frame.focus {
return FocusHandle::for_id(focus_id, &cx.focus_handles);
}
None
}
pub fn paint(&mut self, window: &mut Window, cx: &mut App) {
self.0.paint(window, cx);
}
pub fn layout_as_root(
&mut self,
available_space: Size<AvailableSpace>,
window: &mut Window,
cx: &mut App,
) -> Size<Pixels> {
self.0.layout_as_root(available_space, window, cx)
}
pub fn prepaint_at(
&mut self,
origin: Point<Pixels>,
window: &mut Window,
cx: &mut App,
) -> Option<FocusHandle> {
window.with_absolute_element_offset(origin, |window| self.prepaint(window, cx))
}
pub fn prepaint_as_root(
&mut self,
origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
window: &mut Window,
cx: &mut App,
) -> Option<FocusHandle> {
self.layout_as_root(available_space, window, cx);
window.with_absolute_element_offset(origin, |window| self.prepaint(window, cx))
}
}
impl Element for AnyElement {
type RequestLayoutState = ();
type PrepaintState = ();
fn id(&self) -> Option<ElementId> {
None
}
fn source_location(&self) -> Option<&'static panic::Location<'static>> {
None
}
fn request_layout(
&mut self,
_: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
let layout_id = self.request_layout(window, cx);
(layout_id, ())
}
fn prepaint(
&mut self,
_: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
_: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
window: &mut Window,
cx: &mut App,
) {
self.prepaint(window, cx);
}
fn paint(
&mut self,
_: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
_: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
window: &mut Window,
cx: &mut App,
) {
self.paint(window, cx);
}
}
impl IntoElement for AnyElement {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
fn into_any_element(self) -> AnyElement {
self
}
}
pub struct Empty;
impl IntoElement for Empty {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
impl Element for Empty {
type RequestLayoutState = ();
type PrepaintState = ();
fn id(&self) -> Option<ElementId> {
None
}
fn source_location(&self) -> Option<&'static panic::Location<'static>> {
None
}
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
(
window.request_layout(
Style {
display: crate::Display::None,
..Default::default()
},
None,
cx,
),
(),
)
}
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_state: &mut Self::RequestLayoutState,
_window: &mut Window,
_cx: &mut App,
) {
}
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
_prepaint: &mut Self::PrepaintState,
_window: &mut Window,
_cx: &mut App,
) {
}
}
#[inline(never)]
fn prepare_element_id(element_id: ElementId, window: &mut Window) -> GlobalElementId {
window.element_id_stack.push(element_id);
window.element_id_stack.global_id()
}
#[cfg(any(feature = "inspector", debug_assertions))]
#[inline(never)]
fn prepare_inspector_id(
source: &'static panic::Location<'static>,
window: &mut Window,
) -> InspectorElementId {
let path = InspectorElementPath {
global_id: window.element_id_stack.global_id(),
source_location: source,
};
window.build_inspector_element_id(path)
}
#[cfg(test)]
mod global_element_id_tests {
use super::*;
use std::hash::{Hash, Hasher};
fn path(depth: usize, leaf: u64) -> Vec<ElementId> {
let mut ids: Vec<ElementId> = (0..depth as u64)
.map(|level| ElementId::NamedInteger("some-container-element".into(), level))
.collect();
ids.push(ElementId::NamedInteger("leaf".into(), leaf));
ids
}
fn fx_hash(value: &impl Hash) -> u64 {
let mut hasher = collections::FxHasher::default();
value.hash(&mut hasher);
hasher.finish()
}
#[test]
fn equal_paths_are_equal_and_hash_alike() {
let a = GlobalElementId::new(&path(8, 1));
let b = GlobalElementId::new(&path(8, 1));
let c = GlobalElementId::new(&path(8, 2));
let shallower = GlobalElementId::new(&path(7, 1));
assert_eq!(a, b);
assert_eq!(fx_hash(&a), fx_hash(&b));
assert_eq!(a.accesskit_node_id(), b.accesskit_node_id());
assert_ne!(a, c);
assert_ne!(a, shallower);
assert_ne!(fx_hash(&a), fx_hash(&c));
assert_eq!(&*a, path(8, 1).as_slice());
assert_eq!(a.to_string(), b.to_string());
assert_eq!(GlobalElementId::default(), GlobalElementId::new(&[]));
}
struct DeepStatefulTree {
items: usize,
depth: usize,
}
impl crate::Render for DeepStatefulTree {
fn render(&mut self, _: &mut Window, _: &mut crate::Context<Self>) -> impl IntoElement {
use crate::{InteractiveElement as _, ParentElement as _, Styled as _, div, px};
let depth = self.depth;
div()
.flex()
.flex_wrap()
.size_full()
.children((0..self.items).map(move |item| {
let mut element = div().id(("leaf", item as u64)).size(px(2.));
for level in (0..depth).rev() {
element = div()
.id(("some-container-element", level as u64))
.child(element);
}
element.id(("item", item as u64)).size(px(4.))
}))
}
}
#[crate::test]
#[ignore = "prints timings; run by hand"]
fn frame_cost(cx: &mut crate::TestAppContext) {
use crate::AppContext as _;
for (items, depth) in [(500, 4), (500, 12), (2000, 12)] {
let window = cx.add_window(move |_, _| DeepStatefulTree { items, depth });
let window = crate::AnyWindowHandle::from(window);
let mut frame = |cx: &mut crate::TestAppContext| {
cx.update_window(window, |_, window, cx| {
window.refresh();
let started = std::time::Instant::now();
window.draw(cx).clear(cx);
started.elapsed()
})
.unwrap()
};
for _ in 0..5 {
frame(cx);
}
let mut samples: Vec<_> = (0..40).map(|_| frame(cx)).collect();
samples.sort();
let median = samples[samples.len() / 2];
let stateful = items * (depth + 2);
eprintln!(
"{items} items x {depth} deep ({stateful} stateful elements): median frame {:.2} ms, {:.0} ns per stateful element",
median.as_secs_f64() * 1e3,
median.as_nanos() as f64 / stateful as f64,
);
}
}
#[test]
#[ignore = "prints timings; run by hand"]
fn hash_cost() {
let ids = path(11, 7);
let id = GlobalElementId::new(&ids);
let n = 1_000_000u64;
let mut acc = 0u64;
let started = std::time::Instant::now();
for _ in 0..n {
acc ^= fx_hash(&std::hint::black_box(ids.as_slice()));
}
let walk = started.elapsed();
let started = std::time::Instant::now();
for _ in 0..n {
acc ^= fx_hash(std::hint::black_box(&id));
}
let cached = started.elapsed();
let started = std::time::Instant::now();
for _ in 0..n {
acc ^= GlobalElementId::new(std::hint::black_box(&ids)).hash;
}
let build = started.elapsed();
eprintln!(
"12-deep path: hash by walking ids {:.1} ns, cached {:.1} ns, build (clone + hash once) {:.1} ns [{acc}]",
walk.as_nanos() as f64 / n as f64,
cached.as_nanos() as f64 / n as f64,
build.as_nanos() as f64 / n as f64,
);
}
}