use crate::{prelude::*, wrap_render::*};
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Align {
#[default]
Start,
Center,
End,
Stretch,
}
#[derive(Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HAlign {
#[default]
Left,
Center,
Right,
Stretch,
}
#[derive(Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VAlign {
#[default]
Top,
Center,
Bottom,
Stretch,
}
#[derive(Default)]
pub struct HAlignWidget {
pub h_align: HAlign,
}
#[derive(Default)]
pub struct VAlignWidget {
pub v_align: VAlign,
}
#[macro_export]
macro_rules! h_align_widget {
($($t: tt)*) => { fn_widget! { @HAlignWidget { $($t)* } } };
}
#[macro_export]
macro_rules! v_align_widget {
($($t: tt)*) => { fn_widget! { @VAlignWidget { $($t)* } } };
}
impl Declare for HAlignWidget {
type Builder = FatObj<()>;
#[inline]
fn declarer() -> Self::Builder { FatObj::new(()) }
}
impl Declare for VAlignWidget {
type Builder = FatObj<()>;
#[inline]
fn declarer() -> Self::Builder { FatObj::new(()) }
}
impl_compose_child_for_wrap_render!(HAlignWidget);
impl_compose_child_for_wrap_render!(VAlignWidget);
impl WrapRender for HAlignWidget {
fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size {
let max = clamp.max.width;
let host_clamp = if self.h_align == HAlign::Stretch && max.is_finite() {
clamp.with_fixed_width(max)
} else {
clamp.free_width()
};
let size = host.perform_layout(host_clamp, ctx);
let container = clamp.container_width(size.width);
let x = Align::align_value(self.h_align.into(), size.width, container);
let pos = ctx.box_pos().unwrap_or_default();
ctx.update_position(ctx.widget_id(), Point::new(x, pos.y));
size
}
#[inline]
fn wrapper_dirty_phase(&self) -> DirtyPhase { DirtyPhase::Layout }
}
impl WrapRender for VAlignWidget {
fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size {
let max = clamp.max.height;
let host_clamp = if self.v_align == VAlign::Stretch && max.is_finite() {
clamp.with_fixed_height(max)
} else {
clamp.free_height()
};
let size = host.perform_layout(host_clamp, ctx);
let container = clamp.container_height(size.height);
let y = Align::align_value(self.v_align.into(), size.height, container);
let pos = ctx.box_pos().unwrap_or_default();
ctx.update_position(ctx.widget_id(), Point::new(pos.x, y));
size
}
#[inline]
fn wrapper_dirty_phase(&self) -> DirtyPhase { DirtyPhase::Layout }
}
impl Align {
pub fn align_value(self, child_size: f32, box_size: f32) -> f32 {
if !box_size.is_finite() {
return 0.;
}
match self {
Align::Center => (box_size - child_size) / 2.,
Align::End => box_size - child_size,
_ => 0.,
}
}
}
impl From<HAlign> for Align {
fn from(h: HAlign) -> Self {
match h {
HAlign::Left => Align::Start,
HAlign::Center => Align::Center,
HAlign::Right => Align::End,
HAlign::Stretch => Align::Stretch,
}
}
}
impl From<VAlign> for Align {
fn from(h: VAlign) -> Self {
match h {
VAlign::Top => Align::Start,
VAlign::Center => Align::Center,
VAlign::Bottom => Align::End,
VAlign::Stretch => Align::Stretch,
}
}
}
impl From<Align> for HAlign {
fn from(h: Align) -> Self {
match h {
Align::Start => HAlign::Left,
Align::Center => HAlign::Center,
Align::End => HAlign::Right,
Align::Stretch => HAlign::Stretch,
}
}
}
impl From<Align> for VAlign {
fn from(h: Align) -> Self {
match h {
Align::Start => VAlign::Top,
Align::Center => VAlign::Center,
Align::End => VAlign::Bottom,
Align::Stretch => VAlign::Stretch,
}
}
}
#[cfg(test)]
mod tests {
use ribir_dev_helper::*;
use super::*;
use crate::test_helper::*;
const CHILD_SIZE: Size = Size::new(10., 10.);
const WND_SIZE: Size = Size::new(100., 100.);
fn h_align(h_align: HAlign) -> GenWidget {
fn_widget! {
@HAlignWidget {
h_align,
@MockBox { size: CHILD_SIZE }
}
}
.r_into()
}
#[test]
fn align_value_infinity() {
let align = Align::Center;
assert_eq!(align.align_value(10., f32::INFINITY), 0.);
}
#[test]
fn align_value_nan() {
let align = Align::Center;
assert_eq!(align.align_value(10., f32::NAN), 0.);
}
#[test]
fn align_value_child_larger_than_box() {
let align = Align::Center;
assert_eq!(align.align_value(20., 10.), -5.);
}
#[test]
fn align_value_child_equal_to_box() {
let align = Align::Center;
assert_eq!(align.align_value(10., 10.), 0.);
}
widget_layout_test!(
left_align,
WidgetTester::new(h_align(HAlign::Left)).with_wnd_size(WND_SIZE),
LayoutCase::default()
.with_size(CHILD_SIZE)
.with_x(0.)
);
widget_layout_test!(
h_center_align,
WidgetTester::new(h_align(HAlign::Center)).with_wnd_size(WND_SIZE),
LayoutCase::default()
.with_size(CHILD_SIZE)
.with_x(45.)
);
widget_layout_test!(
right_align,
WidgetTester::new(h_align(HAlign::Right)).with_wnd_size(WND_SIZE),
LayoutCase::default()
.with_size(CHILD_SIZE)
.with_x(90.)
);
widget_layout_test!(
h_stretch_algin,
WidgetTester::new(h_align(HAlign::Stretch)).with_wnd_size(WND_SIZE),
LayoutCase::default()
.with_size(Size::new(WND_SIZE.width, 10.))
.with_x(0.)
);
fn v_align(v_align: VAlign) -> GenWidget {
fn_widget! {
@VAlignWidget {
v_align,
@MockBox { size: CHILD_SIZE }
}
}
.r_into()
}
widget_layout_test!(
top_align,
WidgetTester::new(v_align(VAlign::Top)).with_wnd_size(WND_SIZE),
LayoutCase::default()
.with_size(CHILD_SIZE)
.with_y(0.)
);
widget_layout_test!(
v_center_align,
WidgetTester::new(v_align(VAlign::Center)).with_wnd_size(WND_SIZE),
LayoutCase::default()
.with_size(CHILD_SIZE)
.with_y(45.)
);
widget_layout_test!(
bottom_align,
WidgetTester::new(v_align(VAlign::Bottom)).with_wnd_size(WND_SIZE),
LayoutCase::default()
.with_size(CHILD_SIZE)
.with_y(90.)
);
widget_layout_test!(
v_stretch_align,
WidgetTester::new(v_align(VAlign::Stretch)).with_wnd_size(WND_SIZE),
LayoutCase::default().with_size(Size::new(10., WND_SIZE.height))
);
fn all_align() -> GenWidget {
fn_widget! {
@MockBox {
h_align: HAlign::Center,
v_align: VAlign::Center,
size: CHILD_SIZE
}
}
.r_into()
}
widget_layout_test!(
all_align,
WidgetTester::new(all_align()).with_wnd_size(WND_SIZE),
LayoutCase::default()
.with_size(CHILD_SIZE)
.with_x(45.)
.with_y(45.)
);
}