mirror of
https://github.com/danbulant/cushy
synced 2026-05-24 12:28:23 +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;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::ops::{ControlFlow, Deref, Div, Mul};
|
||||
use std::panic::{RefUnwindSafe, UnwindSafe};
|
||||
|
|
@ -780,9 +781,13 @@ macro_rules! impl_percent_between {
|
|||
impl PercentBetween for $type {
|
||||
fn percent_between(&self, min: &Self, max: &Self) -> ZeroToOne {
|
||||
assert!(min <= max, "percent_between requires min <= max");
|
||||
assert!(
|
||||
self >= min && self <= max,
|
||||
"self must satisfy min <= self <= max"
|
||||
);
|
||||
|
||||
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,
|
||||
max: Color,
|
||||
func: impl Fn(Color) -> u8,
|
||||
) -> ZeroToOne {
|
||||
func(value).percent_between(&func(min), &func(max))
|
||||
) -> Option<ZeroToOne> {
|
||||
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(
|
||||
(*channel_percent(*self, *min, *max, Color::red)
|
||||
+ *channel_percent(*self, *min, *max, Color::green)
|
||||
+ *channel_percent(*self, *min, *max, Color::blue)
|
||||
+ *channel_percent(*self, *min, *max, Color::alpha))
|
||||
/ 4.,
|
||||
)
|
||||
let mut total_percent_change = 0.;
|
||||
let mut different_channels = 0_u8;
|
||||
|
||||
for func in [Color::red, Color::green, Color::blue, Color::alpha] {
|
||||
if let Some(red) = channel_percent(*self, *min, *max, func) {
|
||||
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.
|
||||
///
|
||||
/// Because of these restrictions, this type implements `Ord` and `Eq`.
|
||||
|
|
@ -922,19 +967,19 @@ impl PartialEq<f32> 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)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
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
|
||||
/// desaturated variation of the primary color will be used.
|
||||
pub neutral_variant: Option<ColorSource>,
|
||||
hue_shift: f32,
|
||||
hue_shift: OklabHue,
|
||||
}
|
||||
|
||||
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.
|
||||
#[must_use]
|
||||
pub fn new(primary: ColorSource) -> Self {
|
||||
pub fn new(primary: impl ProtoColor) -> Self {
|
||||
Self {
|
||||
primary,
|
||||
primary: primary.into_source(ZeroToOne::new(0.8)),
|
||||
secondary: None,
|
||||
tertiary: None,
|
||||
error: None,
|
||||
neutral: 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 {
|
||||
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 {
|
||||
hue: self.primary.hue - hue_shift,
|
||||
saturation: self.primary.saturation / 3.,
|
||||
|
|
@ -1726,10 +1721,59 @@ impl ColorSchemeBuilder {
|
|||
fn generate_neutral_variant(&self) -> ColorSource {
|
||||
ColorSource {
|
||||
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
|
||||
/// unspecified colors.
|
||||
#[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.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct ColorScheme {
|
||||
|
|
@ -1773,20 +1880,14 @@ pub struct ColorScheme {
|
|||
impl ColorScheme {
|
||||
/// Returns a generated color scheme based on a `primary` color.
|
||||
#[must_use]
|
||||
pub fn from_primary(primary: ColorSource) -> Self {
|
||||
pub fn from_primary(primary: impl ProtoColor) -> Self {
|
||||
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 {
|
||||
fn default() -> Self {
|
||||
Self::from_primary_hue(138.5)
|
||||
Self::from_primary(138.5)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue