use std::ptr::NonNull;
use ribir_algo::Rc;
use ribir_types::{Point, Rect, Size};
use crate::{
prelude::ProviderCtx,
query::QueryRef,
state::WriteRef,
widget::{BoxClamp, WidgetTree},
widget_tree::WidgetId,
window::Window,
};
pub trait WidgetCtx {
fn widget_id(&self) -> WidgetId;
fn parent(&self) -> Option<WidgetId>;
fn ancestor_of(&self, w: WidgetId) -> bool;
fn successor_of(&self, w: WidgetId) -> bool;
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 children(&self) -> impl Iterator<Item = WidgetId> + '_;
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 widget_box_pos(&self, wid: WidgetId) -> Option<Point>;
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_all_iter<T: 'static>(&self) -> impl DoubleEndedIterator<Item = QueryRef<'_, T>>;
fn query<T: 'static>(&self) -> Option<QueryRef<'_, T>>;
fn query_write<T: 'static>(&self) -> Option<WriteRef<'_, T>>;
fn query_of_widget<T: 'static>(&self, w: WidgetId) -> Option<QueryRef<'_, T>>;
fn query_write_of_widget<T: 'static>(&self, w: WidgetId) -> Option<WriteRef<'_, T>>;
fn window(&self) -> Rc<Window>;
}
pub(crate) trait WidgetCtxImpl {
fn id(&self) -> WidgetId;
fn tree(&self) -> &WidgetTree;
fn split_tree(&mut self) -> (&mut Self, &WidgetTree) {
let tree = unsafe { &*(self.tree() as *const WidgetTree) };
(self, tree)
}
}
impl<T: WidgetCtxImpl> WidgetCtx for T {
#[inline]
fn widget_id(&self) -> WidgetId { self.id() }
#[inline]
fn parent(&self) -> Option<WidgetId> { self.id().parent(self.tree()) }
#[inline]
fn ancestor_of(&self, w: WidgetId) -> bool { self.id().ancestor_of(w, self.tree()) }
#[inline]
fn successor_of(&self, w: WidgetId) -> bool { w.ancestor_of(self.id(), self.tree()) }
#[inline]
fn widget_parent(&self, w: WidgetId) -> Option<WidgetId> { w.parent(self.tree()) }
#[inline]
#[track_caller]
fn single_child(&self) -> Option<WidgetId> { self.id().single_child(self.tree()) }
#[inline]
fn first_child(&self) -> Option<WidgetId> { self.id().first_child(self.tree()) }
#[inline]
fn children(&self) -> impl Iterator<Item = WidgetId> + '_ { self.id().children(self.tree()) }
#[inline]
fn box_rect(&self) -> Option<Rect> { self.widget_box_rect(self.id()) }
#[inline]
fn box_pos(&self) -> Option<Point> { self.widget_box_pos(self.id()) }
#[inline]
fn box_size(&self) -> Option<Size> { self.widget_box_size(self.id()) }
fn layout_clamp(&self) -> Option<BoxClamp> {
self
.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
.tree()
.layout_info(wid)
.and_then(|info| info.size)
}
fn widget_box_pos(&self, wid: WidgetId) -> Option<Point> {
self.tree().layout_info(wid).map(|info| info.pos)
}
fn widget_box_rect(&self, wid: WidgetId) -> Option<Rect> {
self
.tree()
.layout_info(wid)
.and_then(|info| info.size.map(|size| Rect::new(info.pos, size)))
}
fn map_to_global(&self, pos: Point) -> Point { self.tree().map_to_global(pos, self.id()) }
fn map_from_global(&self, pos: Point) -> Point { self.tree().map_from_global(pos, self.id()) }
fn map_to_parent(&self, pos: Point) -> Point { self.tree().map_to_parent(self.id(), pos) }
fn map_from_parent(&self, pos: Point) -> Point { self.tree().map_from_parent(self.id(), pos) }
fn map_to(&self, pos: Point, w: WidgetId) -> Point {
let global = self.map_to_global(pos);
self.tree().map_from_global(global, w)
}
fn map_from(&self, pos: Point, w: WidgetId) -> Point {
let global = self.tree().map_to_global(pos, w);
self.map_from_global(global)
}
fn query_all_iter<Q: 'static>(&self) -> impl DoubleEndedIterator<Item = QueryRef<'_, Q>> {
self.id().query_all_iter(self.tree())
}
fn query<Q: 'static>(&self) -> Option<QueryRef<'_, Q>> { self.query_of_widget::<Q>(self.id()) }
#[inline]
fn query_write<Q: 'static>(&self) -> Option<WriteRef<'_, Q>> {
self.query_write_of_widget::<Q>(self.id())
}
fn query_of_widget<Q: 'static>(&self, w: WidgetId) -> Option<QueryRef<'_, Q>> {
w.query_ref::<Q>(self.tree())
}
fn query_write_of_widget<Q: 'static>(&self, w: WidgetId) -> Option<WriteRef<'_, Q>> {
w.query_write(self.tree())
}
fn window(&self) -> Rc<Window> { self.tree().window() }
}
pub struct HitTestCtx {
id: WidgetId,
tree: NonNull<WidgetTree>,
provider_ctx: ProviderCtx,
}
impl WidgetCtxImpl for HitTestCtx {
#[inline]
fn id(&self) -> WidgetId { self.id }
fn tree(&self) -> &WidgetTree { unsafe { self.tree.as_ref() } }
}
impl HitTestCtx {
pub(crate) fn new(tree: NonNull<WidgetTree>) -> Self {
let id = unsafe { tree.as_ref() }.root();
let provider_ctx = ProviderCtx::default();
Self { id, tree, provider_ctx }
}
pub fn box_hit_test(&self, pos: Point) -> bool {
self
.box_rect()
.is_some_and(|rect| rect.contains(pos))
}
pub(crate) fn set_id(&mut self, id: WidgetId) { self.id = id; }
pub(crate) fn finish(&mut self) { self.provider_ctx.pop_providers_for(self.id); }
}
impl AsRef<ProviderCtx> for HitTestCtx {
fn as_ref(&self) -> &ProviderCtx { &self.provider_ctx }
}
impl AsMut<ProviderCtx> for HitTestCtx {
fn as_mut(&mut self) -> &mut ProviderCtx { &mut self.provider_ctx }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{prelude::*, reset_test_env, test_helper::*};
#[test]
fn map_self_eq_self() {
reset_test_env!();
let w = fn_widget! {
@MockBox {
size: Size::zero(),
margin: EdgeInsets::all(2.),
}
};
let wnd = TestWindow::from_widget(w);
wnd.draw_frame();
let tree = wnd.tree();
let root = tree.root();
let pos = Point::zero();
let child = root.single_child(tree).unwrap();
let mut w_ctx = HitTestCtx::new(wnd.tree);
w_ctx.set_id(child);
assert_eq!(w_ctx.map_from(pos, child), pos);
assert_eq!(w_ctx.map_to(pos, child), pos);
}
#[test]
fn map_transform_test() {
reset_test_env!();
let w = fn_widget! {
@MockBox {
size: Size::new(100., 100.),
@MockBox {
transform: Transform::scale(0.5, 0.5),
x: 30., y: 30.,
size: Size::new(40., 40.)
}
}
};
let wnd = TestWindow::new_with_size(w, Size::new(100., 100.));
wnd.draw_frame();
let root = wnd.tree().root();
let child = get_single_child_by_depth(root, wnd.tree(), 2);
let w_ctx = HitTestCtx::new(wnd.tree);
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: &WidgetTree, mut depth: u32) -> WidgetId {
let mut child = id;
while depth > 0 {
child = child.single_child(tree).unwrap();
depth -= 1;
}
child
}
}