ButtonClick + Overlays at locations

The overlay example now supports right-clicking to open overlays at the
clicked location, showing how a context menu widget can begin being
built.
This commit is contained in:
Jonathan Johnson 2024-05-08 08:38:26 -07:00
parent ecf0d45bfa
commit 1c8d4e0176
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
20 changed files with 165 additions and 76 deletions

View file

@ -68,6 +68,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `PlatformWindowImplementation::set_cursor_icon` and - `PlatformWindowImplementation::set_cursor_icon` and
`PlatformWindow::set_cursor_icon` have been renamed to `set_cursor` and accept `PlatformWindow::set_cursor_icon` have been renamed to `set_cursor` and accept
`winit` 0.30's new `Cursor` type. `winit` 0.30's new `Cursor` type.
- `Button::on_click` now takes a `Option<ButtonClick>` structure. When this
value is provided, information about the mouse click that caused the event is
provided.
### Fixed ### Fixed

View file

@ -3,6 +3,7 @@ use std::time::Duration;
use cushy::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn}; use cushy::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn};
use cushy::value::{Destination, Dynamic}; use cushy::value::{Destination, Dynamic};
use cushy::widget::MakeWidget; use cushy::widget::MakeWidget;
use cushy::widgets::button::ButtonClick;
use cushy::widgets::progress::Progressable; use cushy::widgets::progress::Progressable;
use cushy::{Run, WithClone}; use cushy::{Run, WithClone};
use figures::units::Lp; use figures::units::Lp;
@ -41,7 +42,7 @@ fn animate_to(
animation: &Dynamic<AnimationHandle>, animation: &Dynamic<AnimationHandle>,
value: &Dynamic<u8>, value: &Dynamic<u8>,
target: u8, target: u8,
) -> impl FnMut(()) { ) -> impl FnMut(Option<ButtonClick>) {
(animation, value).with_clone(|(animation, value)| { (animation, value).with_clone(|(animation, value)| {
move |_| { move |_| {
// Here we use spawn to schedule the animation, which returns an // Here we use spawn to schedule the animation, which returns an

View file

@ -27,7 +27,7 @@ fn main() -> cushy::Result {
.into_button() .into_button()
.on_click({ .on_click({
let task = dynamic.clone(); let task = dynamic.clone();
move |()| { move |_| {
let background_task = Task::default(); let background_task = Task::default();
spawn_background_thread(&background_task.progress, &task); spawn_background_thread(&background_task.progress, &task);
task.set(Some(background_task)); task.set(Some(background_task));

View file

@ -29,7 +29,7 @@ fn main() -> cushy::Result {
.into_button() .into_button()
.on_click({ .on_click({
let clicked_button = clicked_button.clone(); let clicked_button = clicked_button.clone();
move |()| clicked_button.set("inner button clicked") move |_| clicked_button.set("inner button clicked")
}) })
.with(&ButtonHoverBackground, Color::RED) .with(&ButtonHoverBackground, Color::RED)
.with(&ButtonHoverForeground, Color::WHITE); .with(&ButtonHoverForeground, Color::WHITE);
@ -40,7 +40,7 @@ fn main() -> cushy::Result {
.into_button() .into_button()
.on_click({ .on_click({
let clicked_button = clicked_button.clone(); let clicked_button = clicked_button.clone();
move |()| clicked_button.set("middle button clicked") move |_| clicked_button.set("middle button clicked")
}); });
let outer_button = "Yo dawg!" let outer_button = "Yo dawg!"
@ -49,7 +49,7 @@ fn main() -> cushy::Result {
.into_button() .into_button()
.on_click({ .on_click({
let clicked_button = clicked_button.clone(); let clicked_button = clicked_button.clone();
move |()| clicked_button.set("outer button clicked") move |_| clicked_button.set("outer button clicked")
}); });
outer_button outer_button

View file

@ -10,7 +10,7 @@ fn main() -> cushy::Result {
checkbox_state checkbox_state
.clone() .clone()
.to_checkbox(label) .to_checkbox(label)
.and("Maybe".into_button().on_click(move |()| { .and("Maybe".into_button().on_click(move |_| {
checkbox_state.set(CheckboxState::Indeterminant); checkbox_state.set(CheckboxState::Indeterminant);
})) }))
.into_columns() .into_columns()

View file

@ -87,7 +87,7 @@ fn edit_contact_form(contact: &Contact, db: &Dynamic<HashMap<u64, Contact>>) ->
.on_click({ .on_click({
let contact_id = contact.id; let contact_id = contact.id;
let db = db.clone(); let db = db.clone();
move |()| { move |_| {
let mut db = db.lock(); let mut db = db.lock();
let contact = db.get_mut(&contact_id).expect("missing contact"); let contact = db.get_mut(&contact_id).expect("missing contact");
contact.first_name = first.get(); contact.first_name = first.get();

View file

@ -19,7 +19,7 @@ fn main() -> cushy::Result {
let info = info.clone(); let info = info.clone();
let window_count = window_count.clone(); let window_count = window_count.clone();
let total_windows = total_windows.clone(); let total_windows = total_windows.clone();
move |()| open_a_window(&window_count, &total_windows, &info, &mut app) move |_| open_a_window(&window_count, &total_windows, &info, &mut app)
}) })
.make_widget(); .make_widget();

View file

@ -64,7 +64,7 @@ fn main() -> cushy::Result {
.and( .and(
"Log In" "Log In"
.into_button() .into_button()
.on_click(validations.when_valid(move |()| { .on_click(validations.when_valid(move |_| {
println!("Welcome, {}", username.get()); println!("Welcome, {}", username.get());
exit(0); exit(0);
})) }))

View file

@ -51,7 +51,7 @@ fn open_window_button(
let open_windows = open_windows.clone(); let open_windows = open_windows.clone();
let counter = counter.clone(); let counter = counter.clone();
let texture = texture.clone(); let texture = texture.clone();
"Open Another Window".into_button().on_click(move |()| { "Open Another Window".into_button().on_click(move |_| {
open_another_window(&mut app, &open_windows, &counter, &texture); open_another_window(&mut app, &open_windows, &counter, &texture);
}) })
} }
@ -82,7 +82,7 @@ fn open_another_window(
.and( .and(
"Close" "Close"
.into_button() .into_button()
.on_click(move |()| handle.request_close()), .on_click(move |_| handle.request_close()),
) )
.into_rows() .into_rows()
.centered(), .centered(),

View file

@ -4,6 +4,7 @@ use cushy::widgets::layers::{OverlayBuilder, OverlayLayer};
use cushy::Run; use cushy::Run;
use figures::units::Lp; use figures::units::Lp;
use figures::{Point, Zero}; use figures::{Point, Zero};
use kludgine::app::winit::event::MouseButton;
use kludgine::Color; use kludgine::Color;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
@ -55,10 +56,20 @@ fn show_overlay_button(
direction_func: impl for<'a> Fn(OverlayBuilder<'a>) -> OverlayBuilder<'a> + Send + 'static, direction_func: impl for<'a> Fn(OverlayBuilder<'a>) -> OverlayBuilder<'a> + Send + 'static,
) -> impl MakeWidget { ) -> impl MakeWidget {
let overlay = overlay.clone(); let overlay = overlay.clone();
label.into_button().on_click(move |()| { let button_tag = WidgetTag::unique();
direction_func(overlay.build_overlay(test_widget(&overlay, false))) let button_id = button_tag.id();
.hide_on_unhover() label
.show() .into_button()
.forget(); .on_click(move |click| {
}) let overlay = overlay.build_overlay(test_widget(&overlay, false));
let overlay = match click {
Some(click) if click.mouse_button == MouseButton::Right => {
overlay.above(button_id).at(click.window_location)
}
_ => direction_func(overlay),
};
overlay.hide_on_unhover().show().forget();
})
.make_with_tag(button_tag)
} }

View file

@ -80,7 +80,7 @@ fn main() -> cushy::Result {
.and(editors.neutral_variant.1) .and(editors.neutral_variant.1)
.and("Copy to Clipboard".into_button().on_click({ .and("Copy to Clipboard".into_button().on_click({
let cushy = app.cushy().clone(); let cushy = app.cushy().clone();
move |()| { move |_| {
if let Some(mut clipboard) = cushy.clipboard_guard() { if let Some(mut clipboard) = cushy.clipboard_guard() {
let builder = color_scheme_builder.get(); let builder = color_scheme_builder.get();
let mut source = String::default(); let mut source = String::default();

View file

@ -22,13 +22,13 @@ fn main() -> cushy::Result {
.and( .and(
"Submit" "Submit"
.into_button() .into_button()
.on_click(validations.clone().when_valid(move |()| { .on_click(validations.clone().when_valid(move |_| {
println!( println!(
"Success! This callback only happens when all associated validations are valid" "Success! This callback only happens when all associated validations are valid"
); );
})), })),
) )
.and("Reset".into_button().on_click(move |()| { .and("Reset".into_button().on_click(move |_| {
let _value = text.take(); let _value = text.take();
validations.reset(); validations.reset();
})) }))

View file

@ -52,7 +52,7 @@ impl DebugContext {
let id = self.section.map_ref(|section| { let id = self.section.map_ref(|section| {
section.values.lock().push(Box::new(RegisteredValue { section.values.lock().push(Box::new(RegisteredValue {
label: label.into(), label: label.into(),
value: reader.clone(), _value: reader.clone(),
widget: make_observer(value.weak_clone()).make_widget(), widget: make_observer(value.weak_clone()).make_widget(),
})) }))
}); });
@ -145,13 +145,13 @@ impl Drop for DebugContext {
trait Observable: Send { trait Observable: Send {
fn label(&self) -> &str; fn label(&self) -> &str;
fn alive(&self) -> bool; // fn alive(&self) -> bool;
fn widget(&self) -> &WidgetInstance; fn widget(&self) -> &WidgetInstance;
} }
struct RegisteredValue<T> { struct RegisteredValue<T> {
label: String, label: String,
value: DynamicReader<T>, _value: DynamicReader<T>,
widget: WidgetInstance, widget: WidgetInstance,
} }
@ -163,9 +163,9 @@ where
&self.label &self.label
} }
fn alive(&self) -> bool { // fn alive(&self) -> bool {
self.value.connected() // self.value.connected()
} // }
fn widget(&self) -> &WidgetInstance { fn widget(&self) -> &WidgetInstance {
&self.widget &self.widget

View file

@ -30,7 +30,7 @@ pub struct Button {
/// The label to display on the button. /// The label to display on the button.
pub content: WidgetRef, pub content: WidgetRef,
/// The callback that is invoked when the button is clicked. /// The callback that is invoked when the button is clicked.
pub on_click: Option<Callback<()>>, pub on_click: Option<Callback<Option<ButtonClick>>>,
/// The kind of button to draw. /// The kind of button to draw.
pub kind: Value<ButtonKind>, pub kind: Value<ButtonKind>,
focusable: bool, focusable: bool,
@ -158,7 +158,7 @@ impl Button {
#[must_use] #[must_use]
pub fn on_click<F>(mut self, callback: F) -> Self pub fn on_click<F>(mut self, callback: F) -> Self
where where
F: FnMut(()) + Send + 'static, F: FnMut(Option<ButtonClick>) + Send + 'static,
{ {
self.on_click = Some(Callback::new(callback)); self.on_click = Some(Callback::new(callback));
self self
@ -171,10 +171,10 @@ impl Button {
self self
} }
fn invoke_on_click(&mut self, context: &WidgetContext<'_>) { fn invoke_on_click(&mut self, button: Option<ButtonClick>, context: &WidgetContext<'_>) {
if context.enabled() { if context.enabled() {
if let Some(on_click) = self.on_click.as_mut() { if let Some(on_click) = self.on_click.as_mut() {
on_click.invoke(()); on_click.invoke(button);
} }
} }
} }
@ -461,7 +461,7 @@ impl Widget for Button {
&mut self, &mut self,
location: Option<Point<Px>>, location: Option<Point<Px>>,
_device_id: DeviceId, _device_id: DeviceId,
_button: MouseButton, button: MouseButton,
context: &mut EventContext<'_>, context: &mut EventContext<'_>,
) { ) {
let window_local = self.per_window.entry(context).or_default(); let window_local = self.per_window.entry(context).or_default();
@ -470,12 +470,19 @@ impl Widget for Button {
context.deactivate(); context.deactivate();
if let (true, Some(location)) = (self.focusable, location) { if let (true, Some(location)) = (self.focusable, location) {
if Rect::from(context.last_layout().expect("must have been rendered").size) let last_layout = context.last_layout().expect("must have been rendered");
.contains(location) // let button_relative
{ if Rect::from(last_layout.size).contains(location) {
context.focus(); context.focus();
self.invoke_on_click(context); self.invoke_on_click(
Some(ButtonClick {
mouse_button: button,
location,
window_location: location + last_layout.origin,
}),
context,
);
} }
} }
} }
@ -533,7 +540,7 @@ impl Widget for Button {
// If we have no buttons pressed, the event should fire on activate not // If we have no buttons pressed, the event should fire on activate not
// on deactivate. // on deactivate.
if window_local.buttons_pressed == 0 { if window_local.buttons_pressed == 0 {
self.invoke_on_click(context); self.invoke_on_click(None, context);
} }
self.update_colors(context, true); self.update_colors(context, true);
} }
@ -581,3 +588,14 @@ define_components! {
ButtonDisabledOutline(Color, "disabled_outline_color", Color::CLEAR_BLACK) ButtonDisabledOutline(Color, "disabled_outline_color", Color::CLEAR_BLACK)
} }
} }
/// A mouse click in a [`Button`].
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct ButtonClick {
/// The mouse button that caused the event.
pub mouse_button: MouseButton,
/// The location relative to the button of the click.
pub location: Point<Px>,
/// The location relative to the window of the click.
pub window_location: Point<Px>,
}

View file

@ -59,7 +59,7 @@ impl MakeWidgetWithTag for Checkbox {
.and(self.label) .and(self.label)
.into_columns() .into_columns()
.into_button() .into_button()
.on_click(move |()| { .on_click(move |_| {
let mut value = self.state.lock(); let mut value = self.state.lock();
*value = !*value; *value = !*value;
}) })

View file

@ -856,6 +856,12 @@ impl<const N: usize> DerefMut for GridWidgets<N> {
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub struct GridSection<const N: usize>([WidgetInstance; N]); pub struct GridSection<const N: usize>([WidgetInstance; N]);
impl Default for GridSection<0> {
fn default() -> Self {
Self::new()
}
}
impl GridSection<0> { impl GridSection<0> {
/// Returns an empty section. /// Returns an empty section.
#[must_use] #[must_use]

View file

@ -1243,9 +1243,6 @@ where
} }
} }
#[derive(Debug, PartialEq, Eq)]
struct NotVisible(Point<Px>, usize);
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
struct BlinkState { struct BlinkState {
visible: bool, visible: bool,

View file

@ -149,7 +149,7 @@ impl OverlayLayer {
layout: OverlayLayout { layout: OverlayLayout {
widget: WidgetRef::new(overlay), widget: WidgetRef::new(overlay),
relative_to: None, relative_to: None,
direction: Direction::Right, positioning: Position::Relative(Direction::Right),
requires_hover: false, requires_hover: false,
on_dismiss: None, on_dismiss: None,
layout: None, layout: None,
@ -395,26 +395,26 @@ impl OverlayState {
context: &mut LayoutContext<'_, '_, '_, '_>, context: &mut LayoutContext<'_, '_, '_, '_>,
relative_to: WidgetId, relative_to: WidgetId,
) -> Option<Rect<Px>> { ) -> Option<Rect<Px>> {
let direction = self.overlays[index].direction; let positioning = self.overlays[index].positioning;
let relative_to = relative_to.find_in(context)?.last_layout()?; let relative_to = relative_to.find_in(context)?.last_layout()?;
let relative_to_unsigned = relative_to.into_unsigned(); let relative_to_unsigned = relative_to.into_unsigned();
let constraints = match direction { let constraints = match positioning {
Direction::Up => Size::new( Position::Relative(Direction::Up) => Size::new(
relative_to_unsigned.size.width, relative_to_unsigned.size.width,
relative_to_unsigned.origin.y, relative_to_unsigned.origin.y,
), ),
Direction::Down => Size::new( Position::Relative(Direction::Down) => Size::new(
relative_to_unsigned.size.width, relative_to_unsigned.size.width,
available_space.height available_space.height
- relative_to_unsigned.origin.y - relative_to_unsigned.origin.y
- relative_to_unsigned.size.height, - relative_to_unsigned.size.height,
), ),
Direction::Left => Size::new( Position::Relative(Direction::Left) => Size::new(
relative_to_unsigned.origin.x, relative_to_unsigned.origin.x,
relative_to_unsigned.size.height, relative_to_unsigned.size.height,
), ),
Direction::Right => Size::new( Position::Relative(Direction::Right) => Size::new(
available_space.width.saturating_sub( available_space.width.saturating_sub(
relative_to_unsigned relative_to_unsigned
.origin .origin
@ -423,6 +423,7 @@ impl OverlayState {
), ),
relative_to_unsigned.size.height, relative_to_unsigned.size.height,
), ),
Position::At(_) => available_space,
}; };
let size = context let size = context
@ -430,26 +431,39 @@ impl OverlayState {
.layout(constraints.map(ConstraintLimit::SizeToFit)) .layout(constraints.map(ConstraintLimit::SizeToFit))
.into_signed(); .into_signed();
let mut layout_direction = direction; let mut layout_direction = positioning;
let mut layout; let mut layout;
loop { loop {
let origin = match layout_direction { let (origin, intersection_matters) = match layout_direction {
Direction::Up => Point::new( Position::Relative(Direction::Up) => (
relative_to.origin.x + relative_to.size.width / 2 - size.width / 2, Point::new(
relative_to.origin.y - size.height, relative_to.origin.x + relative_to.size.width / 2 - size.width / 2,
relative_to.origin.y - size.height,
),
true,
), ),
Direction::Down => Point::new( Position::Relative(Direction::Down) => (
relative_to.origin.x + relative_to.size.width / 2 - size.width / 2, Point::new(
relative_to.origin.y + relative_to.size.height, relative_to.origin.x + relative_to.size.width / 2 - size.width / 2,
relative_to.origin.y + relative_to.size.height,
),
true,
), ),
Direction::Left => Point::new( Position::Relative(Direction::Left) => (
relative_to.origin.x - size.width, Point::new(
relative_to.origin.y + relative_to.size.height / 2 - size.height / 2, relative_to.origin.x - size.width,
relative_to.origin.y + relative_to.size.height / 2 - size.height / 2,
),
true,
), ),
Direction::Right => Point::new( Position::Relative(Direction::Right) => (
relative_to.origin.x + relative_to.size.width, Point::new(
relative_to.origin.y + relative_to.size.height / 2 - size.height / 2, relative_to.origin.x + relative_to.size.width,
relative_to.origin.y + relative_to.size.height / 2 - size.height / 2,
),
true,
), ),
Position::At(pt) => (pt, false),
}; };
layout = Rect::new(origin.max(Point::ZERO), size); layout = Rect::new(origin.max(Point::ZERO), size);
@ -462,16 +476,27 @@ impl OverlayState {
layout.origin.y -= bottom_right.y - available_space.height.into_signed(); layout.origin.y -= bottom_right.y - available_space.height.into_signed();
} }
if layout.intersects(&relative_to) || self.layout_intersects(index, &layout, context) { if intersection_matters
layout_direction = layout_direction.next_clockwise(); && (layout.intersects(&relative_to)
if layout_direction == direction { || self.layout_intersects(index, &layout, context))
// No layout worked optimally. {
if let Some(next_direction) = layout_direction.next_clockwise() {
if layout_direction == positioning {
// No layout worked optimally.
break;
}
layout_direction = next_direction;
} else {
break; break;
} }
} else { } else {
break; break;
} }
} }
// TODO check to ensure the widget is fully on-window, otherwise attempt
// to shift it to become visible.
Some(layout) Some(layout)
} }
@ -516,7 +541,7 @@ impl OverlayState {
if let Some(relative_to) = self.overlays[index].relative_to { if let Some(relative_to) = self.overlays[index].relative_to {
self.layout_overlay_relative(index, widget, available_space, context, relative_to) self.layout_overlay_relative(index, widget, available_space, context, relative_to)
} else { } else {
let direction = self.overlays[index].direction; let direction = self.overlays[index].positioning;
let size = context let size = context
.for_other(widget) .for_other(widget)
.layout(available_space.map(ConstraintLimit::SizeToFit)) .layout(available_space.map(ConstraintLimit::SizeToFit))
@ -525,22 +550,23 @@ impl OverlayState {
let available_space = available_space.into_signed(); let available_space = available_space.into_signed();
let origin = match direction { let origin = match direction {
Direction::Up => Point::new( Position::Relative(Direction::Up) => Point::new(
available_space.width / 2, available_space.width / 2,
(available_space.height - size.height) / 2, (available_space.height - size.height) / 2,
), ),
Direction::Down => Point::new( Position::Relative(Direction::Down) => Point::new(
available_space.width / 2, available_space.width / 2,
available_space.height / 2 + size.height / 2, available_space.height / 2 + size.height / 2,
), ),
Direction::Right => Point::new( Position::Relative(Direction::Right) => Point::new(
available_space.width / 2 + size.width / 2, available_space.width / 2 + size.width / 2,
available_space.height / 2, available_space.height / 2,
), ),
Direction::Left => Point::new( Position::Relative(Direction::Left) => Point::new(
(available_space.width - size.width) / 2, (available_space.width - size.width) / 2,
available_space.height / 2, available_space.height / 2,
), ),
Position::At(pt) => pt,
}; };
Some(Rect::new(origin, size)) Some(Rect::new(origin, size))
@ -592,7 +618,14 @@ impl OverlayBuilder<'_> {
#[must_use] #[must_use]
pub fn near(mut self, id: WidgetId, direction: Direction) -> Self { pub fn near(mut self, id: WidgetId, direction: Direction) -> Self {
self.layout.relative_to = Some(id); self.layout.relative_to = Some(id);
self.layout.direction = direction; self.layout.positioning = Position::Relative(direction);
self
}
/// Shows this overlay at a specified window `location`.
#[must_use]
pub fn at(mut self, location: Point<Px>) -> Self {
self.layout.positioning = Position::At(location);
self self
} }
@ -632,7 +665,7 @@ struct OverlayLayout {
widget: WidgetRef, widget: WidgetRef,
opacity: Dynamic<ZeroToOne>, opacity: Dynamic<ZeroToOne>,
relative_to: Option<WidgetId>, relative_to: Option<WidgetId>,
direction: Direction, positioning: Position<Px>,
requires_hover: bool, requires_hover: bool,
layout: Option<Rect<Px>>, layout: Option<Rect<Px>>,
on_dismiss: Option<Arc<Mutex<Callback>>>, on_dismiss: Option<Arc<Mutex<Callback>>>,
@ -654,7 +687,7 @@ impl PartialEq for OverlayLayout {
self.widget == other.widget self.widget == other.widget
&& self.opacity == other.opacity && self.opacity == other.opacity
&& self.relative_to == other.relative_to && self.relative_to == other.relative_to
&& self.direction == other.direction && self.positioning == other.positioning
&& self.requires_hover == other.requires_hover && self.requires_hover == other.requires_hover
&& self.layout == other.layout && self.layout == other.layout
&& match (&self.on_dismiss, &other.on_dismiss) { && match (&self.on_dismiss, &other.on_dismiss) {
@ -665,6 +698,26 @@ impl PartialEq for OverlayLayout {
} }
} }
/// An overlay position.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Position<T> {
/// Relative to the parent in a given direction.
Relative(Direction),
/// At a window coordinate.
At(Point<T>),
}
impl<T> Position<T> {
/// Returns the next direction when rotating clockwise.
#[must_use]
pub fn next_clockwise(&self) -> Option<Self> {
match self {
Self::Relative(direction) => Some(Self::Relative(direction.next_clockwise())),
Self::At(_) => None,
}
}
}
/// A relative direction. /// A relative direction.
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Direction { pub enum Direction {
@ -674,7 +727,7 @@ pub enum Direction {
Right, Right,
/// Positive along the Y axis. /// Positive along the Y axis.
Down, Down,
/// Legative along the X axis. /// Negative along the X axis.
Left, Left,
} }

View file

@ -62,7 +62,7 @@ where
.and(self.label) .and(self.label)
.into_columns() .into_columns()
.into_button() .into_button()
.on_click(move |()| { .on_click(move |_| {
self.state.set(self.value.clone()); self.state.set(self.value.clone());
}) })
.kind(self.kind) .kind(self.kind)

View file

@ -73,7 +73,7 @@ where
}); });
self.label self.label
.into_button() .into_button()
.on_click(move |()| { .on_click(move |_| {
self.state.set(self.value.clone()); self.state.set(self.value.clone());
}) })
.kind(kind) .kind(kind)