use ribir_geom::ZERO_SIZE;
use super::{widget_id::split_arena, DirtySet, WidgetId, WidgetTree};
use crate::{
builtin_widgets::PerformedLayoutListener,
context::{LayoutCtx, WindowCtx},
prelude::{Point, Rect, Size, INFINITY_SIZE},
widget::{QueryOrder, TreeArena},
};
use std::{cmp::Reverse, collections::HashMap};
#[derive(Debug, Clone, PartialEq, Copy)]
pub struct BoxClamp {
pub min: Size,
pub max: Size,
}
impl BoxClamp {
pub const EXPAND_X: BoxClamp = BoxClamp {
min: Size::new(f32::INFINITY, 0.),
max: Size::new(f32::INFINITY, f32::INFINITY),
};
pub const EXPAND_Y: BoxClamp = BoxClamp {
min: Size::new(0., f32::INFINITY),
max: Size::new(f32::INFINITY, f32::INFINITY),
};
pub const EXPAND_BOTH: BoxClamp = BoxClamp {
min: Size::new(f32::INFINITY, f32::INFINITY),
max: Size::new(f32::INFINITY, f32::INFINITY),
};
pub const fn fixed_width(width: f32) -> Self {
BoxClamp {
min: Size::new(width, 0.),
max: Size::new(width, f32::INFINITY),
}
}
pub const fn fixed_height(height: f32) -> Self {
BoxClamp {
min: Size::new(0., height),
max: Size::new(f32::INFINITY, height),
}
}
pub const fn fixed_size(size: Size) -> Self { BoxClamp { min: size, max: size } }
pub const fn min_width(width: f32) -> Self {
let mut clamp = BoxClamp::EXPAND_BOTH;
clamp.min.width = width;
clamp
}
pub const fn min_height(height: f32) -> Self {
let mut clamp = BoxClamp::EXPAND_BOTH;
clamp.min.height = height;
clamp
}
pub fn with_fixed_height(mut self, height: f32) -> Self {
self.min.height = height;
self.max.height = height;
self
}
pub fn with_fixed_width(mut self, width: f32) -> Self {
self.min.width = width;
self.max.width = width;
self
}
}
#[derive(Debug, Default, Clone)]
pub struct LayoutInfo {
pub clamp: BoxClamp,
pub size: Option<Size>,
pub pos: Point,
}
#[derive(Default)]
pub(crate) struct LayoutStore {
data: HashMap<WidgetId, LayoutInfo, ahash::RandomState>,
performed: Vec<WidgetId>,
}
pub struct Layouter<'a> {
pub(crate) wid: WidgetId,
pub(crate) arena: &'a mut TreeArena,
pub(crate) store: &'a mut LayoutStore,
pub(crate) wnd_ctx: &'a mut WindowCtx,
pub(crate) dirty_set: &'a DirtySet,
pub(crate) is_layout_root: bool,
}
impl LayoutStore {
pub(crate) fn force_layout(&mut self, id: WidgetId) -> Option<LayoutInfo> { self.remove(id) }
pub(crate) fn remove(&mut self, id: WidgetId) -> Option<LayoutInfo> { self.data.remove(&id) }
pub(crate) fn layout_box_size(&self, id: WidgetId) -> Option<Size> {
self.layout_info(id).and_then(|info| info.size)
}
pub(crate) fn layout_box_position(&self, id: WidgetId) -> Option<Point> {
self.layout_info(id).map(|info| info.pos)
}
pub(crate) fn layout_info(&self, id: WidgetId) -> Option<&LayoutInfo> { self.data.get(&id) }
pub(crate) fn swap(&mut self, id1: WidgetId, id2: WidgetId) {
let info1 = self.layout_info(id1).cloned();
let info2 = self.layout_info(id2).cloned();
let mut reset = |id, v| {
if let Some(info) = v {
self.data.insert(id, info);
} else {
self.data.remove(&id);
}
};
reset(id1, info2);
reset(id2, info1);
}
pub(crate) fn layout_info_or_default(&mut self, id: WidgetId) -> &mut LayoutInfo {
self.data.entry(id).or_insert_with(LayoutInfo::default)
}
pub(crate) fn map_to_parent(&self, id: WidgetId, pos: Point, arena: &TreeArena) -> Point {
self.layout_box_position(id).map_or(pos, |offset| {
let pos = id
.assert_get(arena)
.get_transform()
.map_or(pos, |t| t.transform_point(pos));
pos + offset.to_vector()
})
}
pub(crate) fn map_from_parent(&self, id: WidgetId, pos: Point, arena: &TreeArena) -> Point {
self.layout_box_position(id).map_or(pos, |offset| {
let pos = pos - offset.to_vector();
id.assert_get(arena)
.get_transform()
.map_or(pos, |t| t.inverse().map_or(pos, |t| t.transform_point(pos)))
})
}
pub(crate) fn map_to_global(&self, pos: Point, widget: WidgetId, arena: &TreeArena) -> Point {
widget
.ancestors(arena)
.fold(pos, |pos, p| self.map_to_parent(p, pos, arena))
}
pub(crate) fn map_from_global(&self, pos: Point, widget: WidgetId, arena: &TreeArena) -> Point {
let stack = widget.ancestors(arena).collect::<Vec<_>>();
stack
.iter()
.rev()
.fold(pos, |pos, p| self.map_from_parent(*p, pos, arena))
}
pub(crate) fn take_performed(&mut self) -> Vec<WidgetId> {
let performed = self.performed.clone();
self.performed.clear();
performed
}
}
impl BoxClamp {
#[inline]
pub fn clamp(self, size: Size) -> Size { size.clamp(self.min, self.max) }
#[inline]
pub fn expand(mut self) -> Self {
self.max = INFINITY_SIZE;
self
}
#[inline]
pub fn loose(mut self) -> Self {
self.min = ZERO_SIZE;
self
}
}
impl<'a> Layouter<'a> {
pub fn perform_widget_layout(&mut self, clamp: BoxClamp) -> Size {
let Self {
wid,
arena,
store,
wnd_ctx,
dirty_set,
is_layout_root,
} = self;
let size = store
.layout_info(*wid)
.filter(|info| clamp == info.clamp)
.and_then(|info| info.size)
.unwrap_or_else(|| {
let (arena1, arena2) = unsafe { split_arena(arena) };
let layout = wid.assert_get(arena1);
let mut ctx = LayoutCtx {
id: *wid,
arena: arena2,
store,
wnd_ctx,
dirty_set,
};
let size = layout.perform_layout(clamp, &mut ctx);
let size = clamp.clamp(size);
let info = store.layout_info_or_default(*wid);
info.clamp = clamp;
info.size = Some(size);
layout.query_all_type(
|_: &PerformedLayoutListener| {
store.performed.push(*wid);
false
},
QueryOrder::OutsideFirst,
);
size
});
if !*is_layout_root {
store.layout_info_or_default(*wid).pos = Point::zero();
}
size
}
pub fn into_next_sibling(self) -> Option<Self> {
assert!(
self.layout_rect().is_some(),
"Before try to layout next sibling, self must performed layout."
);
let Self {
arena,
store,
wnd_ctx,
dirty_set,
wid,
..
} = self;
wid.next_sibling(arena).map(move |sibling| Layouter {
wid: sibling,
arena,
store,
wnd_ctx,
dirty_set,
is_layout_root: false,
})
}
pub fn into_first_child_layouter(self) -> Option<Self> {
let Self {
arena,
store,
wnd_ctx,
dirty_set,
wid,
..
} = self;
wid.first_child(arena).map(move |wid| Layouter {
wid,
arena,
store,
wnd_ctx,
dirty_set,
is_layout_root: false,
})
}
pub fn has_child(&self) -> bool { self.wid.first_child(self.arena).is_some() }
#[inline]
pub fn layout_rect(&self) -> Option<Rect> {
self
.store
.layout_info(self.wid)
.and_then(|info| info.size.map(|size| Rect::new(info.pos, size)))
}
#[inline]
pub fn layout_pos(&self) -> Option<Point> {
self.store.layout_info(self.wid).map(|info| info.pos)
}
#[inline]
pub fn layout_size(&self) -> Option<Size> {
self.store.layout_info(self.wid).and_then(|info| info.size)
}
#[inline]
pub fn update_position(&mut self, pos: Point) {
self.store.layout_info_or_default(self.wid).pos = pos;
}
#[inline]
pub fn update_size(&mut self, child: WidgetId, size: Size) {
self.store.layout_info_or_default(child).size = Some(size);
}
#[inline]
pub fn query_widget_type<W: 'static>(&self, callback: impl FnOnce(&W)) {
self
.wid
.assert_get(self.arena)
.query_on_first_type(QueryOrder::OutsideFirst, callback);
}
pub fn reset_children_position(&mut self) {
let Self { wid, arena, store, .. } = self;
wid.children(arena).for_each(move |id| {
store.layout_info_or_default(id).pos = Point::zero();
});
}
}
impl WidgetTree {
pub(crate) fn layout_list(&mut self) -> Option<Vec<WidgetId>> {
if self.dirty_set.borrow().is_empty() {
return None;
}
let mut needs_layout = vec![];
let dirty_widgets = {
let mut state_changed = self.dirty_set.borrow_mut();
let dirty_widgets = state_changed.clone();
state_changed.clear();
dirty_widgets
};
for id in dirty_widgets.iter() {
if id.is_dropped(&self.arena) {
continue;
}
let mut relayout_root = *id;
if let Some(info) = self.store.data.get_mut(id) {
info.size.take();
}
for p in id.0.ancestors(&self.arena).skip(1).map(WidgetId) {
if self.store.layout_box_size(p).is_none() {
break;
}
relayout_root = p;
if let Some(info) = self.store.data.get_mut(&p) {
info.size.take();
}
let r = self.arena.get(p.0).unwrap().get();
if r.only_sized_by_parent() {
break;
}
}
needs_layout.push(relayout_root);
}
(!needs_layout.is_empty()).then(|| {
needs_layout.sort_by_cached_key(|w| Reverse(w.ancestors(&self.arena).count()));
needs_layout
})
}
}
impl Default for BoxClamp {
fn default() -> Self {
Self {
min: Size::new(0., 0.),
max: Size::new(f32::INFINITY, f32::INFINITY),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{impl_query_self_only, prelude::*, test_helper::*};
use ribir_dev_helper::*;
use std::{cell::RefCell, rc::Rc};
#[derive(Declare, Clone, SingleChild)]
struct OffsetBox {
pub offset: Point,
pub size: Size,
}
impl Render for OffsetBox {
fn perform_layout(&self, mut clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size {
clamp.max = clamp.max.min(self.size);
let mut layouter = ctx.assert_single_child_layouter();
layouter.perform_widget_layout(clamp);
layouter.update_position(self.offset);
self.size
}
#[inline]
fn only_sized_by_parent(&self) -> bool { true }
#[inline]
fn paint(&self, _: &mut PaintingCtx) {}
}
impl Query for OffsetBox {
impl_query_self_only!();
}
#[test]
fn fix_incorrect_relayout_root() {
let child_box = Stateful::new(MockBox { size: Size::zero() });
let root_layout_cnt = Stateful::new(0);
let w = widget! {
states {
child_box: child_box.clone(),
root_layout_cnt: root_layout_cnt.clone(),
}
MockMulti {
on_performed_layout: move |_| *root_layout_cnt += 1,
DynWidget {
dyns: if child_box.size.is_empty() {
MockBox { size: Size::new(1., 1.) }.into_widget()
} else {
child_box.clone_stateful().into_widget()
}
}
}
};
let app_ctx = <_>::default();
let scheduler = FuturesLocalSchedulerPool::default().spawner();
let mut tree = WidgetTree::new(w, WindowCtx::new(app_ctx, scheduler));
tree.layout(Size::zero());
assert_eq!(*root_layout_cnt.state_ref(), 1);
{
child_box.state_ref().size = Size::new(2., 2.);
}
tree.layout(Size::zero());
assert_eq!(*root_layout_cnt.state_ref(), 2);
}
#[test]
fn layout_list_from_root_to_leaf() {
let layout_order = Stateful::new(vec![]);
let trigger = Stateful::new(Size::zero());
let w = widget! {
states {
layout_order: layout_order.clone(),
trigger: trigger.clone()
}
MockBox {
size: *trigger,
on_performed_layout: move |_| layout_order.push(1),
MockBox {
size: *trigger,
on_performed_layout: move |_| layout_order.push(2),
MockBox {
size: *trigger,
on_performed_layout: move |_| layout_order.push(3),
}
}
}
};
let mut wnd = TestWindow::new(w);
wnd.draw_frame();
assert_eq!([3, 2, 1], &**layout_order.state_ref());
{
*trigger.state_ref() = Size::new(1., 1.);
}
wnd.draw_frame();
assert_eq!([3, 2, 1, 3, 2, 1], &**layout_order.state_ref());
}
#[test]
fn relayout_size() {
let trigger = Stateful::new(Size::zero());
let w = widget! {
states {trigger: trigger.clone()}
OffsetBox {
size: Size::new(100., 100.),
offset: Point::new(50., 50.),
MockBox {
size: Size::new(50., 50.),
MockBox {
size: *trigger,
}
}
}
};
let mut wnd = TestWindow::new(w);
wnd.draw_frame();
assert_layout_result_by_path!(
wnd,
{ path = [0, 0], rect == ribir_geom::rect(50., 50., 50., 50.),}
);
assert_layout_result_by_path!(
wnd,
{path = [0, 0, 0], rect == ribir_geom::rect(0., 0., 0., 0.),}
);
{
*trigger.state_ref() = Size::new(10., 10.);
}
wnd.draw_frame();
assert_layout_result_by_path!(
wnd,
{path = [0, 0], rect == ribir_geom::rect(50., 50., 50., 50.),}
);
assert_layout_result_by_path!(
wnd,
{path = [0, 0, 0], rect == ribir_geom::rect(0., 0., 10., 10.),}
);
}
#[test]
fn relayout_from_parent() {
let trigger = Stateful::new(Size::zero());
let cnt = Rc::new(RefCell::new(0));
let cnt2 = cnt.clone();
let w = widget! {
states {trigger: trigger.clone()}
init { let cnt = cnt2; }
MockBox {
size: Size::new(50., 50.),
on_performed_layout: move |_| *cnt.borrow_mut() += 1,
MockBox {
size: *trigger,
}
}
};
let mut wnd = TestWindow::new(w);
wnd.draw_frame();
assert_eq!(*cnt.borrow(), 1);
{
*trigger.state_ref() = Size::new(10., 10.);
}
wnd.draw_frame();
assert_eq!(*cnt.borrow(), 2);
}
#[test]
fn layout_visit_prev_position() {
#[derive(Declare)]
struct MockWidget {
pos: RefCell<Point>,
size: Size,
}
impl Render for MockWidget {
fn perform_layout(&self, _: BoxClamp, ctx: &mut LayoutCtx) -> Size {
*self.pos.borrow_mut() = ctx
.layout_store()
.layout_box_position(ctx.id)
.unwrap_or_default();
self.size
}
#[inline]
fn only_sized_by_parent(&self) -> bool { true }
#[inline]
fn paint(&self, _: &mut PaintingCtx) {}
}
impl Query for MockWidget {
impl_query_self_only!();
}
let pos = Rc::new(RefCell::new(Point::zero()));
let pos2 = pos.clone();
let trigger = Stateful::new(Size::zero());
let w = widget! {
states {trigger: trigger.clone()}
init {
let pos = pos2.clone();
}
MockMulti {
MockBox{
size: Size::new(50., 50.),
}
MockWidget {
id: w,
size: *trigger,
pos: RefCell::new(Point::zero()),
on_performed_layout: move |_| {
*pos.borrow_mut() = *w.pos.borrow();
}
}
}
};
let mut wnd = TestWindow::new(w);
wnd.draw_frame();
*trigger.state_ref() = Size::new(1., 1.);
wnd.draw_frame();
assert_eq!(*pos.borrow(), Point::new(50., 0.));
}
}