ribir_core/builtin_widgets/
global_anchor.rs1use std::rc::Rc;
2
3use crate::{prelude::*, ticker::FrameMsg};
4
5#[derive(Query, Default)]
6pub struct GlobalAnchor {
7 pub global_anchor: Anchor,
8}
9
10impl Declare for GlobalAnchor {
11 type Builder = FatObj<()>;
12 #[inline]
13 fn declarer() -> Self::Builder { FatObj::new(()) }
14}
15
16impl ComposeChild for GlobalAnchor {
17 type Child = Widget;
18 #[inline]
19 fn compose_child(this: impl StateWriter<Value = Self>, child: Self::Child) -> impl WidgetBuilder {
20 fn_widget! {
21 let wnd = ctx!().window();
22 let tick_of_layout_ready = wnd
23 .frame_tick_stream()
24 .filter(|msg| matches!(msg, FrameMsg::LayoutReady(_)));
25
26 let mut child = @$child {};
27 let wid = child.lazy_id();
28 let u = watch!(($this.get_global_anchor(), $child.layout_size()))
29 .sample(tick_of_layout_ready)
30 .subscribe(move |(_, size)| {
31 let wnd_size = wnd.size();
32 let base = wnd.map_to_global(Point::zero(), wid.assert_id());
33 let Anchor {x, y} = $this.get_global_anchor();
35 let anchor = Anchor {
36 x: x.map(|x| match x {
37 HAnchor::Left(x) => x - base.x,
38 HAnchor::Right(x) => (wnd_size.width - x) - size.width - base.x,
39 }).map(HAnchor::Left),
40 y: y.map(|y| match y {
41 VAnchor::Top(y) => y - base.y,
42 VAnchor::Bottom(y) => (wnd_size.height - y) - size.height - base.y,
43 }).map(VAnchor::Top),
44 };
45 if $child.anchor != anchor {
46 $child.write().anchor = anchor;
47 }
48 });
49
50 @ $child { on_disposed: move |_| { u.unsubscribe(); } }
51 }
52 }
53}
54
55impl GlobalAnchor {
56 fn get_global_anchor(&self) -> Anchor { self.global_anchor }
57}
58
59fn bind_h_anchor(
60 this: &impl StateWriter<Value = GlobalAnchor>, wnd: &Rc<Window>, relative: HAnchor, base: f32,
61) {
62 let size = wnd.size();
63 let anchor = match relative {
64 HAnchor::Left(x) => HAnchor::Left(base + x),
65 HAnchor::Right(x) => HAnchor::Right((size.width - base) + x),
66 };
67 if this.read().global_anchor.x != Some(anchor) {
68 this.write().global_anchor.x = Some(anchor);
69 }
70}
71
72fn bind_v_anchor(
73 this: &impl StateWriter<Value = GlobalAnchor>, wnd: &Rc<Window>, relative: VAnchor, base: f32,
74) {
75 let size = wnd.size();
76 let anchor = match relative {
77 VAnchor::Top(x) => VAnchor::Top(base + x),
78 VAnchor::Bottom(x) => VAnchor::Bottom((size.height - base) + x),
79 };
80 if this.read().global_anchor.y != Some(anchor) {
81 this.write().global_anchor.y = Some(anchor);
82 }
83}
84
85impl<W> FatObj<W> {
86 pub fn left_align_to(
90 &mut self, wid: &LazyWidgetId, offset: f32, ctx: &BuildCtx,
91 ) -> impl Subscription {
92 let this = self.get_global_anchor_widget().clone_writer();
93 let wnd = ctx.window();
94 let wid = wid.clone();
95 let tick_of_layout_ready = wnd
96 .frame_tick_stream()
97 .filter(|msg| matches!(msg, FrameMsg::LayoutReady(_)));
98 tick_of_layout_ready.subscribe(move |_| {
99 let base = wnd
100 .map_to_global(Point::zero(), wid.assert_id())
101 .x;
102 bind_h_anchor(&this, &wnd, HAnchor::Left(offset), base);
103 })
104 }
105
106 pub fn right_align_to(
110 &mut self, wid: &LazyWidgetId, relative: f32, ctx: &BuildCtx,
111 ) -> impl Subscription {
112 let this = self.get_global_anchor_widget().clone_writer();
113 let wnd = ctx.window();
114 let wid = wid.clone();
115 let tick_of_layout_ready = wnd
116 .frame_tick_stream()
117 .filter(|msg| matches!(msg, FrameMsg::LayoutReady(_)));
118 tick_of_layout_ready.subscribe(move |_| {
119 let base = wnd
120 .map_to_global(Point::zero(), wid.assert_id())
121 .x;
122 let size = wnd
123 .layout_size(wid.assert_id())
124 .unwrap_or_default();
125 bind_h_anchor(&this, &wnd, HAnchor::Right(relative), base + size.width);
126 })
127 }
128
129 pub fn top_align_to(
133 &mut self, wid: &LazyWidgetId, relative: f32, ctx: &BuildCtx,
134 ) -> impl Subscription {
135 let this = self.get_global_anchor_widget().clone_writer();
136 let wnd = ctx.window();
137 let wid = wid.clone();
138 let tick_of_layout_ready = wnd
139 .frame_tick_stream()
140 .filter(|msg| matches!(msg, FrameMsg::LayoutReady(_)));
141 tick_of_layout_ready.subscribe(move |_| {
142 let base = wnd
143 .map_to_global(Point::zero(), wid.assert_id())
144 .y;
145 bind_v_anchor(&this, &wnd, VAnchor::Top(relative), base);
146 })
147 }
148
149 pub fn bottom_align_to(
153 &mut self, wid: &LazyWidgetId, relative: f32, ctx: &BuildCtx,
154 ) -> impl Subscription {
155 let this = self.get_global_anchor_widget().clone_writer();
156 let wnd = ctx.window();
157 let wid = wid.clone();
158 let tick_of_layout_ready = wnd
159 .frame_tick_stream()
160 .filter(|msg| matches!(msg, FrameMsg::LayoutReady(_)));
161 tick_of_layout_ready.subscribe(move |_| {
162 let base = wnd
163 .map_to_global(Point::zero(), wid.assert_id())
164 .y;
165 let size = wnd
166 .layout_size(wid.assert_id())
167 .unwrap_or_default();
168 bind_v_anchor(&this, &wnd, VAnchor::Bottom(relative), base + size.height);
169 })
170 }
171}
172#[cfg(test)]
173mod tests {
174 use ribir_dev_helper::*;
175
176 use super::*;
177 use crate::test_helper::*;
178
179 const WND_SIZE: Size = Size::new(100., 100.);
180 fn global_anchor() -> impl WidgetBuilder {
181 fn_widget! {
182 let parent = @MockBox {
183 anchor: Anchor::left_top(10., 10.),
184 size: Size::new(50., 50.),
185 };
186 let wid = parent.lazy_host_id();
187 let mut top_left = @MockBox {
188 size: Size::new(10., 10.),
189 };
190 top_left.left_align_to(&wid, 20., ctx!());
191 top_left.top_align_to(&wid, 10., ctx!());
192
193 let mut bottom_right = @MockBox {
194 size: Size::new(10., 10.),
195 };
196 bottom_right.right_align_to(&wid, 10., ctx!());
197 bottom_right.bottom_align_to(&wid, 20., ctx!());
198 @ $parent {
199 @MockStack {
200 child_pos: vec![Point::new(0., 0.), Point::new(0., 0.)],
201 @ { top_left }
202 @ { bottom_right }
203 }
204 }
205 }
206 }
207
208 widget_layout_test!(
209 global_anchor,
210 wnd_size = WND_SIZE,
211 { path = [0, 0, 0, 0, 0], x == 20.,}
212 { path = [0, 0, 0, 0, 0], y == 10.,}
213
214 { path = [0, 0, 0, 1, 0], x == 30.,}
215 { path = [0, 0, 0, 1, 0], y == 20.,}
216 );
217}