use crate::{
prelude::AppCtx,
widget::{BoxClamp, WidgetTree},
widget_tree::WidgetId,
window::{Window, WindowId},
};
use ribir_geom::{Point, Rect, Size};
use std::rc::Rc;
pub trait WidgetCtx {
fn parent(&self) -> Option<WidgetId>;
fn widget_parent(&self, w: WidgetId) -> Option<WidgetId>;
fn single_child(&self) -> Option<WidgetId>;
#[inline]
fn assert_single_child(&self) -> WidgetId { self.single_child().expect("Must have one child.") }
fn has_child(&self) -> bool { self.first_child().is_some() }
fn first_child(&self) -> Option<WidgetId>;
fn single_child_box(&self) -> Option<Rect>;
fn box_rect(&self) -> Option<Rect>;
fn box_size(&self) -> Option<Size>;
fn box_pos(&self) -> Option<Point>;
fn layout_clamp(&self) -> Option<BoxClamp>;
fn widget_box_size(&self, wid: WidgetId) -> Option<Size>;
fn widget_box_rect(&self, wid: WidgetId) -> Option<Rect>;
fn map_to_global(&self, pos: Point) -> Point;
fn map_from_global(&self, pos: Point) -> Point;
fn map_to_parent(&self, pos: Point) -> Point;
fn map_from_parent(&self, pos: Point) -> Point;
fn map_to(&self, pos: Point, w: WidgetId) -> Point;
fn map_from(&self, pos: Point, w: WidgetId) -> Point;
fn query_type<W: 'static, R>(&self, callback: impl FnOnce(&W) -> R) -> Option<R>;
fn query_widget_type<W: 'static, R>(
&self,
id: WidgetId,
callback: impl FnOnce(&W) -> R,
) -> Option<R>;
fn window(&self) -> Rc<Window>;
}
pub(crate) trait WidgetCtxImpl {
fn id(&self) -> WidgetId;
fn current_wnd(&self) -> Rc<Window>;
#[inline]
fn with_tree<F: FnOnce(&WidgetTree) -> R, R>(&self, f: F) -> R {
f(&self.current_wnd().widget_tree.borrow())
}
}
impl<T: WidgetCtxImpl> WidgetCtx for T {
#[inline]
fn parent(&self) -> Option<WidgetId> { self.with_tree(|tree| self.id().parent(&tree.arena)) }
#[inline]
fn widget_parent(&self, w: WidgetId) -> Option<WidgetId> {
self.with_tree(|tree| w.parent(&tree.arena))
}
#[inline]
fn single_child(&self) -> Option<WidgetId> {
self.with_tree(|tree| self.id().single_child(&tree.arena))
}
#[inline]
fn first_child(&self) -> Option<WidgetId> {
self.with_tree(|tree| self.id().first_child(&tree.arena))
}
#[inline]
fn box_rect(&self) -> Option<Rect> { self.widget_box_rect(self.id()) }
#[inline]
fn box_pos(&self) -> Option<Point> {
self.with_tree(|tree| tree.store.layout_info(self.id()).map(|info| info.pos))
}
#[inline]
fn box_size(&self) -> Option<Size> { self.widget_box_size(self.id()) }
fn layout_clamp(&self) -> Option<BoxClamp> {
self.with_tree(|tree| tree.store.layout_info(self.id()).map(|info| info.clamp))
}
fn single_child_box(&self) -> Option<Rect> {
self.single_child().and_then(|c| self.widget_box_rect(c))
}
fn widget_box_size(&self, wid: WidgetId) -> Option<Size> {
self.with_tree(|tree| tree.store.layout_info(wid).and_then(|info| info.size))
}
fn widget_box_rect(&self, wid: WidgetId) -> Option<Rect> {
self.with_tree(|tree| {
tree
.store
.layout_info(wid)
.and_then(|info| info.size.map(|size| Rect::new(info.pos, size)))
})
}
fn map_to_global(&self, pos: Point) -> Point {
self.with_tree(|tree| tree.store.map_to_global(pos, self.id(), &tree.arena))
}
fn map_from_global(&self, pos: Point) -> Point {
self.with_tree(|tree| tree.store.map_from_global(pos, self.id(), &tree.arena))
}
fn map_to_parent(&self, pos: Point) -> Point {
self.with_tree(|tree| tree.store.map_to_parent(self.id(), pos, &tree.arena))
}
fn map_from_parent(&self, pos: Point) -> Point {
self.with_tree(|tree| tree.store.map_from_parent(self.id(), pos, &tree.arena))
}
fn map_to(&self, pos: Point, w: WidgetId) -> Point {
let global = self.map_to_global(pos);
self.with_tree(|tree| tree.store.map_from_global(global, w, &tree.arena))
}
fn map_from(&self, pos: Point, w: WidgetId) -> Point {
let global = self.with_tree(|tree| tree.store.map_to_global(pos, w, &tree.arena));
self.map_from_global(global)
}
#[inline]
fn query_type<W: 'static, R>(&self, callback: impl FnOnce(&W) -> R) -> Option<R> {
self.query_widget_type(self.id(), callback)
}
fn query_widget_type<W: 'static, R>(
&self,
id: WidgetId,
callback: impl FnOnce(&W) -> R,
) -> Option<R> {
self.with_tree(|tree| id.assert_get(&tree.arena).query_most_outside(callback))
}
fn window(&self) -> Rc<Window> { self.current_wnd() }
}
macro_rules! define_widget_context {
(
$(#[$outer:meta])*
$name: ident $(, $extra_name: ident: $extra_ty: ty)*
) => {
$(#[$outer])*
pub struct $name {
pub(crate) id: WidgetId,
pub(crate) wnd_id: WindowId,
$(pub(crate) $extra_name: $extra_ty,)*
}
impl WidgetCtxImpl for $name {
#[inline]
fn id(&self) -> WidgetId { self.id }
#[inline]
fn current_wnd(&self) -> Rc<Window> { AppCtx::get_window_assert(self.wnd_id) }
}
};
}
pub(crate) use define_widget_context;
define_widget_context!(HitTestCtx);
#[cfg(test)]
mod tests {
use super::*;
use crate::{
prelude::*,
test_helper::{MockBox, TestWindow},
};
define_widget_context!(TestCtx);
#[test]
fn map_self_eq_self() {
let _guard = unsafe { AppCtx::new_lock_scope() };
let w = fn_widget! {
@MockBox {
size: Size::zero(),
margin: EdgeInsets::all(2.),
}
};
let mut wnd = TestWindow::new(w);
wnd.draw_frame();
let tree = &wnd.widget_tree.borrow();
let root = tree.root();
let pos = Point::zero();
let child = root.single_child(&tree.arena).unwrap();
let w_ctx = TestCtx { id: child, wnd_id: wnd.id() };
assert_eq!(w_ctx.map_from(pos, child), pos);
assert_eq!(w_ctx.map_to(pos, child), pos);
}
#[test]
fn map_transform_test() {
let _guard = unsafe { AppCtx::new_lock_scope() };
let w = fn_widget! {
@MockBox {
size: Size::new(100., 100.),
@MockBox {
transform: Transform::scale(0.5, 0.5),
anchor: Anchor::left_top(30., 30.),
size: Size::new(40., 40.)
}
}
};
let mut wnd = TestWindow::new_with_size(w, Size::new(100., 100.));
wnd.draw_frame();
let root = wnd.widget_tree.borrow().root();
let child = get_single_child_by_depth(root, &wnd.widget_tree.borrow().arena, 3);
let w_ctx = TestCtx { id: root, wnd_id: wnd.id() };
let from_pos = Point::new(30., 30.);
assert_eq!(w_ctx.map_from(from_pos, child), Point::new(45., 45.));
let to_pos = Point::new(50., 50.);
assert_eq!(w_ctx.map_to(to_pos, child), Point::new(40., 40.));
}
fn get_single_child_by_depth(id: WidgetId, tree: &TreeArena, mut depth: u32) -> WidgetId {
let mut child = id;
while depth > 0 {
child = child.single_child(tree).unwrap();
depth -= 1;
}
child
}
}