Skip to content

Commit

Permalink
Indicator<T> + Radio refactor
Browse files Browse the repository at this point in the history
Refs #203
  • Loading branch information
ecton committed Nov 4, 2024
1 parent 44b1053 commit 804fbbf
Show file tree
Hide file tree
Showing 15 changed files with 943 additions and 490 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `ConstraintLimit::fit_measured` and `FitMeasuredSize::fit_measured` now accept
either a `Px` or `UPx` measurement, and does not perform scaling adjustments.
To convert `Lp` use `into_upx()` first.
- `Radio` and `Checkbox` are now powered by a new widget `Indicator<T>`. This
new implementation treats the indicators as independently focusable widgets
rather than how the `Button`-powered implementation shows focus around the
entire "label" of the button.

The `Button`-powered implementation can still be used by using the `kind`
function to pick the `ButtonKind` to use. Prior to this change,
`ButtonKind::Transparent` was the default.

Lastly, several APIs no longer accept a `label` parameter. Instead, the
widgets have new functions `labelled_by(label)` that can be used to attach a
clickable label to an indicator. The affected APIs are:

- `Radio::new`
- `Checkbox::new`
- `Checkable::into_checkbox`
- `Checkable::to_checkbox`
- `Dynamic::new_radio`
- `Dynamic::new_checkbox`

### Changed

Expand Down
15 changes: 12 additions & 3 deletions examples/file-picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,18 @@ fn main(app: &mut App) -> cushy::Result {
pending
.with_root(
picker_mode
.new_radio(PickerMode::SaveFile, "Save File")
.and(picker_mode.new_radio(PickerMode::PickFile, "Pick File"))
.and(picker_mode.new_radio(PickerMode::PickFolder, "Pick Folder"))
.new_radio(PickerMode::SaveFile)
.labelled_by("Save File")
.and(
picker_mode
.new_radio(PickerMode::PickFile)
.labelled_by("Pick File"),
)
.and(
picker_mode
.new_radio(PickerMode::PickFolder)
.labelled_by("Pick Folder"),
)
.into_columns()
.and(
pick_multiple
Expand Down
21 changes: 11 additions & 10 deletions examples/fullscreen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ fn main(app: &mut App) -> cushy::Result {
let fullscreen = Dynamic::new(None);

fullscreen
.new_radio(None, "Not Fullscreen")
.new_radio(None)
.labelled_by("Not Fullscreen")
.and(
monitors
.available
Expand Down Expand Up @@ -38,20 +39,20 @@ fn monitor_modes(
let name = monitor.name().unwrap_or_else(|| format!("Monitor {index}"));

name.h1()
.and(fullscreen.new_radio(
Some(Fullscreen::Borderless(Some(monitor.handle().clone()))),
"Borderless Fullscreen",
))
.and(
fullscreen
.new_radio(Some(Fullscreen::Borderless(Some(monitor.handle().clone()))))
.labelled_by("Borderless Fullscreen"),
)
.chain(monitor.video_modes().map(|mode| {
fullscreen.new_radio(
Some(Fullscreen::Exclusive(mode.handle().clone())),
format!(
fullscreen
.new_radio(Some(Fullscreen::Exclusive(mode.handle().clone())))
.labelled_by(format!(
"{}x{} @ {}Hz ({}-bit color)",
mode.size().width,
mode.size().height,
mode.refresh_rate_millihertz() as f32 / 1_000.,
mode.bit_depth()
),
)
))
}))
}
23 changes: 16 additions & 7 deletions examples/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ fn main() -> cushy::Result {

let apsect_mode_select = "Mode"
.h3()
.and(aspect_mode.new_radio(Aspect::Fit, "Fit"))
.and(aspect_mode.new_radio(Aspect::Fill, "Fill"))
.and(aspect_mode.new_radio(Aspect::Fit).labelled_by("Fit"))
.and(aspect_mode.new_radio(Aspect::Fill).labelled_by("Fill"))
.into_rows();

let hide_aspect_editor = mode.map_each(|scale| !matches!(scale, ScalingMode::Aspect));
Expand All @@ -77,20 +77,29 @@ fn main() -> cushy::Result {

let filter_select = "Filter mode"
.h1()
.and(selected_filter.new_radio(FilterMode::Nearest, "Nearest"))
.and(selected_filter.new_radio(FilterMode::Linear, "Linear"))
.and(
selected_filter
.new_radio(FilterMode::Nearest)
.labelled_by("Nearest"),
)
.and(
selected_filter
.new_radio(FilterMode::Linear)
.labelled_by("Linear"),
)
.into_rows();

let mode_select = "Scaling Mode"
.h1()
.and(mode.new_radio(ScalingMode::Scale, "Scale"))
.and(mode.new_radio(ScalingMode::Scale).labelled_by("Scale"))
.and(scale_editor)
.and(
mode.new_radio(ScalingMode::Aspect, "Aspect")
mode.new_radio(ScalingMode::Aspect)
.labelled_by("Aspect")
.and(aspect_editor)
.into_rows(),
)
.and(mode.new_radio(ScalingMode::Stretch, "Stretch"))
.and(mode.new_radio(ScalingMode::Stretch).labelled_by("Stretch"))
.and(filter_select)
.into_rows();

Expand Down
6 changes: 5 additions & 1 deletion examples/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ fn list() -> impl MakeWidget {
let current_style: Dynamic<ListStyle> = Dynamic::default();
let options = ListStyle::provided()
.into_iter()
.map(|style| current_style.new_radio(style.clone(), format!("{style:?}")))
.map(|style| {
current_style
.new_radio(style.clone())
.labelled_by(format!("{style:?}"))
})
.collect::<WidgetList>();

let rows = (1..100).map(|i| i.to_string()).collect::<WidgetList>();
Expand Down
7 changes: 4 additions & 3 deletions examples/radio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ fn main() -> cushy::Result {
let option = Dynamic::default();

option
.new_radio(Choice::A, "A")
.and(option.new_radio(Choice::B, "B"))
.and(option.new_radio(Choice::C, "C"))
.new_radio(Choice::A)
.labelled_by("A")
.and(option.new_radio(Choice::B).labelled_by("B"))
.and(option.new_radio(Choice::C).labelled_by("C"))
.into_rows()
.centered()
.run()
Expand Down
5 changes: 3 additions & 2 deletions examples/shared-switcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ fn main(app: &mut PendingApp) -> cushy::Result {

// Open up another window containing our controls
selected
.new_radio(Contents::A, "A")
.and(selected.new_radio(Contents::B, "B"))
.new_radio(Contents::A)
.labelled_by("A")
.and(selected.new_radio(Contents::B).labelled_by("B"))
.into_rows()
.open(app)?;

Expand Down
42 changes: 33 additions & 9 deletions examples/wrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,45 @@ fn main() -> cushy::Result {
.and(
"Wrap Align"
.h5()
.and(align.new_radio(WrapAlign::Start, "Start"))
.and(align.new_radio(WrapAlign::End, "End"))
.and(align.new_radio(WrapAlign::Center, "Center"))
.and(align.new_radio(WrapAlign::SpaceAround, "Space Around"))
.and(align.new_radio(WrapAlign::SpaceEvenly, "Space Evenly"))
.and(align.new_radio(WrapAlign::SpaceBetween, "Space Between"))
.and(align.new_radio(WrapAlign::Start).labelled_by("Start"))
.and(align.new_radio(WrapAlign::End).labelled_by("End"))
.and(align.new_radio(WrapAlign::Center).labelled_by("Center"))
.and(
align
.new_radio(WrapAlign::SpaceAround)
.labelled_by("Space Around"),
)
.and(
align
.new_radio(WrapAlign::SpaceEvenly)
.labelled_by("Space Evenly"),
)
.and(
align
.new_radio(WrapAlign::SpaceBetween)
.labelled_by("Space Between"),
)
.into_rows()
.contain(),
)
.and(
"Vertical Align"
.h5()
.and(vertical_align.new_radio(VerticalAlign::Top, "Top"))
.and(vertical_align.new_radio(VerticalAlign::Middle, "Middle"))
.and(vertical_align.new_radio(VerticalAlign::Bottom, "Bottom"))
.and(
vertical_align
.new_radio(VerticalAlign::Top)
.labelled_by("Top"),
)
.and(
vertical_align
.new_radio(VerticalAlign::Middle)
.labelled_by("Middle"),
)
.and(
vertical_align
.new_radio(VerticalAlign::Bottom)
.labelled_by("Bottom"),
)
.into_rows()
.contain(),
)
Expand Down
14 changes: 14 additions & 0 deletions src/styles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1978,6 +1978,20 @@ pub trait ColorExt: Copy {
self.into_hsla().hsl.lightness
}

/// Returns this color lightened by `amount`.
fn lighten_by(self, amount: ZeroToOne) -> Color {
let mut hsla = self.into_hsla();
hsla.hsl.lightness /= amount;
hsla.into()
}

/// Returns this color darkened by `amount`.
fn darken_by(self, amount: ZeroToOne) -> Color {
let mut hsla = self.into_hsla();
hsla.hsl.lightness *= amount;
hsla.into()
}

/// Returns the contrast between this color and the components provided.
///
/// To achieve a contrast of 1.0:
Expand Down
19 changes: 14 additions & 5 deletions src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ use crate::utils::WithClone;
use crate::widget::{
MakeWidget, MakeWidgetWithTag, OnceCallback, WidgetId, WidgetInstance, WidgetList,
};
use crate::widgets::{Label, Radio, Select, Space, Switcher};
use crate::widgets::checkbox::CheckboxState;
use crate::widgets::{Checkbox, Label, Radio, Select, Space, Switcher};
use crate::window::WindowHandle;

/// A source of one or more `T` values.
Expand Down Expand Up @@ -1295,18 +1296,26 @@ impl<T> Dynamic<T> {
}

/// Returns a new [`Radio`] that updates this dynamic to `widget_value` when
/// pressed. `label` is drawn next to the checkbox and is also clickable to
/// select the radio.
/// pressed.
#[must_use]
pub fn new_radio(&self, widget_value: T, label: impl MakeWidget) -> Radio<T>
pub fn new_radio(&self, widget_value: T) -> Radio<T>
where
Self: Clone,
// Technically this trait bound isn't necessary, but it prevents trying
// to call new_radio on unsupported types. The MakeWidget/Widget
// implementations require these bounds (and more).
T: Clone + PartialEq,
{
Radio::new(widget_value, self.clone(), label)
Radio::new(widget_value, self.clone())
}

/// Returns a new checkbox that updates `self` when clicked.
#[must_use]
pub fn new_checkbox(&self) -> Checkbox
where
Self: IntoDynamic<CheckboxState>,
{
Checkbox::new(self.clone())
}

/// Returns a new [`Select`] that updates this dynamic to `widget_value`
Expand Down
1 change: 1 addition & 0 deletions src/widgets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub mod disclose;
mod expand;
pub mod grid;
pub mod image;
pub mod indicator;
pub mod input;
pub mod label;
pub mod layers;
Expand Down
8 changes: 2 additions & 6 deletions src/widgets/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::styles::components::{
DefaultHoveredForegroundColor, Easing, HighlightColor, IntrinsicPadding, OpaqueWidgetColor,
OutlineColor, OutlineWidth, SurfaceColor, TextColor,
};
use crate::styles::{ColorExt, Hsla, Styles};
use crate::styles::{ColorExt, Styles};
use crate::value::{Destination, Dynamic, IntoValue, Source, Value};
use crate::widget::{
Callback, EventHandling, MakeWidget, SharedCallback, Widget, WidgetRef, HANDLED,
Expand Down Expand Up @@ -575,11 +575,7 @@ define_components! {
ButtonActiveBackground(Color, "active_background_color", .surface.color)
/// The background color of the button when the mouse cursor is hovering over
/// it.
ButtonHoverBackground(Color, "hover_background_color", |context| {
let mut hsla = Hsla::from(context.get(&ButtonBackground));
hsla.hsl.lightness *= ZeroToOne::new(0.8);
Color::from(hsla)
})
ButtonHoverBackground(Color, "hover_background_color", |context| context.get(&ButtonBackground).darken_by(ZeroToOne::new(0.8)))
/// The background color of the button when the mouse cursor is hovering over
/// it.
ButtonDisabledBackground(Color, "disabled_background_color", .surface.dim_color)
Expand Down
Loading

0 comments on commit 804fbbf

Please sign in to comment.