//! `cargo run --example clip_path`
//!
//! Demonstrates [`Canvas::set_clip_path`] — real per-path clipping. Each cell
//! fills its whole rect with a background colour, then installs a path clip and
//! fills again with a foreground colour: only the pixels inside the path are
//! repainted, so the shape's silhouette appears (not its bounding box).
//!
//! Both backends clip with smooth, anti-aliased edges: the software backend
//! with a tiny-skia coverage mask, the GLES backend by compositing an offscreen
//! layer through an equivalent coverage mask. The circle and rounded corners
//! look the same on both. Esc exits.

use ltk::{ column, row, text, App, Color, Element, External, Keysym, PathCmd, Rect };

#[ derive( Clone, Debug ) ]
enum Msg {}

struct Demo;

/// Floor for the square cells — the orientation-derived size never
/// shrinks below this, so the shapes stay readable on tiny windows.
const CELL_MIN: f32 = 120.0;
const MARGIN: f32 = 24.0;

fn bg() -> Color { Color::rgba( 0.16, 0.18, 0.22, 1.0 ) }
fn fg() -> Color { Color::rgba( 0.30, 0.68, 0.90, 1.0 ) }

/// Rounded rectangle inset by `MARGIN`, corners of `radius`, built from lines
/// and quadratic corners.
fn rounded_rect_path( r: Rect, radius: f32 ) -> Vec<PathCmd>
{
	let l     = r.x + MARGIN;
	let t     = r.y + MARGIN;
	let right = r.x + r.width  - MARGIN;
	let b     = r.y + r.height - MARGIN;
	vec![
		PathCmd::MoveTo( l + radius, t ),
		PathCmd::LineTo( right - radius, t ),
		PathCmd::QuadTo( right, t, right, t + radius ),
		PathCmd::LineTo( right, b - radius ),
		PathCmd::QuadTo( right, b, right - radius, b ),
		PathCmd::LineTo( l + radius, b ),
		PathCmd::QuadTo( l, b, l, b - radius ),
		PathCmd::LineTo( l, t + radius ),
		PathCmd::QuadTo( l, t, l + radius, t ),
		PathCmd::Close,
	]
}

/// Circle centred in the cell, drawn as four cubic-Bézier quadrants.
fn circle_path( r: Rect ) -> Vec<PathCmd>
{
	let cx  = r.x + r.width  / 2.0;
	let cy  = r.y + r.height / 2.0;
	let rad = ( r.width.min( r.height ) ) / 2.0 - MARGIN;
	let k   = rad * 0.552_284_75; // 4/3 * tan(pi/8): cubic circle approximation
	vec![
		PathCmd::MoveTo( cx + rad, cy ),
		PathCmd::CubicTo( cx + rad, cy + k,   cx + k,   cy + rad, cx,       cy + rad ),
		PathCmd::CubicTo( cx - k,   cy + rad, cx - rad, cy + k,   cx - rad, cy ),
		PathCmd::CubicTo( cx - rad, cy - k,   cx - k,   cy - rad, cx,       cy - rad ),
		PathCmd::CubicTo( cx + k,   cy - rad, cx + rad, cy - k,   cx + rad, cy ),
		PathCmd::Close,
	]
}

/// Downward triangle inset by `MARGIN` — a polygon clip.
fn triangle_path( r: Rect ) -> Vec<PathCmd>
{
	vec![
		PathCmd::MoveTo( r.x + r.width / 2.0, r.y + MARGIN ),
		PathCmd::LineTo( r.x + r.width - MARGIN, r.y + r.height - MARGIN ),
		PathCmd::LineTo( r.x + MARGIN, r.y + r.height - MARGIN ),
		PathCmd::Close,
	]
}

/// A cell that paints `bg()` everywhere, then `fg()` clipped to `shape`. The
/// previous clip is snapshotted and restored so the clip does not leak to the
/// rest of the frame.
fn clipped_cell( size: f32, shape: fn( Rect ) -> Vec<PathCmd> ) -> Element<Msg>
{
	External::cpu( size, size, move |canvas, rect|
	{
		canvas.fill_rect( rect, bg(), 0.0 );
		let saved = canvas.clip_bounds();
		canvas.set_clip_path( &shape( rect ) );
		canvas.fill_rect( rect, fg(), 0.0 );
		canvas.set_clip_rects( &saved );
	} ).into()
}

impl App for Demo
{
	type Message = Msg;

	fn view( &self ) -> Element<Msg>
	{
		// Cells side by side in landscape, stacked in portrait; the
		// runtime rebuilds the view on every configure, so both the
		// arrangement and the cell size follow the window live. The
		// square side comes from the axis the cells do NOT flow along
		// (the height in landscape, the width in portrait), capped so
		// the three of them also fit along the flow axis with the
		// paddings and gaps accounted for.
		let ( vw, vh ) = ltk::viewport_size();
		let ( vw, vh ) = ( vw as f32, vh as f32 );
		let cells: Element<Msg> = match ltk::orientation()
		{
			ltk::Orientation::Landscape =>
			{
				let cell = ( vh * 0.5 ).min( ( vw - 80.0 ) / 3.0 ).max( CELL_MIN );
				row()
					.spacing( 16.0 )
					.push( clipped_cell( cell, |r| rounded_rect_path( r, 32.0 ) ) )
					.push( clipped_cell( cell, circle_path ) )
					.push( clipped_cell( cell, triangle_path ) )
					.into()
			}
			ltk::Orientation::Portrait =>
			{
				let cell = ( vw * 0.55 ).min( ( vh - 130.0 ) / 3.0 ).max( CELL_MIN );
				column()
					.padding( 0.0 )
					.spacing( 16.0 )
					.push( clipped_cell( cell, |r| rounded_rect_path( r, 32.0 ) ) )
					.push( clipped_cell( cell, circle_path ) )
					.push( clipped_cell( cell, triangle_path ) )
					.into()
			}
		};

		column()
			.spacing( 16.0 )
			.padding( 24.0 )
			.push( text( "Canvas::set_clip_path — rounded rect, circle, triangle" ) )
			.push( cells )
			.into()
	}

	fn update( &mut self, _msg: Msg ) {}

	fn on_key( &mut self, keysym: Keysym ) -> Option<Msg>
	{
		if keysym == Keysym::Escape
		{
			std::process::exit( 0 );
		}
		None
	}

	fn background_color( &self ) -> Color
	{
		ltk::theme_palette().bg
	}
}

fn main()
{
	ltk::run( Demo );
}
