use crate::{prelude::*, wrap_render::WrapRender};
#[derive(Debug, Clone, Copy)]
pub enum HAnchor {
Left(f32),
Right(f32),
}
#[derive(Debug, Clone, Copy)]
pub enum VAnchor {
Top(f32),
Bottom(f32),
}
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)),
}
}
}
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 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,
}
}
}
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,
}
}
}
#[derive(Clone, Copy, Default, PartialEq)]
pub struct Anchor {
pub x: Option<HAnchor>,
pub y: Option<VAnchor>,
}
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 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!(),
}
}
}
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,
};
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 Anchor {
pub fn new(x: HAnchor, y: VAnchor) -> Self { Self { x: Some(x), y: Some(y) } }
pub fn from_point(pos: Point) -> Self { Self::new(HAnchor::Left(pos.x), VAnchor::Top(pos.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 left_top(x: f32, y: f32) -> Self { Self::new(HAnchor::Left(x), VAnchor::Top(y)) }
pub fn right_top(x: f32, y: f32) -> Self { Self::new(HAnchor::Right(x), VAnchor::Top(y)) }
pub fn left_bottom(x: f32, y: f32) -> Self { Self::new(HAnchor::Left(x), VAnchor::Bottom(y)) }
pub fn right_bottom(x: f32, y: f32) -> Self { Self::new(HAnchor::Right(x), VAnchor::Bottom(y)) }
}
#[derive(Default)]
pub struct RelativeAnchor {
pub anchor: Anchor,
}
impl Declare for RelativeAnchor {
type Builder = FatObj<()>;
#[inline]
fn declarer() -> Self::Builder { FatObj::new(()) }
}
impl<'c> ComposeChild<'c> for RelativeAnchor {
type Child = Widget<'c>;
fn compose_child(this: impl StateWriter<Value = Self>, child: Self::Child) -> Widget<'c> {
WrapRender::combine_child(this, child)
}
}
impl WrapRender for RelativeAnchor {
fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size {
let child_size = host.perform_layout(clamp, ctx);
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();
ctx.update_position(ctx.widget_id(), Point::new(x, y));
child_size
}
}
impl From<Point> for Anchor {
fn from(value: Point) -> Self {
Anchor { x: Some(HAnchor::Left(value.x)), y: Some(VAnchor::Top(value.y)) }
}
}
#[cfg(test)]
mod test {
use ribir_dev_helper::*;
use super::*;
use crate::test_helper::*;
const CHILD_SIZE: Size = Size::new(50., 50.);
const WND_SIZE: Size = Size::new(100., 100.);
fn widget_tester(anchor: Anchor) -> WidgetTester {
WidgetTester::new(fn_widget! {
@MockBox { size: CHILD_SIZE, anchor }
})
.with_wnd_size(WND_SIZE)
}
widget_layout_test!(
pixel_left_top,
widget_tester(Anchor::left_top(1., 1.)),
LayoutCase::default().with_pos(Point::new(1., 1.))
);
widget_layout_test!(
pixel_left_bottom,
widget_tester(Anchor::left_bottom(1., 1.)),
LayoutCase::default().with_pos((1., 49.).into())
);
widget_layout_test!(
pixel_top_right,
widget_tester(Anchor::right_top(1., 1.)),
LayoutCase::default().with_pos((49., 1.).into())
);
widget_layout_test!(
pixel_bottom_right,
widget_tester(Anchor::right_bottom(1., 1.)),
LayoutCase::default().with_pos((49., 49.).into())
);
}