ltk/widget/pattern_grid/
mod.rsuse std::sync::Arc;
use crate::types::{ Color, Length, Point, Rect };
use crate::render::Canvas;
use super::Element;
mod theme;
#[ cfg( test ) ]
mod tests;
pub const NODES: u8 = 9;
pub struct PatternGrid<Msg: Clone>
{
pub( crate ) path: Vec<u8>,
pub( crate ) on_node: Option<Arc<dyn Fn( u8 ) -> Msg>>,
pub( crate ) on_release: Option<Msg>,
pub( crate ) hide_stroke: bool,
pub( crate ) error: bool,
pub( crate ) size: Option<Length>,
}
impl<Msg: Clone> PatternGrid<Msg>
{
pub fn new( path: Vec<u8> ) -> Self
{
Self { path, on_node: None, on_release: None, hide_stroke: false, error: false, size: None }
}
pub fn size( mut self, s: impl Into<Length> ) -> Self
{
self.size = Some( s.into() );
self
}
pub fn on_node( mut self, f: impl Fn( u8 ) -> Msg + 'static ) -> Self
{
self.on_node = Some( Arc::new( f ) );
self
}
pub fn on_release( mut self, msg: Msg ) -> Self
{
self.on_release = Some( msg );
self
}
pub fn hide_stroke( mut self, hide: bool ) -> Self
{
self.hide_stroke = hide;
self
}
pub fn error( mut self, error: bool ) -> Self
{
self.error = error;
self
}
pub( crate ) fn map_msg<U>( self, f: &super::MapFn<Msg, U> ) -> PatternGrid<U>
where
U: Clone + 'static,
Msg: 'static,
{
let on_node = self.on_node.map( |old| -> Arc<dyn Fn( u8 ) -> U>
{
let mapper = Arc::clone( f );
Arc::new( move |n| ( *mapper )( ( *old )( n ) ) )
} );
PatternGrid
{
path: self.path,
on_node,
on_release: self.on_release.map( |m| ( *f )( m ) ),
hide_stroke: self.hide_stroke,
error: self.error,
size: self.size,
}
}
pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> ( f32, f32 )
{
let size = match self.size
{
Some( l ) => canvas.resolve_geom( l ),
None => canvas.geom_px( theme::SIZE ),
};
let size = size.min( max_width );
( size, size )
}
pub fn draw( &self, canvas: &mut Canvas, rect: Rect, _focused: bool )
{
let palette = crate::theme::palette();
let active = if self.error { palette.danger } else { palette.accent };
let node_r = canvas.geom_px( theme::NODE_RADIUS );
let dot_r = canvas.geom_px( theme::DOT_RADIUS );
let stroke_w = canvas.geom_px( theme::STROKE_W );
if !self.hide_stroke
{
for pair in self.path.windows( 2 )
{
let a = node_center( rect, pair[ 0 ] );
let b = node_center( rect, pair[ 1 ] );
canvas.draw_line( a.x, a.y, b.x, b.y, active, stroke_w );
}
}
for node in 0..NODES
{
let c = node_center( rect, node );
let visited = self.path.contains( &node );
if visited
{
let ring = Color { a: active.a * theme::RING_ALPHA, ..active };
circle( canvas, c, node_r, ring );
}
circle( canvas, c, dot_r, if visited { active } else { palette.text_secondary } );
}
}
}
fn circle( canvas: &mut Canvas, center: Point, radius: f32, color: Color )
{
let rect = Rect { x: center.x - radius, y: center.y - radius, width: radius * 2.0, height: radius * 2.0 };
canvas.fill_rect( rect, color, radius );
}
pub fn node_center( rect: Rect, index: u8 ) -> Point
{
let col = ( index % 3 ) as f32;
let row = ( index / 3 ) as f32;
let side = rect.width.min( rect.height );
let ox = rect.x + ( rect.width - side ) / 2.0;
let oy = rect.y + ( rect.height - side ) / 2.0;
let cell = side / 3.0;
Point { x: ox + cell * ( col + 0.5 ), y: oy + cell * ( row + 0.5 ) }
}
pub fn node_at( rect: Rect, pos: Point, snap_radius: f32 ) -> Option<u8>
{
( 0..NODES ).find( |&node|
{
let c = node_center( rect, node );
( pos.x - c.x ).hypot( pos.y - c.y ) <= snap_radius
} )
}
pub fn snap_design_px() -> f32
{
theme::SNAP_RADIUS
}
pub fn pattern_grid<Msg: Clone>( path: Vec<u8> ) -> PatternGrid<Msg>
{
PatternGrid::new( path )
}
impl<Msg: Clone + 'static> From<PatternGrid<Msg>> for Element<Msg>
{
fn from( g: PatternGrid<Msg> ) -> Self
{
Element::PatternGrid( g )
}
}