use std::sync::Arc;
use crate::types::Color;
use crate::layout::column::column;
use crate::layout::row::row;
use crate::layout::spacer::spacer;
use crate::theme::{ ColorStop, GradientSpace, LinearGradient, Paint };
use super::Element;
mod theme;
#[ cfg( test ) ]
mod tests;
pub fn color_to_hex( c: Color, with_alpha: bool ) -> String
{
let to_byte = | f: f32 | ( f.clamp( 0.0, 1.0 ) * 255.0 ).round() as u8;
let r = to_byte( c.r );
let g = to_byte( c.g );
let b = to_byte( c.b );
let a = to_byte( c.a );
if with_alpha && a != 255
{
format!( "#{:02X}{:02X}{:02X}{:02X}", r, g, b, a )
} else {
format!( "#{:02X}{:02X}{:02X}", r, g, b )
}
}
pub fn parse_hex( s: &str ) -> Option<Color>
{
let s = s.trim();
let s = s.strip_prefix( '#' ).unwrap_or( s );
let parse_byte = | hi: char, lo: char | -> Option<u8>
{
let h = hi.to_digit( 16 )?;
let l = lo.to_digit( 16 )?;
Some( ( ( h << 4 ) | l ) as u8 )
};
let chars: Vec<char> = s.chars().collect();
let to_color = | r: u8, g: u8, b: u8, a: u8 | Color::rgba(
r as f32 / 255.0,
g as f32 / 255.0,
b as f32 / 255.0,
a as f32 / 255.0,
);
match chars.len()
{
3 =>
{
let r = parse_byte( chars[0], chars[0] )?;
let g = parse_byte( chars[1], chars[1] )?;
let b = parse_byte( chars[2], chars[2] )?;
Some( to_color( r, g, b, 255 ) )
}
4 =>
{
let r = parse_byte( chars[0], chars[0] )?;
let g = parse_byte( chars[1], chars[1] )?;
let b = parse_byte( chars[2], chars[2] )?;
let a = parse_byte( chars[3], chars[3] )?;
Some( to_color( r, g, b, a ) )
}
6 =>
{
let r = parse_byte( chars[0], chars[1] )?;
let g = parse_byte( chars[2], chars[3] )?;
let b = parse_byte( chars[4], chars[5] )?;
Some( to_color( r, g, b, 255 ) )
}
8 =>
{
let r = parse_byte( chars[0], chars[1] )?;
let g = parse_byte( chars[2], chars[3] )?;
let b = parse_byte( chars[4], chars[5] )?;
let a = parse_byte( chars[6], chars[7] )?;
Some( to_color( r, g, b, a ) )
}
_ => None,
}
}
pub struct ColorPicker<Msg: Clone>
{
pub value: Color,
pub on_change: Option<Arc<dyn Fn( Color ) -> Msg>>,
pub show_alpha: bool,
}
impl<Msg: Clone + 'static> ColorPicker<Msg>
{
pub fn new( value: Color ) -> Self
{
Self
{
value,
on_change: None,
show_alpha: false,
}
}
pub fn on_change( mut self, f: impl Fn( Color ) -> Msg + 'static ) -> Self
{
self.on_change = Some( Arc::new( f ) );
self
}
pub fn show_alpha( mut self, on: bool ) -> Self
{
self.show_alpha = on;
self
}
pub fn build( self ) -> Element<Msg>
{
use super::{ container, text, text_edit };
use super::slider::slider;
let value = self.value;
let on_chg = self.on_change.clone();
let show_alpha = self.show_alpha;
let swatch: Element<Msg> = container::<Msg>( spacer() )
.background( value )
.border( theme::divider(), 1.0 )
.radius( 12.0 )
.padding( 0.0 )
.into();
let mut preview_row = row::<Msg>().spacing( theme::SPACING ).push(
container::<Msg>( swatch )
.padding( 0.0 )
.radius( 12.0 ),
);
let _ = theme::SWATCH_SZ;
let hex_value = color_to_hex( value, show_alpha );
let mut hex_edit = text_edit::<Msg>( "#RRGGBB", hex_value );
if let Some( ref cb ) = on_chg
{
let cb = cb.clone();
hex_edit = hex_edit.on_change( move |s|
{
match parse_hex( &s )
{
Some( c ) => cb( c ),
None => cb( value ), }
} );
}
preview_row = preview_row.push( hex_edit );
let chan_slider = | label: &str, current: f32, build_color: Arc<dyn Fn( f32 ) -> Color> | -> Element<Msg>
{
let mut s = slider::<Msg>( current );
if let Some( ref cb ) = on_chg
{
let cb_outer = cb.clone();
s = s.on_change( move |v|
{
let c = build_color( v );
cb_outer( c )
} );
}
column::<Msg>().spacing( 4.0 )
.push( text( label ).size( theme::LABEL_FS ).color( theme::text_muted() ) )
.push( s )
.into()
};
let r_build: Arc<dyn Fn( f32 ) -> Color> = Arc::new( move |v| Color::rgba( v, value.g, value.b, value.a ) );
let g_build: Arc<dyn Fn( f32 ) -> Color> = Arc::new( move |v| Color::rgba( value.r, v, value.b, value.a ) );
let b_build: Arc<dyn Fn( f32 ) -> Color> = Arc::new( move |v| Color::rgba( value.r, value.g, v, value.a ) );
let a_build: Arc<dyn Fn( f32 ) -> Color> = Arc::new( move |v| Color::rgba( value.r, value.g, value.b, v ) );
let mut sliders = column::<Msg>().spacing( theme::SPACING )
.push( chan_slider( "R", value.r, r_build ) )
.push( chan_slider( "G", value.g, g_build ) )
.push( chan_slider( "B", value.b, b_build ) );
if show_alpha
{
sliders = sliders.push( chan_slider( "A", value.a, a_build ) );
}
const HUE_RANGE: f32 = 359.0;
let current_hue = rgb_to_hue( value.r, value.g, value.b );
let hue_alpha = value.a;
let mut hue_slider = slider::<Msg>( ( current_hue / HUE_RANGE ).clamp( 0.0, 1.0 ) )
.track_paint( rainbow_gradient() );
if let Some( ref cb ) = on_chg
{
let cb = cb.clone();
hue_slider = hue_slider.on_change( move |v|
{
let ( r, g, b ) = hue_to_rgb( v.clamp( 0.0, 1.0 ) * HUE_RANGE );
cb( Color::rgba( r, g, b, hue_alpha ) )
} );
}
let hue_row: Element<Msg> = column::<Msg>().spacing( 4.0 )
.push( text( "Hue" ).size( theme::LABEL_FS ).color( theme::text_muted() ) )
.push( hue_slider )
.into();
let body = column::<Msg>().spacing( theme::SPACING * 2.0 )
.push( preview_row )
.push( sliders )
.push( hue_row );
container::<Msg>( body )
.background( theme::surface_alt() )
.padding( theme::PADDING )
.radius( theme::RADIUS )
.into()
}
}
fn rainbow_gradient() -> Paint
{
let stop = | pos: f32, c: Color | ColorStop { position: pos, color: c };
Paint::Linear( LinearGradient
{
angle_deg: 90.0,
stops: vec!
[
stop( 0.000, Color::rgba( 1.0, 0.0, 0.0, 1.0 ) ), stop( 1.0 / 6.0, Color::rgba( 1.0, 1.0, 0.0, 1.0 ) ), stop( 2.0 / 6.0, Color::rgba( 0.0, 1.0, 0.0, 1.0 ) ), stop( 3.0 / 6.0, Color::rgba( 0.0, 1.0, 1.0, 1.0 ) ), stop( 4.0 / 6.0, Color::rgba( 0.0, 0.0, 1.0, 1.0 ) ), stop( 5.0 / 6.0, Color::rgba( 1.0, 0.0, 1.0, 1.0 ) ), stop( 1.000, Color::rgba( 1.0, 0.0, 0.0, 1.0 ) ), ],
space: GradientSpace::Srgb,
} )
}
pub fn rgb_to_hue( r: f32, g: f32, b: f32 ) -> f32
{
let max = r.max( g ).max( b );
let min = r.min( g ).min( b );
let delta = max - min;
if delta <= f32::EPSILON { return 0.0; }
let h = if max == r
{
60.0 * ( ( ( g - b ) / delta ).rem_euclid( 6.0 ) )
}
else if max == g
{
60.0 * ( ( b - r ) / delta + 2.0 )
}
else
{
60.0 * ( ( r - g ) / delta + 4.0 )
};
if h < 0.0 { h + 360.0 } else { h }
}
pub fn hue_to_rgb( hue_deg: f32 ) -> ( f32, f32, f32 )
{
let h = hue_deg.rem_euclid( 360.0 ) / 60.0;
let c = 1.0_f32;
let x = c * ( 1.0 - ( ( h.rem_euclid( 2.0 ) ) - 1.0 ).abs() );
let ( r, g, b ) = match h as u32
{
0 => ( c, x, 0.0 ),
1 => ( x, c, 0.0 ),
2 => ( 0.0, c, x ),
3 => ( 0.0, x, c ),
4 => ( x, 0.0, c ),
_ => ( c, 0.0, x ),
};
( r, g, b )
}
impl<Msg: Clone + 'static> From<ColorPicker<Msg>> for Element<Msg>
{
fn from( c: ColorPicker<Msg> ) -> Self { c.build() }
}
pub fn color_picker<Msg: Clone + 'static>( value: Color ) -> ColorPicker<Msg>
{
ColorPicker::new( value )
}