mirror of https://github.com/linebender/xilem
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:
parent
f73bf9d577
commit
d6af6a6ef7
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
/// );
|
||||
/// }
|
||||
///
|
||||
|
|
|
@ -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))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}),
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue