use frust_core::{
BoxConstraints, BuildCtx, ChangeFlags, LayoutCtx, PaintCtx, PaintScene, View, Widget,
};
use kurbo::Size;
use peniko::Color;
const DEFAULT_THICKNESS: f64 = 1.0;
pub struct DividerView {
color: Color,
thickness: f64,
vertical: bool,
}
pub fn divider(color: Color) -> DividerView {
DividerView {
color,
thickness: DEFAULT_THICKNESS,
vertical: false,
}
}
impl DividerView {
pub fn color(mut self, color: Color) -> Self {
self.color = color;
self
}
pub fn thickness(mut self, thickness: f64) -> Self {
self.thickness = thickness;
self
}
pub fn vertical(mut self) -> Self {
self.vertical = true;
self
}
}
pub struct DividerWidget {
color: Color,
thickness: f64,
vertical: bool,
}
impl<State: 'static> View<State> for DividerView {
type Element = DividerWidget;
fn build(&self, _ctx: &mut BuildCtx<'_>) -> DividerWidget {
DividerWidget {
color: self.color,
thickness: self.thickness,
vertical: self.vertical,
}
}
fn rebuild(
&self,
prev: &Self,
element: &mut DividerWidget,
_ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
let mut flags = ChangeFlags::NONE;
if prev.color != self.color {
element.color = self.color;
flags |= ChangeFlags::PAINT;
}
if prev.thickness != self.thickness || prev.vertical != self.vertical {
element.thickness = self.thickness;
element.vertical = self.vertical;
flags |= ChangeFlags::LAYOUT;
}
flags
}
}
impl Widget for DividerWidget {
fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
let fill = |max: f64, min: f64| if max.is_finite() { max } else { min };
let intrinsic = if self.vertical {
Size::new(self.thickness, fill(bc.max().height, bc.min().height))
} else {
Size::new(fill(bc.max().width, bc.min().width), self.thickness)
};
bc.constrain(intrinsic)
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
scene.fill_rect(ctx.origin(), ctx.size(), self.color);
}
}
#[cfg(test)]
mod tests {
use super::*;
use frust_core::BuildCtx;
use kurbo::Point;
fn build(view: &DividerView) -> DividerWidget {
let mut counter = 0u64;
View::<()>::build(view, &mut BuildCtx::new(&mut counter))
}
#[test]
fn horizontal_divider_fills_width_and_takes_thickness_on_height() {
let view = divider(Color::BLACK);
let mut w = build(&view);
let mut lctx = LayoutCtx::new();
let size = w.layout(&mut lctx, &BoxConstraints::loose(Size::new(320.0, 640.0)));
assert_eq!(size, Size::new(320.0, DEFAULT_THICKNESS));
}
#[test]
fn horizontal_divider_honors_a_custom_thickness() {
let view = divider(Color::BLACK).thickness(4.0);
let mut w = build(&view);
let mut lctx = LayoutCtx::new();
let size = w.layout(&mut lctx, &BoxConstraints::loose(Size::new(200.0, 200.0)));
assert_eq!(size, Size::new(200.0, 4.0));
}
#[test]
fn vertical_divider_fills_height_and_takes_thickness_on_width() {
let view = divider(Color::BLACK).vertical();
let mut w = build(&view);
let mut lctx = LayoutCtx::new();
let size = w.layout(&mut lctx, &BoxConstraints::loose(Size::new(320.0, 640.0)));
assert_eq!(size, Size::new(DEFAULT_THICKNESS, 640.0));
}
#[test]
fn a_tight_constraint_forces_the_divider_to_it_regardless_of_thickness() {
let view = divider(Color::BLACK);
let mut w = build(&view);
let mut lctx = LayoutCtx::new();
let size = w.layout(&mut lctx, &BoxConstraints::tight(Size::new(50.0, 50.0)));
assert_eq!(size, Size::new(50.0, 50.0));
}
#[test]
fn an_unbounded_fill_axis_collapses_to_the_incoming_minimum() {
let view = divider(Color::BLACK);
let mut w = build(&view);
let mut lctx = LayoutCtx::new();
let bc = BoxConstraints::loose(Size::new(f64::INFINITY, 200.0));
let size = w.layout(&mut lctx, &bc);
assert_eq!(size, Size::new(0.0, DEFAULT_THICKNESS));
}
#[test]
fn an_unbounded_fill_axis_collapses_to_a_nonzero_incoming_minimum() {
let view = divider(Color::BLACK).vertical();
let mut w = build(&view);
let mut lctx = LayoutCtx::new();
let bc = BoxConstraints::new(Size::new(0.0, 12.0), Size::new(500.0, f64::INFINITY));
let size = w.layout(&mut lctx, &bc);
assert_eq!(size, Size::new(DEFAULT_THICKNESS, 12.0));
}
#[derive(Default)]
struct Recorder {
rects: Vec<(Point, Size, Color)>,
}
impl PaintScene for Recorder {
fn fill_rect(&mut self, origin: Point, size: Size, color: Color) {
self.rects.push((origin, size, color));
}
fn draw_text(&mut self, _origin: Point, _text: &str) {}
}
#[test]
fn paint_fills_exactly_one_rect_in_the_divider_color() {
let view = divider(Color::from_rgb8(0xCC, 0xCC, 0xCC));
let mut w = build(&view);
let mut lctx = LayoutCtx::new();
let size = w.layout(&mut lctx, &BoxConstraints::loose(Size::new(100.0, 100.0)));
let mut rec = Recorder::default();
let mut pctx = PaintCtx::new(Point::ZERO, size);
w.paint(&mut pctx, &mut rec);
assert_eq!(
rec.rects,
vec![(Point::ZERO, size, Color::from_rgb8(0xCC, 0xCC, 0xCC))]
);
}
#[test]
fn color_override_wins_over_the_constructor_argument() {
let view = divider(Color::BLACK).color(Color::from_rgb8(0x11, 0x22, 0x33));
let mut w = build(&view);
let mut lctx = LayoutCtx::new();
let size = w.layout(&mut lctx, &BoxConstraints::loose(Size::new(10.0, 10.0)));
let mut rec = Recorder::default();
let mut pctx = PaintCtx::new(Point::ZERO, size);
w.paint(&mut pctx, &mut rec);
assert_eq!(
rec.rects,
vec![(Point::ZERO, size, Color::from_rgb8(0x11, 0x22, 0x33))]
);
}
}