mirror of
https://github.com/danbulant/cushy
synced 2026-07-08 12:40:36 +00:00
New slider example showing min/max
This commit is contained in:
parent
42ed86cdfd
commit
4668db3983
3 changed files with 208 additions and 33 deletions
29
examples/slider.rs
Normal file
29
examples/slider.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
use gooey::value::{Dynamic, StringValue};
|
||||||
|
use gooey::widget::MakeWidget;
|
||||||
|
use gooey::widgets::slider::Slidable;
|
||||||
|
use gooey::Run;
|
||||||
|
use kludgine::figures::units::Lp;
|
||||||
|
|
||||||
|
fn main() -> gooey::Result {
|
||||||
|
let min_text = Dynamic::new(u8::MIN.to_string());
|
||||||
|
let min = min_text.map_each(|min| min.parse().unwrap_or(u8::MIN));
|
||||||
|
let max_text = Dynamic::new(u8::MAX.to_string());
|
||||||
|
let max = max_text.map_each(|max| max.parse().unwrap_or(u8::MAX));
|
||||||
|
let value = Dynamic::new(128_u8);
|
||||||
|
let value_text = value.map_each(ToString::to_string);
|
||||||
|
|
||||||
|
"Min"
|
||||||
|
.and(min_text.into_input())
|
||||||
|
.and("Max")
|
||||||
|
.and(max_text.into_input())
|
||||||
|
.into_columns()
|
||||||
|
.centered()
|
||||||
|
.and(value.slider_between(min, max))
|
||||||
|
.and(value_text.centered())
|
||||||
|
.into_rows()
|
||||||
|
.expand_horizontally()
|
||||||
|
.width(..Lp::points(800))
|
||||||
|
.centered()
|
||||||
|
.expand()
|
||||||
|
.run()
|
||||||
|
}
|
||||||
|
|
@ -39,6 +39,7 @@
|
||||||
|
|
||||||
pub mod easings;
|
pub mod easings;
|
||||||
|
|
||||||
|
use std::cmp::Ordering;
|
||||||
use std::fmt::{Debug, Display};
|
use std::fmt::{Debug, Display};
|
||||||
use std::ops::{ControlFlow, Deref, Div, Mul};
|
use std::ops::{ControlFlow, Deref, Div, Mul};
|
||||||
use std::panic::{RefUnwindSafe, UnwindSafe};
|
use std::panic::{RefUnwindSafe, UnwindSafe};
|
||||||
|
|
@ -780,9 +781,13 @@ macro_rules! impl_percent_between {
|
||||||
impl PercentBetween for $type {
|
impl PercentBetween for $type {
|
||||||
fn percent_between(&self, min: &Self, max: &Self) -> ZeroToOne {
|
fn percent_between(&self, min: &Self, max: &Self) -> ZeroToOne {
|
||||||
assert!(min <= max, "percent_between requires min <= max");
|
assert!(min <= max, "percent_between requires min <= max");
|
||||||
|
assert!(
|
||||||
|
self >= min && self <= max,
|
||||||
|
"self must satisfy min <= self <= max"
|
||||||
|
);
|
||||||
|
|
||||||
let range = *max - *min;
|
let range = *max - *min;
|
||||||
ZeroToOne::from(*self as $float / range as $float)
|
ZeroToOne::from((*self - *min) as $float / range as $float)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -810,20 +815,60 @@ impl PercentBetween for Color {
|
||||||
min: Color,
|
min: Color,
|
||||||
max: Color,
|
max: Color,
|
||||||
func: impl Fn(Color) -> u8,
|
func: impl Fn(Color) -> u8,
|
||||||
) -> ZeroToOne {
|
) -> Option<ZeroToOne> {
|
||||||
func(value).percent_between(&func(min), &func(max))
|
let value = func(value);
|
||||||
|
let min = func(min);
|
||||||
|
let max = func(max);
|
||||||
|
match min.cmp(&max) {
|
||||||
|
Ordering::Less => Some(value.percent_between(&min, &max)),
|
||||||
|
Ordering::Equal => None,
|
||||||
|
Ordering::Greater => Some(value.percent_between(&max, &min).one_minus()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ZeroToOne::new(
|
let mut total_percent_change = 0.;
|
||||||
(*channel_percent(*self, *min, *max, Color::red)
|
let mut different_channels = 0_u8;
|
||||||
+ *channel_percent(*self, *min, *max, Color::green)
|
|
||||||
+ *channel_percent(*self, *min, *max, Color::blue)
|
for func in [Color::red, Color::green, Color::blue, Color::alpha] {
|
||||||
+ *channel_percent(*self, *min, *max, Color::alpha))
|
if let Some(red) = channel_percent(*self, *min, *max, func) {
|
||||||
/ 4.,
|
total_percent_change += *red;
|
||||||
)
|
different_channels += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if different_channels > 0 {
|
||||||
|
ZeroToOne::new(total_percent_change / f32::from(different_channels))
|
||||||
|
} else {
|
||||||
|
ZeroToOne::ZERO
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn int_percent_between() {
|
||||||
|
assert_eq!(1_u8.percent_between(&1_u8, &2_u8), ZeroToOne::ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn color_lerp() {
|
||||||
|
let gray = Color::new(51, 51, 51, 51);
|
||||||
|
let percent_gray = gray.percent_between(&Color::CLEAR_BLACK, &Color::WHITE);
|
||||||
|
|
||||||
|
assert_eq!(gray, Color::CLEAR_BLACK.lerp(&Color::WHITE, *percent_gray));
|
||||||
|
|
||||||
|
let gray = Color::new(51, 51, 51, 255);
|
||||||
|
let percent_gray = gray.percent_between(&Color::BLACK, &Color::WHITE);
|
||||||
|
|
||||||
|
assert_eq!(gray, Color::BLACK.lerp(&Color::WHITE, *percent_gray));
|
||||||
|
|
||||||
|
let red_green = Color::RED.lerp(&Color::GREEN, 0.5);
|
||||||
|
let percent_between = red_green.percent_between(&Color::RED, &Color::GREEN);
|
||||||
|
// Why 1 / 255 / 4? This operation is working on u8s, and there are 4
|
||||||
|
// channels that can be averaged. The percent is guaranteed to be within
|
||||||
|
// this range, which works out to be 0.0098 percent.
|
||||||
|
assert!((*percent_between - 0.5).abs() < 1. / 255. / 4.);
|
||||||
|
}
|
||||||
|
|
||||||
/// An `f32` that is clamped between 0.0 and 1.0 and cannot be NaN or Infinity.
|
/// An `f32` that is clamped between 0.0 and 1.0 and cannot be NaN or Infinity.
|
||||||
///
|
///
|
||||||
/// Because of these restrictions, this type implements `Ord` and `Eq`.
|
/// Because of these restrictions, this type implements `Ord` and `Eq`.
|
||||||
|
|
@ -922,19 +967,19 @@ impl PartialEq<f32> for ZeroToOne {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ord for ZeroToOne {
|
impl Ord for ZeroToOne {
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
self.0.total_cmp(&other.0)
|
self.0.total_cmp(&other.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd for ZeroToOne {
|
impl PartialOrd for ZeroToOne {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
Some(self.cmp(other))
|
Some(self.cmp(other))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd<f32> for ZeroToOne {
|
impl PartialOrd<f32> for ZeroToOne {
|
||||||
fn partial_cmp(&self, other: &f32) -> Option<std::cmp::Ordering> {
|
fn partial_cmp(&self, other: &f32) -> Option<Ordering> {
|
||||||
Some(self.0.total_cmp(other))
|
Some(self.0.total_cmp(other))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
141
src/styles.rs
141
src/styles.rs
|
|
@ -1665,27 +1665,21 @@ pub struct ColorSchemeBuilder {
|
||||||
/// The neutral variant color of the scheme. If not provided, a mostly
|
/// The neutral variant color of the scheme. If not provided, a mostly
|
||||||
/// desaturated variation of the primary color will be used.
|
/// desaturated variation of the primary color will be used.
|
||||||
pub neutral_variant: Option<ColorSource>,
|
pub neutral_variant: Option<ColorSource>,
|
||||||
hue_shift: f32,
|
hue_shift: OklabHue,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ColorSchemeBuilder {
|
impl ColorSchemeBuilder {
|
||||||
/// Returns a builder for the provided hue, in degrees.
|
|
||||||
#[must_use]
|
|
||||||
pub fn from_hue(hue: impl Into<OklabHue>) -> Self {
|
|
||||||
Self::new(ColorSource::new(hue, 0.8))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a builder for the provided primary color.
|
/// Returns a builder for the provided primary color.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(primary: ColorSource) -> Self {
|
pub fn new(primary: impl ProtoColor) -> Self {
|
||||||
Self {
|
Self {
|
||||||
primary,
|
primary: primary.into_source(ZeroToOne::new(0.8)),
|
||||||
secondary: None,
|
secondary: None,
|
||||||
tertiary: None,
|
tertiary: None,
|
||||||
error: None,
|
error: None,
|
||||||
neutral: None,
|
neutral: None,
|
||||||
neutral_variant: None,
|
neutral_variant: None,
|
||||||
hue_shift: 30.,
|
hue_shift: OklabHue::new(30.),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1697,7 +1691,8 @@ impl ColorSchemeBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_tertiary(&self, secondary: ColorSource) -> ColorSource {
|
fn generate_tertiary(&self, secondary: ColorSource) -> ColorSource {
|
||||||
let hue_shift = (secondary.hue - self.primary.hue).into_degrees().signum() * self.hue_shift;
|
let hue_shift = (secondary.hue - self.primary.hue).into_degrees().signum()
|
||||||
|
* self.hue_shift.into_degrees();
|
||||||
ColorSource {
|
ColorSource {
|
||||||
hue: self.primary.hue - hue_shift,
|
hue: self.primary.hue - hue_shift,
|
||||||
saturation: self.primary.saturation / 3.,
|
saturation: self.primary.saturation / 3.,
|
||||||
|
|
@ -1726,10 +1721,59 @@ impl ColorSchemeBuilder {
|
||||||
fn generate_neutral_variant(&self) -> ColorSource {
|
fn generate_neutral_variant(&self) -> ColorSource {
|
||||||
ColorSource {
|
ColorSource {
|
||||||
hue: self.primary.hue,
|
hue: self.primary.hue,
|
||||||
saturation: ZeroToOne::new(0.1),
|
saturation: self.primary.saturation / 10.,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the secondary color and returns self.
|
||||||
|
///
|
||||||
|
/// If `secondary` doesn't specify a saturation, a saturation value that is
|
||||||
|
/// 50% of the primary saturation will be picked.
|
||||||
|
#[must_use]
|
||||||
|
pub fn secondary(mut self, secondary: impl ProtoColor) -> Self {
|
||||||
|
self.secondary = Some(secondary.into_source(self.primary.saturation / 2.));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the tertiary color and returns self.
|
||||||
|
///
|
||||||
|
/// If `tertiary` doesn't specify a saturation, a saturation value that is
|
||||||
|
/// 33% of the primary saturation will be picked.
|
||||||
|
#[must_use]
|
||||||
|
pub fn tertiary(mut self, tertiary: impl ProtoColor) -> Self {
|
||||||
|
self.secondary = Some(tertiary.into_source(self.primary.saturation / 3.));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the neutral color and returns self.
|
||||||
|
///
|
||||||
|
/// If `neutral` doesn't specify a saturation, a saturation of 1%.
|
||||||
|
#[must_use]
|
||||||
|
pub fn neutral(mut self, neutral: impl ProtoColor) -> Self {
|
||||||
|
self.neutral = Some(neutral.into_source(0.01));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the neutral color and returns self.
|
||||||
|
///
|
||||||
|
/// If `neutral_variant` doesn't specify a saturation, a saturation value
|
||||||
|
/// that is 10% of the primary saturation will be picked.
|
||||||
|
#[must_use]
|
||||||
|
pub fn neutral_variant(mut self, neutral_variant: impl ProtoColor) -> Self {
|
||||||
|
self.neutral_variant = Some(neutral_variant.into_source(self.primary.saturation / 10.));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the amount the hue component is shifted when auto-generating colors
|
||||||
|
/// to fill in the palette.
|
||||||
|
///
|
||||||
|
/// The default hue shift is 30 degrees.
|
||||||
|
#[must_use]
|
||||||
|
pub fn hue_shift(mut self, hue_shift: impl Into<OklabHue>) -> Self {
|
||||||
|
self.hue_shift = hue_shift.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Builds a color scheme from the provided colors, generating any
|
/// Builds a color scheme from the provided colors, generating any
|
||||||
/// unspecified colors.
|
/// unspecified colors.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
@ -1753,6 +1797,69 @@ impl ColorSchemeBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A type that can be interpretted as a hue or hue and saturation.
|
||||||
|
pub trait ProtoColor: Sized {
|
||||||
|
/// Returns the hue of this prototype color.
|
||||||
|
#[must_use]
|
||||||
|
fn hue(&self) -> OklabHue;
|
||||||
|
/// Returns the saturation of this prototype color, if available.
|
||||||
|
#[must_use]
|
||||||
|
fn saturation(&self) -> Option<ZeroToOne>;
|
||||||
|
|
||||||
|
/// Returns a color source built from this prototype color
|
||||||
|
#[must_use]
|
||||||
|
fn into_source(self, saturation_if_not_provided: impl Into<ZeroToOne>) -> ColorSource {
|
||||||
|
let saturation = self
|
||||||
|
.saturation()
|
||||||
|
.unwrap_or_else(|| saturation_if_not_provided.into());
|
||||||
|
ColorSource::new(self.hue(), saturation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProtoColor for f32 {
|
||||||
|
fn hue(&self) -> OklabHue {
|
||||||
|
(*self).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn saturation(&self) -> Option<ZeroToOne> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProtoColor for OklabHue {
|
||||||
|
fn hue(&self) -> OklabHue {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn saturation(&self) -> Option<ZeroToOne> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProtoColor for ColorSource {
|
||||||
|
fn hue(&self) -> OklabHue {
|
||||||
|
self.hue
|
||||||
|
}
|
||||||
|
|
||||||
|
fn saturation(&self) -> Option<ZeroToOne> {
|
||||||
|
Some(self.saturation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Hue, Saturation> ProtoColor for (Hue, Saturation)
|
||||||
|
where
|
||||||
|
Hue: Into<OklabHue> + Copy,
|
||||||
|
Saturation: Into<ZeroToOne> + Copy,
|
||||||
|
{
|
||||||
|
fn hue(&self) -> OklabHue {
|
||||||
|
self.0.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn saturation(&self) -> Option<ZeroToOne> {
|
||||||
|
Some(self.1.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A color scheme for a Gooey application.
|
/// A color scheme for a Gooey application.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct ColorScheme {
|
pub struct ColorScheme {
|
||||||
|
|
@ -1773,20 +1880,14 @@ pub struct ColorScheme {
|
||||||
impl ColorScheme {
|
impl ColorScheme {
|
||||||
/// Returns a generated color scheme based on a `primary` color.
|
/// Returns a generated color scheme based on a `primary` color.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn from_primary(primary: ColorSource) -> Self {
|
pub fn from_primary(primary: impl ProtoColor) -> Self {
|
||||||
ColorSchemeBuilder::new(primary).build()
|
ColorSchemeBuilder::new(primary).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a generated color scheme based on a `primary` hue, in degrees.
|
|
||||||
#[must_use]
|
|
||||||
pub fn from_primary_hue(hue: impl Into<OklabHue>) -> Self {
|
|
||||||
ColorSchemeBuilder::from_hue(hue).build()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ColorScheme {
|
impl Default for ColorScheme {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::from_primary_hue(138.5)
|
Self::from_primary(138.5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue