use crate::{prelude::*, ticker::FrameMsg};
use std::rc::Rc;
#[derive(Declare, Query, Default)]
pub struct GlobalAnchor {
#[declare(builtin, default)]
global_anchor: Anchor,
}
impl ComposeChild for GlobalAnchor {
type Child = Widget;
#[inline]
fn compose_child(this: impl StateWriter<Value = Self>, child: Self::Child) -> impl WidgetBuilder {
fn_widget! {
let wnd = ctx!().window();
let tick_of_layout_ready = wnd
.frame_tick_stream()
.filter(|msg| matches!(msg, FrameMsg::LayoutReady(_)));
let mut child = @$child {};
let wid = child.lazy_id();
watch!(($this.get_global_anchor(), $child.layout_size()))
.sample(tick_of_layout_ready)
.subscribe(move |_| {
let size = $child.layout_size();
let wnd_size = wnd.size();
let base = wnd.map_to_global(Point::zero(), wid.assert_id());
let Anchor {x, y} = $this.get_global_anchor();
let anchor = Anchor {
x: x.map(|x| match x {
HAnchor::Left(x) => x - base.x,
HAnchor::Right(x) => (wnd_size.width - x) - size.width - base.x,
}).map(HAnchor::Left),
y: y.map(|y| match y {
VAnchor::Top(y) => y - base.y,
VAnchor::Bottom(y) => (wnd_size.height - y) - size.height - base.y,
}).map(VAnchor::Top),
};
if $child.anchor != anchor {
$child.write().anchor = anchor;
}
});
child.widget_build(ctx!())
}
}
}
impl GlobalAnchor {
fn get_global_anchor(&self) -> Anchor { self.global_anchor }
}
fn bind_h_anchor(
this: &impl StateWriter<Value = GlobalAnchor>,
wnd: &Rc<Window>,
relative: HAnchor,
base: f32,
) {
let size = wnd.size();
let anchor = match relative {
HAnchor::Left(x) => HAnchor::Left(base + x),
HAnchor::Right(x) => HAnchor::Right((size.width - base) + x),
};
if this.read().global_anchor.x != Some(anchor) {
this.write().global_anchor.x = Some(anchor);
}
}
fn bind_v_anchor(
this: &impl StateWriter<Value = GlobalAnchor>,
wnd: &Rc<Window>,
relative: VAnchor,
base: f32,
) {
let size = wnd.size();
let anchor = match relative {
VAnchor::Top(x) => VAnchor::Top(base + x),
VAnchor::Bottom(x) => VAnchor::Bottom((size.height - base) + x),
};
if this.read().global_anchor.y != Some(anchor) {
this.write().global_anchor.y = Some(anchor);
}
}
impl<W> FatObj<W> {
pub fn left_align_to(
&mut self,
wid: &LazyWidgetId,
offset: f32,
ctx: &BuildCtx,
) -> impl Subscription {
let this = self.get_builtin_global_anchor(ctx).clone_writer();
let wnd = ctx.window();
let wid = wid.clone();
let tick_of_layout_ready = wnd
.frame_tick_stream()
.filter(|msg| matches!(msg, FrameMsg::LayoutReady(_)));
tick_of_layout_ready.subscribe(move |_| {
let base = wnd.map_to_global(Point::zero(), wid.assert_id()).x;
bind_h_anchor(&this, &wnd, HAnchor::Left(offset), base);
})
}
pub fn right_align_to(
&mut self,
wid: &LazyWidgetId,
relative: f32,
ctx: &BuildCtx,
) -> impl Subscription {
let this = self.get_builtin_global_anchor(ctx).clone_writer();
let wnd = ctx.window();
let wid = wid.clone();
let tick_of_layout_ready = wnd
.frame_tick_stream()
.filter(|msg| matches!(msg, FrameMsg::LayoutReady(_)));
tick_of_layout_ready.subscribe(move |_| {
let base = wnd.map_to_global(Point::zero(), wid.assert_id()).x;
let size = wnd.layout_size(wid.assert_id()).unwrap_or_default();
bind_h_anchor(&this, &wnd, HAnchor::Right(relative), base + size.width);
})
}
pub fn top_align_to(
&mut self,
wid: &LazyWidgetId,
relative: f32,
ctx: &BuildCtx,
) -> impl Subscription {
let this = self.get_builtin_global_anchor(ctx).clone_writer();
let wnd = ctx.window();
let wid = wid.clone();
let tick_of_layout_ready = wnd
.frame_tick_stream()
.filter(|msg| matches!(msg, FrameMsg::LayoutReady(_)));
tick_of_layout_ready.subscribe(move |_| {
let base = wnd.map_to_global(Point::zero(), wid.assert_id()).y;
bind_v_anchor(&this, &wnd, VAnchor::Top(relative), base);
})
}
pub fn bottom_align_to(
&mut self,
wid: &LazyWidgetId,
relative: f32,
ctx: &BuildCtx,
) -> impl Subscription {
let this = self.get_builtin_global_anchor(ctx).clone_writer();
let wnd = ctx.window();
let wid = wid.clone();
let tick_of_layout_ready = wnd
.frame_tick_stream()
.filter(|msg| matches!(msg, FrameMsg::LayoutReady(_)));
tick_of_layout_ready.subscribe(move |_| {
let base = wnd.map_to_global(Point::zero(), wid.assert_id()).y;
let size = wnd.layout_size(wid.assert_id()).unwrap_or_default();
bind_v_anchor(&this, &wnd, VAnchor::Bottom(relative), base + size.height);
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_helper::*;
use ribir_dev_helper::*;
const WND_SIZE: Size = Size::new(100., 100.);
fn global_anchor() -> impl WidgetBuilder {
fn_widget! {
let parent = @MockBox {
anchor: Anchor::left_top(10., 10.),
size: Size::new(50., 50.),
};
let wid = parent.lazy_host_id();
let mut top_left = @MockBox {
size: Size::new(10., 10.),
};
top_left.left_align_to(&wid, 20., ctx!());
top_left.top_align_to(&wid, 10., ctx!());
let mut bottom_right = @MockBox {
size: Size::new(10., 10.),
};
bottom_right.right_align_to(&wid, 10., ctx!());
bottom_right.bottom_align_to(&wid, 20., ctx!());
@ $parent {
@MockStack {
child_pos: vec![Point::new(0., 0.), Point::new(0., 0.)],
@ { top_left }
@ { bottom_right }
}
}
}
}
widget_layout_test!(
global_anchor,
wnd_size = WND_SIZE,
{ path = [0, 0, 0, 0, 0], x == 20.,}
{ path = [0, 0, 0, 0, 0], y == 10.,}
{ path = [0, 0, 0, 1, 0], x == 30.,}
{ path = [0, 0, 0, 1, 0], y == 20.,}
);
}