use crate::{prelude::*, ticker::FrameMsg};
#[derive(Default)]
pub struct GlobalAnchor {
pub global_anchor: Anchor,
}
impl Declare for GlobalAnchor {
type Builder = FatObj<()>;
#[inline]
fn declarer() -> Self::Builder { FatObj::new(()) }
}
impl<'c> ComposeChild<'c> for GlobalAnchor {
type Child = Widget<'c>;
fn compose_child(this: impl StateWriter<Value = Self>, child: Self::Child) -> Widget<'c> {
let modifies = this.raw_modifies();
fn_widget! {
let wnd = BuildCtx::get().window();
let tick_of_layout_ready = wnd
.frame_tick_stream()
.filter(|msg| matches!(msg, FrameMsg::LayoutReady(_)));
let mut child = FatObj::new(child);
let track_id = $child.track_id();
let u = watch!(($this.get_global_anchor(), $child.layout_size()))
.sample(tick_of_layout_ready)
.subscribe(move |(_, size)| {
let wnd_size = wnd.size();
let Anchor {x, y} = $this.get_global_anchor();
if let Some(cid) = track_id.get() {
let offset = wnd.widget_pos(cid).unwrap_or_else(Point::zero);
let base = wnd.map_to_global(Point::zero(), cid) - offset;
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 { on_disposed: move |_| { u.unsubscribe(); } }
}
.into_widget()
.on_build(move |id| id.dirty_on(modifies))
}
}
impl GlobalAnchor {
fn get_global_anchor(&self) -> Anchor { self.global_anchor }
}
fn bind_h_anchor(
this: &impl StateWriter<Value = GlobalAnchor>, wnd: &Sc<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: &Sc<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);
}
}
pub trait SetGlobalAnchor {
fn left_align_to(&self, wid: TrackId, offset: f32, wnd: Sc<Window>) -> impl Subscription;
fn right_align_to(&self, wid: TrackId, relative: f32, wnd: Sc<Window>) -> impl Subscription;
fn top_align_to(&self, wid: TrackId, relative: f32, wnd: Sc<Window>) -> impl Subscription;
fn bottom_align_to(&self, wid: TrackId, relative: f32, wnd: Sc<Window>) -> impl Subscription;
}
impl<T> SetGlobalAnchor for T
where
T: StateWriter<Value = GlobalAnchor>,
{
fn left_align_to(&self, track_id: TrackId, offset: f32, wnd: Sc<Window>) -> impl Subscription {
let tick_of_layout_ready = wnd
.frame_tick_stream()
.filter(|msg| matches!(msg, FrameMsg::LayoutReady(_)));
let this = self.clone_writer();
tick_of_layout_ready.subscribe(move |_| {
if let Some(wid) = track_id.get() {
let base = wnd.map_to_global(Point::zero(), wid).x;
bind_h_anchor(&this, &wnd, HAnchor::Left(offset), base);
}
})
}
fn right_align_to(&self, track_id: TrackId, relative: f32, wnd: Sc<Window>) -> impl Subscription {
let this = self.clone_writer();
let tick_of_layout_ready = wnd
.frame_tick_stream()
.filter(|msg| matches!(msg, FrameMsg::LayoutReady(_)));
tick_of_layout_ready.subscribe(move |_| {
if let Some(wid) = track_id.get() {
let base = wnd.map_to_global(Point::zero(), wid).x;
let size = wnd.widget_size(wid).unwrap_or_default();
bind_h_anchor(&this, &wnd, HAnchor::Right(relative), base + size.width);
}
})
}
fn top_align_to(&self, track_id: TrackId, relative: f32, wnd: Sc<Window>) -> impl Subscription {
let tick_of_layout_ready = wnd
.frame_tick_stream()
.filter(|msg| matches!(msg, FrameMsg::LayoutReady(_)));
let this = self.clone_writer();
tick_of_layout_ready.subscribe(move |_| {
if let Some(wid) = track_id.get() {
let base = wnd.map_to_global(Point::zero(), wid).y;
bind_v_anchor(&this, &wnd, VAnchor::Top(relative), base);
}
})
}
fn bottom_align_to(
&self, track_id: TrackId, relative: f32, wnd: Sc<Window>,
) -> impl Subscription {
let tick_of_layout_ready = wnd
.frame_tick_stream()
.filter(|msg| matches!(msg, FrameMsg::LayoutReady(_)));
let this = self.clone_writer();
tick_of_layout_ready.subscribe(move |_| {
if let Some(wid) = track_id.get() {
let base = wnd.map_to_global(Point::zero(), wid).y;
let size = wnd.widget_size(wid).unwrap_or_default();
bind_v_anchor(&this, &wnd, VAnchor::Bottom(relative), base + size.height);
}
})
}
}
#[cfg(test)]
mod tests {
use ribir_dev_helper::*;
use super::*;
use crate::test_helper::*;
const WND_SIZE: Size = Size::new(100., 100.);
widget_layout_test!(
global_anchor,
WidgetTester::new(fn_widget! {
let mut parent = @MockBox {
anchor: Anchor::left_top(10., 10.),
size: Size::new(50., 50.),
};
let mut top_left = @MockBox {
size: Size::new(10., 10.),
};
let top_left_anchor = top_left.get_global_anchor_widget().clone_writer();
let mut bottom_right = @MockBox {
size: Size::new(10., 10.),
};
let bottom_right_anchor = bottom_right.get_global_anchor_widget().clone_writer();
@ $parent {
on_mounted: move|e| {
let track_id = $parent.track_id();
top_left_anchor.left_align_to(track_id.clone(), 20., e.window());
top_left_anchor.top_align_to(track_id.clone(), 10., e.window());
bottom_right_anchor.right_align_to(track_id.clone(), 10., e.window());
bottom_right_anchor.bottom_align_to(track_id, 20., e.window());
},
@MockStack {
@ { top_left }
@ { bottom_right }
}
}
})
.with_wnd_size(WND_SIZE),
LayoutCase::new(&[0, 0, 0]).with_pos(Point::new(20., 10.)),
LayoutCase::new(&[0, 0, 1]).with_pos(Point::new(30., 20.))
);
}