From d0544e73f5182a94db6b1bc4ea6efae4e1f11be1 Mon Sep 17 00:00:00 2001 From: Dmitry Stepanov Date: Fri, 4 Sep 2026 20:00:08 +0300 Subject: [PATCH] ability to specify click button for button widget - breaking change - previously the button widget accepted any mouse down event as "click" --- fyrox-ui/src/button.rs | 80 +++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 28 deletions(-) diff --git a/fyrox-ui/src/button.rs b/fyrox-ui/src/button.rs index f0883566d..6bc5d622b 100644 --- a/fyrox-ui/src/button.rs +++ b/fyrox-ui/src/button.rs @@ -22,7 +22,7 @@ #![warn(missing_docs)] -use crate::message::MessageData; +use crate::message::{MessageData, MouseButton}; use crate::style::StyledProperty; use crate::{ border::BorderBuilder, @@ -91,8 +91,11 @@ impl MessageData for ButtonMessage {} /// } /// ``` #[derive(Default, Clone, Visit, PartialEq, Reflect, Debug)] -#[reflect(type_uuid = "2abcf12b-2f19-46da-b900-ae8890f7c9c6")] -#[reflect(derived_type = "UiNode")] +#[reflect( + derived_type = "UiNode", + type_uuid = "2abcf12b-2f19-46da-b900-ae8890f7c9c6" +)] +#[visit(optional)] pub struct Button { /// Base widget of the button. pub widget: Widget, @@ -101,17 +104,17 @@ pub struct Button { /// Current content of the button. It is attached to the content holder. pub content: InheritableVariable>, /// Click repetition interval (in seconds) of the button. - #[visit(optional)] #[reflect(min_value = 0.0)] pub repeat_interval: InheritableVariable, /// Current clicks repetition timer. - #[visit(optional)] #[reflect(hidden)] pub repeat_timer: RefCell>, /// A flag, that defines whether the button should repeat click message when being /// hold or not. Default is `false` (disabled). - #[visit(optional)] pub repeat_clicks_on_hold: InheritableVariable, + /// Specifies the mouse button that will be used to accept clicks on the button. Any other button + /// event will be ignored. The default value is left mouse button. + pub click_button: InheritableVariable, } impl Button { @@ -126,6 +129,29 @@ impl Button { .with(Self::CORNER_RADIUS, 4.0f32) .with(Self::BORDER_THICKNESS, Thickness::uniform(1.0)) } + + fn on_mouse_down(&mut self, message: &mut UiMessage, ui: &mut UserInterface) { + // The only way to avoid a `MouseLeave` message is by capturing the currently picked node. + // Capturing any other node will change the picked node and be considered leaving, + // which would affect the decorator. + ui.capture_mouse(message.destination()); + message.set_handled(true); + if *self.repeat_clicks_on_hold { + self.repeat_timer.replace(Some(*self.repeat_interval)); + } + } + + fn on_mouse_up(&mut self, message: &mut UiMessage, ui: &mut UserInterface) { + // Do the click only if the mouse is still within the button and the event hasn't been handled. + // The event might be handled if there is a child button within this button, as with the + // close button on a tab. + if self.screen_bounds().contains(ui.cursor_position()) && !message.handled() { + ui.post(self.handle(), ButtonMessage::Click); + } + ui.release_mouse_capture(); + message.set_handled(true); + self.repeat_timer.replace(None); + } } impl ConstructorProvider for Button { @@ -168,29 +194,17 @@ impl Control for Button { || self.has_descendant(message.destination(), ui) { match msg { - WidgetMessage::MouseDown { .. } - | WidgetMessage::TouchStarted { .. } - | WidgetMessage::TouchMoved { .. } => { - // The only way to avoid a `MouseLeave` message is by capturing the currently picked node. - // Capturing any other node will change the picked node and be considered leaving, - // which would affect the decorator. - ui.capture_mouse(message.destination()); - message.set_handled(true); - if *self.repeat_clicks_on_hold { - self.repeat_timer.replace(Some(*self.repeat_interval)); - } + WidgetMessage::MouseDown { button, .. } if *button == *self.click_button => { + self.on_mouse_down(message, ui); } - WidgetMessage::MouseUp { .. } | WidgetMessage::TouchEnded { .. } => { - // Do the click only if the mouse is still within the button and the event hasn't been handled. - // The event might be handled if there is a child button within this button, as with the - // close button on a tab. - if self.screen_bounds().contains(ui.cursor_position()) && !message.handled() - { - ui.post(self.handle(), ButtonMessage::Click); - } - ui.release_mouse_capture(); - message.set_handled(true); - self.repeat_timer.replace(None); + WidgetMessage::TouchStarted { .. } | WidgetMessage::TouchMoved { .. } => { + self.on_mouse_down(message, ui); + } + WidgetMessage::MouseUp { button, .. } if *button == *self.click_button => { + self.on_mouse_up(message, ui); + } + WidgetMessage::TouchEnded { .. } => { + self.on_mouse_up(message, ui); } WidgetMessage::KeyDown(key_code) if !message.handled() @@ -314,6 +328,7 @@ pub struct ButtonBuilder { back: Option>, repeat_interval: f32, repeat_clicks_on_hold: bool, + click_button: MouseButton, } fn make_decorator_builder(ctx: &mut BuildContext) -> DecoratorBuilder { @@ -334,6 +349,7 @@ impl ButtonBuilder { back: None, repeat_interval: 0.1, repeat_clicks_on_hold: false, + click_button: MouseButton::Left, } } @@ -366,6 +382,13 @@ impl ButtonBuilder { self } + /// Specifies the mouse button that will be used to accept clicks on the button. Any other button + /// event will be ignored. The default value is left mouse button. + pub fn with_click_button(mut self, mouse_button: MouseButton) -> Self { + self.click_button = mouse_button; + self + } + /// Specifies the widget that will be used as a content holder of the button. By default, it is an /// instance of [`crate::decorator::Decorator`] widget. Usually, this widget should respond to mouse /// events to highlight button state (hovered, pressed, etc.) @@ -437,6 +460,7 @@ impl ButtonBuilder { repeat_interval: self.repeat_interval.into(), repeat_clicks_on_hold: self.repeat_clicks_on_hold.into(), repeat_timer: Default::default(), + click_button: self.click_button.into(), } }