diff --git a/core/src/builtin_widgets.rs b/core/src/builtin_widgets.rs index 8164e84aa..846b6427e 100644 --- a/core/src/builtin_widgets.rs +++ b/core/src/builtin_widgets.rs @@ -170,10 +170,7 @@ impl_builtin_obj!( TransformWidget, HAlignWidget, VAlignWidget, - LeftAnchor, - RightAnchor, - TopAnchor, - BottomAnchor, + RelativeAnchor, GlobalAnchor, Visibility, Opacity, diff --git a/core/src/builtin_widgets/anchor.rs b/core/src/builtin_widgets/anchor.rs index 65ca30f19..cf57fd8c6 100644 --- a/core/src/builtin_widgets/anchor.rs +++ b/core/src/builtin_widgets/anchor.rs @@ -1,91 +1,186 @@ use crate::prelude::*; -/// Widget use to anchor child constraints with the left edge of parent widget. -#[derive(Declare, Query, SingleChild)] -pub struct LeftAnchor { - #[declare(builtin, default)] - pub left_anchor: f32, -} +/// Specifies the horizontal position you want to anchor the widget. +#[derive(Debug, Clone, Copy)] +pub enum HAnchor { + /// positions the widget's left edge x pixels to the right of the target's + /// left edge. + Left(f32), -/// Widget use to anchor child constraints with the right edge of parent widget. -#[derive(Declare, Query, SingleChild)] -pub struct RightAnchor { - #[declare(builtin, default)] - pub right_anchor: f32, + /// positions the widget's right edge x pixels to the left of the target's + /// right edge. + Right(f32), } -/// Widget use to anchor child constraints with the top edge of parent widget. -#[derive(Declare, Query, SingleChild)] -pub struct TopAnchor { - #[declare(builtin, default)] - pub top_anchor: f32, -} +/// Specifies the vertical position you want to anchor the widget. +#[derive(Debug, Clone, Copy)] +pub enum VAnchor { + /// positions the widget's top edge x pixels bellow the target's top edge. + Top(f32), -/// Widget use to anchor child constraints with the bottom edge of parent -/// widget. -#[derive(Declare, Query, SingleChild)] -pub struct BottomAnchor { - #[declare(builtin, default)] - pub bottom_anchor: f32, + /// positions the widget's bottom edge x pixels above the target's bottom + /// edge. + Bottom(f32), } -impl Render for LeftAnchor { - fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { - let mut layouter = ctx.assert_single_child_layouter(); - let child_size = layouter.perform_widget_layout(clamp); - let left = self.left_anchor; - layouter.update_position(Point::new(left, 0.)); - child_size +impl HAnchor { + pub fn map(self, f: impl FnOnce(f32) -> f32) -> Self { + match self { + HAnchor::Left(x) => HAnchor::Left(f(x)), + HAnchor::Right(x) => HAnchor::Right(f(x)), + } } +} - fn paint(&self, _: &mut PaintingCtx) {} - - fn hit_test(&self, _: &HitTestCtx, _: Point) -> HitTest { - HitTest { hit: false, can_hit_child: true } +impl VAnchor { + pub fn map(self, f: impl FnOnce(f32) -> f32) -> Self { + match self { + VAnchor::Top(x) => VAnchor::Top(f(x)), + VAnchor::Bottom(x) => VAnchor::Bottom(f(x)), + } } } -impl Render for RightAnchor { - fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { - let mut layouter = ctx.assert_single_child_layouter(); - let child_size = layouter.perform_widget_layout(clamp); - let right = self.right_anchor; - let x = clamp.max.width - child_size.width - right; - layouter.update_position(Point::new(x, 0.)); +impl PartialEq for HAnchor { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (HAnchor::Left(x1), HAnchor::Left(x2)) => (x1 - x2).abs() < f32::EPSILON, + (HAnchor::Right(x1), HAnchor::Right(x2)) => (x1 - x2).abs() < f32::EPSILON, + _ => false, + } + } +} - child_size +impl PartialEq for VAnchor { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (VAnchor::Top(y1), VAnchor::Top(y2)) => (y1 - y2).abs() < f32::EPSILON, + (VAnchor::Bottom(y1), VAnchor::Bottom(y2)) => (y1 - y2).abs() < f32::EPSILON, + _ => false, + } } +} - fn paint(&self, _: &mut PaintingCtx) {} +#[derive(Clone, Copy, Default, PartialEq)] +pub struct Anchor { + /// Specifies the horizontal position you want to anchor the widget, See + /// [`HAnchor`]!. if None, the widget is anchored by the parent + pub x: Option, - fn hit_test(&self, _: &HitTestCtx, _: Point) -> HitTest { - HitTest { hit: false, can_hit_child: true } + /// Specifies the vertical position you want to anchor the widget, See + /// [`VAnchor`]! if None, the widget is anchored by the parent + pub y: Option, +} + +impl Lerp for HAnchor { + fn lerp(&self, other: &Self, t: f32) -> Self { + match (self, other) { + (HAnchor::Left(x1), HAnchor::Left(x2)) => HAnchor::Left(x1.lerp(x2, t)), + (HAnchor::Right(x1), HAnchor::Right(x2)) => HAnchor::Right(x1.lerp(x2, t)), + _ => unreachable!(), + } } } -impl Render for TopAnchor { - fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { - let mut layouter = ctx.assert_single_child_layouter(); - let child_size = layouter.perform_widget_layout(clamp); - let top = self.top_anchor; - layouter.update_position(Point::new(0., top)); - child_size +impl Lerp for VAnchor { + fn lerp(&self, other: &Self, t: f32) -> Self { + match (self, other) { + (VAnchor::Top(y1), VAnchor::Top(y2)) => VAnchor::Top(y1.lerp(y2, t)), + (VAnchor::Bottom(y1), VAnchor::Bottom(y2)) => VAnchor::Bottom(y1.lerp(y2, t)), + _ => unreachable!(), + } } +} - fn paint(&self, _: &mut PaintingCtx) {} +impl Lerp for Anchor { + fn lerp(&self, other: &Self, t: f32) -> Self { + let x = match (self.x, other.x) { + (Some(x1), Some(x2)) => Some(x1.lerp(&x2, t)), + (Some(x1), None) => Some(x1.map(|x| x.lerp(&0., t))), + (None, Some(x1)) => Some(x1.map(|x| 0_f32.lerp(&x, t))), + _ => None, + }; - fn hit_test(&self, _: &HitTestCtx, _: Point) -> HitTest { - HitTest { hit: false, can_hit_child: true } + let y = match (self.y, other.y) { + (Some(y1), Some(y2)) => Some(y1.lerp(&y2, t)), + (Some(y1), None) => Some(y1.map(|y| y.lerp(&0., t))), + (None, Some(y1)) => Some(y1.map(|y| 0_f32.lerp(&y, t))), + _ => None, + }; + Self { x, y } } } -impl Render for BottomAnchor { +impl Anchor { + pub fn new(x: HAnchor, y: VAnchor) -> Self { Self { x: Some(x), y: Some(y) } } + + /// Return Anchor that positions the widget's left top corner to the position + pub fn from_point(pos: Point) -> Self { Self::new(HAnchor::Left(pos.x), VAnchor::Top(pos.y)) } + + /// Return Anchor that positions the widget's left edge x pixels to the right + /// of the target's left edge. + pub fn left(x: f32) -> Self { Self { x: Some(HAnchor::Left(x)), y: None } } + + /// Return Anchor that positions the widget's right edge x pixels to the left + /// of the target's right edge. + pub fn right(x: f32) -> Self { Self { x: Some(HAnchor::Right(x)), y: None } } + + /// Return Anchor that positions the widget's top edge x pixels bellow the + /// target's top edge. + pub fn top(y: f32) -> Self { Self { x: None, y: Some(VAnchor::Top(y)) } } + + /// Return Anchor that positions the widget's bottom edge x pixels above the + /// parent's bottom edge. + pub fn bottom(y: f32) -> Self { Self { x: None, y: Some(VAnchor::Bottom(y)) } } + + /// Return Anchor that positions the widget's left top corner to the position + /// x pixel right, y pixel bellow relative to the left top corner of + /// the target + pub fn left_top(x: f32, y: f32) -> Self { Self::new(HAnchor::Left(x), VAnchor::Top(y)) } + + /// Return Anchor that positions the widget's right top corner to the position + /// x pixel left, y pixel bellow relative to the right top corner of + /// the target + pub fn right_top(x: f32, y: f32) -> Self { Self::new(HAnchor::Right(x), VAnchor::Top(y)) } + + /// Return Anchor that positions the widget's left bottom corner to the + /// position x pixel right, y pixel above relative to the left bottom corner + /// of the target + pub fn left_bottom(x: f32, y: f32) -> Self { Self::new(HAnchor::Left(x), VAnchor::Bottom(y)) } + + /// Return Anchor that positions the widget's right bottom corner to the + /// position x pixel left, y pixel above relative to the right bottom corner + /// of the target + pub fn right_bottom(x: f32, y: f32) -> Self { Self::new(HAnchor::Right(x), VAnchor::Bottom(y)) } +} + +/// Widget use to anchor child constraints relative to parent widget. +#[derive(Declare, Query, SingleChild)] +pub struct RelativeAnchor { + #[declare(builtin, default)] + pub anchor: Anchor, +} + +impl Render for RelativeAnchor { fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { let mut layouter = ctx.assert_single_child_layouter(); let child_size = layouter.perform_widget_layout(clamp); - let bottom = self.bottom_anchor; - let y = clamp.max.height - child_size.height - bottom; - layouter.update_position(Point::new(0., y)); + + let Anchor { x, y } = self.anchor; + let x = x + .map(|x| match x { + HAnchor::Left(x) => x, + HAnchor::Right(x) => clamp.max.width - child_size.width - x, + }) + .unwrap_or_default(); + let y = y + .map(|y| match y { + VAnchor::Top(y) => y, + VAnchor::Bottom(y) => clamp.max.height - child_size.height - y, + }) + .unwrap_or_default(); + + layouter.update_position(Point::new(x, y)); child_size } @@ -95,6 +190,7 @@ impl Render for BottomAnchor { HitTest { hit: false, can_hit_child: true } } } + #[cfg(test)] mod test { use super::*; @@ -107,8 +203,7 @@ mod test { fn_widget! { @MockBox { size: CHILD_SIZE, - left_anchor: 1., - top_anchor: 1., + anchor: Anchor::left_top(1., 1.), } } } @@ -116,15 +211,14 @@ mod test { pixel_left_top, wnd_size = WND_SIZE, { path = [0, 0], y == 1., } - { path = [0, 0, 0], x == 1., } + { path = [0, 0], x == 1., } ); fn pixel_left_bottom() -> impl WidgetBuilder { fn_widget! { @MockBox { size: CHILD_SIZE, - left_anchor: 1., - bottom_anchor: 1., + anchor: Anchor::left_bottom(1., 1.), } } } @@ -132,15 +226,14 @@ mod test { pixel_left_bottom, wnd_size = WND_SIZE, { path = [0, 0], y == 49.,} - { path = [0, 0, 0], x == 1., } + { path = [0, 0], x == 1., } ); fn pixel_top_right() -> impl WidgetBuilder { fn_widget! { @MockBox { size: CHILD_SIZE, - right_anchor: 1., - top_anchor: 1., + anchor: Anchor::right_top(1., 1.), } } } @@ -148,15 +241,14 @@ mod test { pixel_top_right, wnd_size = WND_SIZE, { path = [0, 0], y == 1.,} - { path = [0, 0, 0], x == 49.,} + { path = [0, 0], x == 49.,} ); fn pixel_bottom_right() -> impl WidgetBuilder { fn_widget! { @MockBox { size: CHILD_SIZE, - right_anchor: 1., - bottom_anchor: 1., + anchor: Anchor::right_bottom(1., 1.) } } } @@ -164,6 +256,6 @@ mod test { pixel_bottom_right, wnd_size = WND_SIZE, { path = [0, 0], y == 49.,} - { path = [0, 0, 0], x== 49.,} + { path = [0, 0], x == 49.,} ); } diff --git a/core/src/builtin_widgets/global_anchor.rs b/core/src/builtin_widgets/global_anchor.rs index 96628dd43..45f86ee6c 100644 --- a/core/src/builtin_widgets/global_anchor.rs +++ b/core/src/builtin_widgets/global_anchor.rs @@ -1,66 +1,16 @@ use crate::{prelude::*, ticker::FrameMsg}; +use std::rc::Rc; -#[derive(Clone, Copy, PartialEq)] -pub enum HAnchor { - /// Anchor the left edge position of the widget - Left(f32), - - /// Anchor the right edge position of the widget - Right(f32), -} - -#[derive(Clone, Copy, PartialEq)] -pub enum VAnchor { - /// Anchor the top edge position of the widget - Top(f32), - - /// Anchor the bottom edge position of the widget - Bottom(f32), -} - -#[derive(Clone, Copy, Default)] -pub struct AnchorPosition { - x: Option, - y: Option, -} - -impl AnchorPosition { - pub fn new(x: HAnchor, y: VAnchor) -> Self { Self { x: Some(x), y: Some(y) } } - - pub fn left(x: f32) -> Self { Self { x: Some(HAnchor::Left(x)), y: None } } - - pub fn right(x: f32) -> Self { Self { x: Some(HAnchor::Right(x)), y: None } } - - pub fn top(y: f32) -> Self { Self { x: None, y: Some(VAnchor::Top(y)) } } - - pub fn bottom(y: f32) -> Self { Self { x: None, y: Some(VAnchor::Bottom(y)) } } - - pub fn top_left(x: f32, y: f32) -> Self { Self::new(HAnchor::Left(x), VAnchor::Top(y)) } - - fn to_offset(self, size: &Size) -> (Option, Option) { - let x = self.x.map(|x| match x { - HAnchor::Left(x) => x, - HAnchor::Right(x) => x - size.width, - }); - let y = self.y.map(|y| match y { - VAnchor::Top(y) => y, - VAnchor::Bottom(y) => y - size.height, - }); - (x, y) - } -} - -#[derive(Declare, Query)] +#[derive(Declare, Query, Default)] pub struct GlobalAnchor { #[declare(builtin, default)] - global_anchor: AnchorPosition, + global_anchor: Anchor, } impl ComposeChild for GlobalAnchor { type Child = Widget; #[inline] fn compose_child(this: impl StateWriter, child: Self::Child) -> impl WidgetBuilder { - fn eq(f1: f32, f2: f32) -> bool { (f1 - f2).abs() < f32::EPSILON } fn_widget! { let wnd = ctx!().window(); let tick_of_layout_ready = wnd @@ -73,35 +23,72 @@ impl ComposeChild for GlobalAnchor { .sample(tick_of_layout_ready) .subscribe(move |_| { let size = $child.layout_size(); - let base = wnd.map_to_global(Point::zero(), wid.wid_uncheck()); - let (x, y) = $this.get_global_anchor().to_offset(&size); - let x = x.map_or(0., |x| x - base.x); - let y = y.map_or(0., |y| y - base.y); - if !eq($child.left_anchor, x) { - $child.write().left_anchor = x; - } - if !eq($child.top_anchor, y) { - $child.write().top_anchor = y; + 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!()) - .attach_state_data(this, ctx!()) + child.widget_build(ctx!()) } } } impl GlobalAnchor { - fn get_global_anchor(&self) -> AnchorPosition { self.global_anchor } + fn get_global_anchor(&self) -> Anchor { self.global_anchor } +} + +fn bind_h_anchor( + this: &impl StateWriter, + wnd: &Rc, + 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, + wnd: &Rc, + 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 FatObj { - /// Anchor the widget's horizontal position to the left edge of the widget - /// with WidgetId wid . - pub fn relative_to_left( + /// Anchor the widget's horizontal position by placing its left edge right to + /// the left edge of the specified widget (`wid`) with the given relative + /// pixel value (`relative`). + pub fn left_align_to( &mut self, - relative: HAnchor, wid: &LazyWidgetId, + offset: f32, ctx: &BuildCtx, ) -> impl Subscription { let this = self.get_builtin_global_anchor(ctx).clone_writer(); @@ -111,20 +98,18 @@ impl FatObj { .frame_tick_stream() .filter(|msg| matches!(msg, FrameMsg::LayoutReady(_))); tick_of_layout_ready.subscribe(move |_| { - let base = wnd.map_to_global(Point::zero(), wid.wid_uncheck()).x; - let anchor = relative.offset(base); - if this.read().global_anchor.x != Some(anchor) { - this.write().global_anchor.x = Some(anchor); - } + let base = wnd.map_to_global(Point::zero(), wid.assert_id()).x; + bind_h_anchor(&this, &wnd, HAnchor::Left(offset), base); }) } - /// Anchor the widget's horizontal position to the right edge of the widget - /// with WidgetId wid . - pub fn relative_to_right( + /// Anchor the widget's horizontal position by placing its right edge left to + /// the right edge of the specified widget (`wid`) with the given relative + /// pixel value (`relative`). + pub fn right_align_to( &mut self, - relative: HAnchor, wid: &LazyWidgetId, + relative: f32, ctx: &BuildCtx, ) -> impl Subscription { let this = self.get_builtin_global_anchor(ctx).clone_writer(); @@ -134,21 +119,19 @@ impl FatObj { .frame_tick_stream() .filter(|msg| matches!(msg, FrameMsg::LayoutReady(_))); tick_of_layout_ready.subscribe(move |_| { - let base = wnd.map_to_global(Point::zero(), wid.wid_uncheck()).x; - let size = wnd.layout_size(wid.wid_uncheck()).unwrap_or_default(); - let anchor = relative.offset(base).offset(size.width); - if this.read().global_anchor.x != Some(anchor) { - this.write().global_anchor.x = Some(anchor); - } + 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); }) } - /// Anchor the widget's vertical position to the top edge of the widget - /// with WidgetId wid. - pub fn relative_to_top( + /// Anchors the widget's vertical position by placing its top edge below the + /// top edge of the specified widget (`wid`) with the given relative pixel + /// value (`relative`). + pub fn top_align_to( &mut self, - relative: VAnchor, wid: &LazyWidgetId, + relative: f32, ctx: &BuildCtx, ) -> impl Subscription { let this = self.get_builtin_global_anchor(ctx).clone_writer(); @@ -158,20 +141,18 @@ impl FatObj { .frame_tick_stream() .filter(|msg| matches!(msg, FrameMsg::LayoutReady(_))); tick_of_layout_ready.subscribe(move |_| { - let base = wnd.map_to_global(Point::zero(), wid.wid_uncheck()).y; - let anchor = relative.offset(base); - if this.read().global_anchor.y != Some(anchor) { - this.write().global_anchor.y = Some(anchor); - } + let base = wnd.map_to_global(Point::zero(), wid.assert_id()).y; + bind_v_anchor(&this, &wnd, VAnchor::Top(relative), base); }) } - /// Anchor the widget's vertical position to the bottom edge of the widget - /// with WidgetId wid. - pub fn relative_to_bottom( + /// Anchors the widget's vertical position by placing its bottom edge above + /// the bottom edge of the specified widget (`wid`) with the given relative + /// pixel value (`relative`). + pub fn bottom_align_to( &mut self, - relative: VAnchor, wid: &LazyWidgetId, + relative: f32, ctx: &BuildCtx, ) -> impl Subscription { let this = self.get_builtin_global_anchor(ctx).clone_writer(); @@ -181,87 +162,54 @@ impl FatObj { .frame_tick_stream() .filter(|msg| matches!(msg, FrameMsg::LayoutReady(_))); tick_of_layout_ready.subscribe(move |_| { - let base = wnd.map_to_global(Point::zero(), wid.wid_uncheck()).y; - let size = wnd.layout_size(wid.wid_uncheck()).unwrap_or_default(); - let anchor = relative.offset(base).offset(size.height); - if this.read().global_anchor.y != Some(anchor) { - this.write().global_anchor.y = Some(anchor); - } + 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); }) } } - -impl HAnchor { - pub fn offset(self, offset: f32) -> Self { - match self { - HAnchor::Left(x) => HAnchor::Left(x + offset), - HAnchor::Right(x) => HAnchor::Right(x + offset), - } - } -} - -impl VAnchor { - pub fn offset(self, offset: f32) -> Self { - match self { - VAnchor::Top(x) => VAnchor::Top(x + offset), - VAnchor::Bottom(x) => VAnchor::Bottom(x + offset), - } - } -} - #[cfg(test)] mod tests { use super::*; - use crate::{reset_test_env, test_helper::*}; - - #[test] - fn global_anchor() { - reset_test_env!(); - let (base_offset, offset_writer) = split_value(10.); - let (wid, wid_writer) = split_value(None); + use crate::test_helper::*; + use ribir_dev_helper::*; - let w = fn_widget! { - let child1 = @MockBox { - left_anchor: 10., - top_anchor: 10., - size: Size::new(10., 10.), + 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 child1_id = child1.lazy_host_id(); - let mut follow_widget = @MockBox { - size: Size::new(10., 10.) + let wid = parent.lazy_host_id(); + let mut top_left = @MockBox { + size: Size::new(10., 10.), }; - *wid_writer.write() = Some(follow_widget.lazy_host_id()); - follow_widget.relative_to_left(HAnchor::Left(0.), &child1_id, ctx!()); - follow_widget.relative_to_bottom(VAnchor::Bottom(20.), &child1_id, ctx!()); + top_left.left_align_to(&wid, 20., ctx!()); + top_left.top_align_to(&wid, 10., ctx!()); - @MockBox { - left_anchor: pipe!(*$base_offset), - size: Size::new(100., 100.), + 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., 10.), - Point::new(10., 0.), - ], - @ { child1 } - @ { follow_widget } + child_pos: vec![Point::new(0., 0.), Point::new(0., 0.)], + @ { top_left } + @ { bottom_right } } } - }; - - let mut wnd = TestWindow::new_with_size(w, Size::new(200., 200.)); - wnd.draw_frame(); + } + } - let follow_wid = wid.read().clone().unwrap(); - assert_eq!( - wnd.map_to_global(Point::zero(), follow_wid.wid_uncheck()), - Point::new(20., 40.) - ); + 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.,} - *offset_writer.write() = 20.; - wnd.draw_frame(); - assert_eq!( - wnd.map_to_global(Point::zero(), follow_wid.wid_uncheck()), - Point::new(30., 40.) - ); - } + { path = [0, 0, 0, 1, 0], x == 30.,} + { path = [0, 0, 0, 1, 0], y == 20.,} + ); } diff --git a/core/src/builtin_widgets/scrollable.rs b/core/src/builtin_widgets/scrollable.rs index fb8d756b6..9698ab32a 100644 --- a/core/src/builtin_widgets/scrollable.rs +++ b/core/src/builtin_widgets/scrollable.rs @@ -41,8 +41,7 @@ impl ComposeChild for ScrollableWidget { }; let mut child = @ $child { - left_anchor: pipe!($this.get_scroll_pos().x), - top_anchor: pipe!($this.get_scroll_pos().y), + anchor: pipe!($this.get_scroll_pos()).map(|pos| Anchor::left_top(pos.x, pos.y)), }; watch!($child.layout_size()) @@ -142,9 +141,7 @@ mod tests { wnd.draw_frame(); let pos = wnd.layout_info_by_path(&[0, 0, 0, 0]).unwrap().pos; - assert_eq!(pos.y, expect_y); - let pos = wnd.layout_info_by_path(&[0, 0, 0, 0, 0]).unwrap().pos; - assert_eq!(pos.x, expect_x); + assert_eq!(pos, Point::new(expect_x, expect_y)); } #[test] diff --git a/core/src/context/widget_ctx.rs b/core/src/context/widget_ctx.rs index 1b36278c6..187e55f12 100644 --- a/core/src/context/widget_ctx.rs +++ b/core/src/context/widget_ctx.rs @@ -241,8 +241,7 @@ mod tests { size: Size::new(100., 100.), @MockBox { transform: Transform::scale(0.5, 0.5), - left_anchor: 30., - top_anchor: 30., + anchor: Anchor::left_top(30., 30.), size: Size::new(40., 40.) } } @@ -252,7 +251,7 @@ mod tests { wnd.draw_frame(); let root = wnd.widget_tree.borrow().root(); - let child = get_single_child_by_depth(root, &wnd.widget_tree.borrow().arena, 4); + 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.)); diff --git a/docs/builtin_widget/declare_builtin_fields.md b/docs/builtin_widget/declare_builtin_fields.md index d18c28db6..e61991f69 100644 --- a/docs/builtin_widget/declare_builtin_fields.md +++ b/docs/builtin_widget/declare_builtin_fields.md @@ -80,8 +80,8 @@ - specify how rounded the corners have of the widget. - padding : [`EdgeInsets`] - set the padding area on all four sides of a widget. -- global_anchor : [`AnchorPosition`] - - Anchor the widget's position using logical coordinates +- global_anchor : [`Anchor`] + - use to anchor child position, and the positioning used is relative to the window - cursor : [`CursorIcon`] - assign cursor to the widget. - margin : [`impl EdgeInsets`] @@ -96,14 +96,8 @@ - describe how widget align to its box in x-axis. - v_align : [`VAlign`] - describe how widget align to its box in y-axis. -- left_anchor : [`PositionUnit`] - - use to anchor child constraints with the left edge of parent widget. -- right_anchor : [`PositionUnit`] - - use to anchor child constraints with the right edge of parent widget. -- top_anchor : [`PositionUnit`] - - use to anchor child constraints with the top edge of parent widget -- bottom_anchor : [`PositionUnit`] - - use to anchor child constraints with the bottom edge of parent widget. +- anchor : [`Anchor`] + - use to anchor child position, and the positioning used is relative to the parent - visible : [`bool`] - Whether to show or hide a child - opacity : [`f32`] diff --git a/examples/animation_demo.rs b/examples/animation_demo.rs index 38b9c316b..78a09944f 100644 --- a/examples/animation_demo.rs +++ b/examples/animation_demo.rs @@ -15,8 +15,7 @@ fn main() { }); let w = widget! { PathsPaintKit { - top_anchor: 100., - left_anchor: 100., + anchor: Anchor::left_top(100., 100.), transform: Transform::scale(0.5, 0.5), id: path_widget, paths, diff --git a/macros/src/builtin_fields_list.rs b/macros/src/builtin_fields_list.rs index 788df6c37..bf87339d5 100644 --- a/macros/src/builtin_fields_list.rs +++ b/macros/src/builtin_fields_list.rs @@ -234,8 +234,8 @@ builtin! { } GlobalAnchor { - #[doc= "Anchor the widget's position using logical coordinates"] - global_anchor: AnchorPosition, + #[doc= "use to anchor child position, and the positioning used is relative to the window"] + global_anchor: Anchor, } Cursor { @@ -276,24 +276,9 @@ builtin! { v_align: VAlign, } - LeftAnchor { - #[doc="use to anchor child constraints with the left edge of parent widget."] - left_anchor: PositionUnit, - } - - RightAnchor { - #[doc="use to anchor child constraints with the right edge of parent widget."] - right_anchor: PositionUnit, - } - - TopAnchor { - #[doc="use to anchor child constraints with the top edge of parent widget"] - top_anchor: PositionUnit, - } - - BottomAnchor { - #[doc="use to anchor child constraints with the bottom edge of parent widget."] - bottom_anchor: PositionUnit, + RelativeAnchor { + #[doc="use to anchor child position, and the positioning used is relative to the parent"] + anchor: Anchor, } Visibility { diff --git a/tests/rdl_macro_test.rs b/tests/rdl_macro_test.rs index aef008e90..6772c618c 100644 --- a/tests/rdl_macro_test.rs +++ b/tests/rdl_macro_test.rs @@ -834,7 +834,7 @@ fn no_watch() { #[test] fn fix_direct_use_map_writer_with_builtin() { fn _x(mut host: FatObj, ctx!(): &BuildCtx) { - let _left = map_writer!($host.left_anchor); - let _left = split_writer!($host.left_anchor); + let _anchor = map_writer!($host.anchor); + let _anchor = split_writer!($host.anchor); } } diff --git a/themes/material/src/lib.rs b/themes/material/src/lib.rs index f7ca9b363..a707ed8a2 100644 --- a/themes/material/src/lib.rs +++ b/themes/material/src/lib.rs @@ -267,9 +267,9 @@ fn override_compose_decorator(theme: &mut FullTheme) { styles.override_compose_decorator::(|this, host, ctx| { fn_widget! { let host = scrollbar_thumb(host, EdgeInsets::vertical(1.)); - let mut thumb = @ $host { left_anchor: pipe!($this.offset) }; + let mut thumb = @ $host { anchor: pipe!($this.offset).map(Anchor::left) }; - map_writer!($thumb.left_anchor) + map_writer!($thumb.anchor) .transition(transitions::LINEAR.of(ctx!()), ctx!()); thumb @@ -279,9 +279,9 @@ fn override_compose_decorator(theme: &mut FullTheme) { styles.override_compose_decorator::(|this, host, ctx| { fn_widget! { let host = scrollbar_thumb(host, EdgeInsets::vertical(1.)); - let mut thumb = @ $host { top_anchor: pipe!($this.offset) }; + let mut thumb = @ $host { anchor: pipe!($this.offset).map(Anchor::top) }; - map_writer!($thumb.top_anchor) + map_writer!($thumb.anchor) .transition(transitions::LINEAR.of(ctx!()), ctx!()); thumb @@ -291,34 +291,32 @@ fn override_compose_decorator(theme: &mut FullTheme) { styles.override_compose_decorator::(|style, host, ctx| { fn_widget! { let mut indicator = @ $host { - left_anchor: pipe!{ + anchor: pipe!{ let style = $style; - match style.pos { + let x = match style.pos { Position::Top | Position::Bottom => style.rect.origin.x + (style.rect.size.width - INDICATOR_SIZE) / 2., Position::Left => style.rect.size.width - style.extent, Position::Right => 0., - } - }, - top_anchor: pipe!{ - let style = $style; - match style.pos { + }; + let y = match style.pos { Position::Left | Position::Right => style.rect.origin.y + (style.rect.size.height - INDICATOR_SIZE) / 2., Position::Top => style.rect.size.height - style.extent, Position::Bottom => 0., - } + }; + Anchor::left_top(x, y) }, }; let ease_in = transitions::EASE_IN.of(ctx!()); match $style.pos { Position::Top | Position::Bottom => { - map_writer!($indicator.left_anchor) + map_writer!($indicator.anchor) .transition(ease_in, ctx!()); } Position::Left | Position::Right => { - map_writer!($indicator.top_anchor) + map_writer!($indicator.anchor) .transition(ease_in, ctx!()); } } diff --git a/widgets/src/input.rs b/widgets/src/input.rs index a716c57a7..63891facf 100644 --- a/widgets/src/input.rs +++ b/widgets/src/input.rs @@ -341,16 +341,19 @@ where let caret_box_id = caret_box.lazy_host_id(); let mut caret_box = @$caret_box { - left_anchor: pipe!($this.caret_position(&$text, $text.layout_size()).map_or(0., |p| p.x)), - top_anchor: pipe!($this.caret_position(&$text, $text.layout_size()).map_or(0., |p| p.y)), + anchor: pipe!( + let pos = $this.caret_position(&$text, $text.layout_size()).unwrap_or_default(); + Anchor::left_top(pos.x, pos.y) + ), }; let scrollable = stack.get_builtin_scrollable_widget(ctx!()); let tick_of_layout_ready = ctx!().window() .frame_tick_stream() .filter(|msg| matches!(msg, FrameMsg::LayoutReady(_))); - watch!(Point::new($caret_box.left_anchor, $caret_box.top_anchor)) + watch!(SelectableText::caret(&*$this)) .distinct_until_changed() .sample(tick_of_layout_ready) + .map(move |_| $this.caret_position(&$text, $text.layout_size()).unwrap_or_default()) .scan_initial((Point::zero(), Point::zero()), |pair, v| (pair.1, v)) .subscribe(move |(before, after)| { let mut scrollable = $scrollable.silent(); diff --git a/widgets/src/input/selected_text.rs b/widgets/src/input/selected_text.rs index e8d5584b7..87a36ce45 100644 --- a/widgets/src/input/selected_text.rs +++ b/widgets/src/input/selected_text.rs @@ -28,8 +28,7 @@ impl Compose for SelectedHighLight { $this.rects.clone().into_iter().map(move |rc| { @Container { background: color.clone(), - top_anchor: rc.origin.y, - left_anchor: rc.origin.x, + anchor: Anchor::from_point(rc.origin), size: rc.size, } }) diff --git a/widgets/src/scrollbar.rs b/widgets/src/scrollbar.rs index 6dad4eccb..8d445c71b 100644 --- a/widgets/src/scrollbar.rs +++ b/widgets/src/scrollbar.rs @@ -29,7 +29,7 @@ pub struct HScrollBarThumbDecorator { impl ComposeDecorator for HScrollBarThumbDecorator { fn compose_decorator(this: State, host: Widget) -> impl WidgetBuilder { - fn_widget! { @$host { left_anchor: pipe!($this.offset) } } + fn_widget! { @$host { anchor: pipe!($this.offset).map(Anchor::left) } } } } @@ -42,7 +42,7 @@ pub struct VScrollBarThumbDecorator { impl ComposeDecorator for VScrollBarThumbDecorator { fn compose_decorator(this: State, host: Widget) -> impl WidgetBuilder { - fn_widget! { @$host { top_anchor: pipe!($this.offset) } } + fn_widget! { @$host { anchor: pipe!($this.offset).map(Anchor::top) } } } } diff --git a/widgets/src/tabs.rs b/widgets/src/tabs.rs index 2c9e54664..abd3f0ded 100644 --- a/widgets/src/tabs.rs +++ b/widgets/src/tabs.rs @@ -139,24 +139,22 @@ impl ComposeDecorator for IndicatorDecorator { fn compose_decorator(this: State, host: Widget) -> impl WidgetBuilder { fn_widget! { @ $host{ - left_anchor: pipe!{ + anchor: pipe!{ let this = $this; - match this.pos { - Position::Top | Position::Bottom => this.rect.origin.x - + (this.rect.size.width - 60.) / 2., + let x = match this.pos { + Position::Top | Position::Bottom => + this.rect.origin.x + (this.rect.size.width - 60.) / 2., Position::Left => this.rect.size.width - this.extent, Position::Right => 0., - } - }, - top_anchor: pipe! { - let this = $this; - match this.pos { + }; + let y = match this.pos { Position::Left | Position::Right => this.rect.origin.y + (this.rect.size.height - 60.) / 2., Position::Top => this.rect.size.height - this.extent, Position::Bottom => 0., - } - } + }; + Anchor::left_top(x, y) + }, } } } @@ -272,15 +270,19 @@ impl ComposeChild for Tabs { Position::Top | Position::Bottom => Direction::Horizontal, Position::Left | Position::Right => Direction::Vertical, }), - left_anchor: pipe!(match $this.pos { - Position::Left => $flex.layout_size().width - 1., - Position::Top | Position::Right | Position::Bottom => 0., - }), - top_anchor: pipe!(match $this.pos { - Position::Top => $flex.layout_size().height - 1., - Position::Bottom | Position::Right | Position::Left => 0., - }), + anchor: pipe!( + let x = match $this.pos { + Position::Left => $flex.layout_size().width - 1., + Position::Top | Position::Right | Position::Bottom => 0., + }; + let y = match $this.pos { + Position::Top => $flex.layout_size().height - 1., + Position::Bottom | Position::Right | Position::Left => 0., + }; + Anchor::left_top(x, y) + ) }; + let indicator_decorator = @IndicatorDecorator { pos: pipe!($this.pos), extent: indicator.extent,