Differentiate pointer buttons in `Button` Widget and View (#397)

In most cases, you want a button which only actuates when the primary
mouse button is pressed, so the easy case is still that. This is a short
term hack, because e.g. the active state is still based on any button
being pressed, not just those we are interested in.

That is, we probably need to represent a set of buttons we are
interested in. However, this change minimally unblocks additional work
with Xilem. In particular, see [#xilem > Minesweeper converted from Iced
to
Xilem](https://xi.zulipchat.com/#narrow/stream/354396-xilem/topic/Minesweeper.20converted.20from.20Iced.20to.20Xilem).
This commit is contained in:
Daniel McNab 2024-06-14 17:40:26 +01:00 committed by GitHub
parent f73bf9d577
commit d6af6a6ef7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 57 additions and 25 deletions

View File

@ -25,7 +25,10 @@ clippy.semicolon_if_nothing_returned = "warn"
# Remove assigning_clones once it's allowed by default in stable Rust
# https://github.com/rust-lang/rust-clippy/pull/12779
clippy.assigning_clones = "allow"
rust.unexpected_cfgs = { level = "warn", check-cfg = ['cfg(FALSE)', 'cfg(tarpaulin_include)'] }
rust.unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(FALSE)',
'cfg(tarpaulin_include)',
] }
[workspace.dependencies]
xilem_web_core = { version = "0.1.0", path = "xilem_web/xilem_web_core" }
@ -37,7 +40,7 @@ kurbo = "0.11.0"
parley = "0.1.0"
peniko = "0.1.1"
winit = "0.30.0"
tracing = {version = "0.1.40", default-features = false}
tracing = { version = "0.1.40", default-features = false }
smallvec = "1.13.2"
dpi = "0.1.1"
fnv = "1.0.7"

View File

@ -45,7 +45,7 @@ struct Driver {
impl AppDriver for Driver {
fn on_action(&mut self, ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) {
match action {
Action::ButtonPressed => {
Action::ButtonPressed(_) => {
let mut root: WidgetMut<RootWidget<Portal<Flex>>> = ctx.get_root();
let mut root = root.get_element();
let mut flex = root.child_mut();

View File

@ -20,7 +20,7 @@ struct Driver;
impl AppDriver for Driver {
fn on_action(&mut self, _ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) {
match action {
Action::ButtonPressed => {
Action::ButtonPressed(_) => {
println!("Hello");
}
action => {

View File

@ -22,7 +22,7 @@ struct Driver {
impl AppDriver for Driver {
fn on_action(&mut self, ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) {
match action {
Action::ButtonPressed => {
Action::ButtonPressed(_) => {
let mut root: WidgetMut<RootWidget<Portal<Flex>>> = ctx.get_root();
let mut root = root.get_element();
let mut flex = root.child_mut();

View File

@ -4,6 +4,8 @@
use std::any::Any;
use std::sync::Arc;
use crate::event::PointerButton;
// TODO - Refactor - See issue #1
// TODO - TextCursor changed, ImeChanged, EnterKey, MouseEnter
@ -13,7 +15,7 @@ use std::sync::Arc;
///
/// Note: Actions are still a WIP feature.
pub enum Action {
ButtonPressed,
ButtonPressed(PointerButton),
TextChanged(String),
TextEntered(String),
CheckboxChecked(bool),
@ -24,7 +26,7 @@ pub enum Action {
impl PartialEq for Action {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::ButtonPressed, Self::ButtonPressed) => true,
(Self::ButtonPressed(l_button), Self::ButtonPressed(r_button)) => l_button == r_button,
(Self::TextChanged(l0), Self::TextChanged(r0)) => l0 == r0,
(Self::TextEntered(l0), Self::TextEntered(r0)) => l0 == r0,
(Self::CheckboxChecked(l0), Self::CheckboxChecked(r0)) => l0 == r0,
@ -39,7 +41,7 @@ impl PartialEq for Action {
impl std::fmt::Debug for Action {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ButtonPressed => write!(f, "ButtonPressed"),
Self::ButtonPressed(button) => f.debug_tuple("ButtonPressed").field(button).finish(),
Self::TextChanged(text) => f.debug_tuple("TextChanged").field(text).finish(),
Self::TextEntered(text) => f.debug_tuple("TextEntered").field(text).finish(),
Self::CheckboxChecked(b) => f.debug_tuple("CheckboxChecked").field(b).finish(),

View File

@ -29,7 +29,7 @@
//! impl AppDriver for Driver {
//! fn on_action(&mut self, ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) {
//! match action {
//! Action::ButtonPressed => {
//! Action::ButtonPressed(_) => {
//! let mut root: WidgetMut<RootWidget<Portal<Flex>>> = ctx.get_root();
//! let mut root = root.get_element();
//! let mut flex = root.child_mut();
@ -122,8 +122,8 @@ pub use action::Action;
pub use box_constraints::BoxConstraints;
pub use contexts::{AccessCtx, EventCtx, LayoutCtx, LifeCycleCtx, PaintCtx, WidgetCtx};
pub use event::{
AccessEvent, InternalLifeCycle, LifeCycle, PointerEvent, PointerState, StatusChange, TextEvent,
WindowEvent, WindowTheme,
AccessEvent, InternalLifeCycle, LifeCycle, PointerButton, PointerEvent, PointerState,
StatusChange, TextEvent, WindowEvent, WindowTheme,
};
pub use kurbo::{Affine, Insets, Point, Rect, Size, Vec2};
pub use parley::layout::Alignment as TextAlignment;

View File

@ -84,6 +84,7 @@ pub const HARNESS_DEFAULT_BACKGROUND_COLOR: Color = Color::rgb8(0x29, 0x29, 0x29
/// ```
/// use insta::assert_debug_snapshot;
///
/// use masonry::PointerButton;
/// use masonry::widget::Button;
/// use masonry::Action;
/// use masonry::assert_render_snapshot;
@ -111,7 +112,7 @@ pub const HARNESS_DEFAULT_BACKGROUND_COLOR: Color = Color::rgb8(0x29, 0x29, 0x29
/// harness.mouse_click_on(button_id);
/// assert_eq!(
/// harness.pop_action(),
/// Some((Action::ButtonPressed, button_id))
/// Some((Action::ButtonPressed(PointerButton::Primary), button_id))
/// );
/// }
///

View File

@ -9,6 +9,7 @@ use tracing::{trace, trace_span, Span};
use vello::Scene;
use crate::action::Action;
use crate::event::PointerButton;
use crate::paint_scene_helpers::{fill_lin_gradient, stroke, UnitPoint};
use crate::text2::TextStorage;
use crate::widget::{Label, WidgetMut, WidgetPod, WidgetRef};
@ -85,9 +86,9 @@ impl Widget for Button {
trace!("Button {:?} pressed", ctx.widget_id());
}
}
PointerEvent::PointerUp(_, _) => {
PointerEvent::PointerUp(button, _) => {
if ctx.is_active() && ctx.is_hot() && !ctx.is_disabled() {
ctx.submit_action(Action::ButtonPressed);
ctx.submit_action(Action::ButtonPressed(*button));
trace!("Button {:?} released", ctx.widget_id());
}
ctx.request_paint();
@ -111,7 +112,7 @@ impl Widget for Button {
if event.target == ctx.widget_id() {
match event.action {
accesskit::Action::Default => {
ctx.submit_action(Action::ButtonPressed);
ctx.submit_action(Action::ButtonPressed(PointerButton::Primary));
ctx.request_paint();
}
_ => {}
@ -242,7 +243,7 @@ mod tests {
harness.mouse_click_on(button_id);
assert_eq!(
harness.pop_action(),
Some((Action::ButtonPressed, button_id))
Some((Action::ButtonPressed(PointerButton::Primary), button_id))
);
}

View File

@ -5,7 +5,7 @@
#![windows_subsystem = "windows"]
use xilem::{
view::{button, checkbox, flex, label, prose, textbox},
view::{button, button_any_pointer, checkbox, flex, label, prose, textbox},
AnyWidgetView, Axis, Color, EventLoop, EventLoopBuilder, TextAlignment, WidgetView, Xilem,
};
const LOREM: &str = r"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi cursus mi sed euismod euismod. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam placerat efficitur tellus at semper. Morbi ac risus magna. Donec ut cursus ex. Etiam quis posuere tellus. Mauris posuere dui et turpis mollis, vitae luctus tellus consectetur. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eu facilisis nisl.
@ -49,7 +49,13 @@ fn app_logic(data: &mut AppData) -> impl WidgetView<AppData> {
))
.direction(Axis::Horizontal),
prose(LOREM).alignment(TextAlignment::Middle),
button(button_label, |data: &mut AppData| data.count += 1),
button_any_pointer(button_label, |data: &mut AppData, button| match button {
masonry::PointerButton::None => tracing::warn!("Got unexpected None from button"),
masonry::PointerButton::Primary => data.count += 1,
masonry::PointerButton::Secondary => data.count -= 1,
masonry::PointerButton::Auxiliary => data.count *= 2,
_ => (),
}),
checkbox("Check me", data.active, |data: &mut AppData, checked| {
data.active = checked;
}),

View File

@ -5,15 +5,34 @@ use crate::{core::View, Pod};
use masonry::{widget, ArcStr};
use xilem_core::Mut;
pub use masonry::PointerButton;
use crate::{MessageResult, ViewCtx, ViewId};
pub fn button<F, State, Action>(label: impl Into<ArcStr>, callback: F) -> Button<F>
where
F: Fn(&mut State) -> Action + Send + 'static,
/// A button which calls `callback` when the primary mouse button (normally left) is pressed.
pub fn button<State, Action>(
label: impl Into<ArcStr>,
callback: impl Fn(&mut State) -> Action + Send + 'static,
) -> Button<impl for<'a> Fn(&'a mut State, PointerButton) -> MessageResult<Action> + Send + 'static>
{
Button {
label: label.into(),
callback,
callback: move |state: &mut State, button| match button {
PointerButton::Primary => MessageResult::Action(callback(state)),
_ => MessageResult::Nop,
},
}
}
/// A button which calls `callback` when pressed.
pub fn button_any_pointer<State, Action>(
label: impl Into<ArcStr>,
callback: impl Fn(&mut State, PointerButton) -> Action + Send + 'static,
) -> Button<impl for<'a> Fn(&'a mut State, PointerButton) -> MessageResult<Action> + Send + 'static>
{
Button {
label: label.into(),
callback: move |state: &mut State, button| MessageResult::Action(callback(state, button)),
}
}
@ -24,7 +43,7 @@ pub struct Button<F> {
impl<F, State, Action> View<State, Action, ViewCtx> for Button<F>
where
F: Fn(&mut State) -> Action + Send + Sync + 'static,
F: Fn(&mut State, PointerButton) -> MessageResult<Action> + Send + Sync + 'static,
{
type Element = Pod<widget::Button>;
type ViewState = ();
@ -69,8 +88,8 @@ where
);
match message.downcast::<masonry::Action>() {
Ok(action) => {
if let masonry::Action::ButtonPressed = *action {
MessageResult::Action((self.callback)(app_state))
if let masonry::Action::ButtonPressed(button) = *action {
(self.callback)(app_state, button)
} else {
tracing::error!("Wrong action type in Button::message: {action:?}");
MessageResult::Stale(action)