From 3650f7c28eb261a52638ab427d516de54e626732 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 2 Nov 2022 23:28:38 +0100 Subject: [PATCH 1/2] proposal: Event Container --- text/0012-event-container.md | 160 +++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 text/0012-event-container.md diff --git a/text/0012-event-container.md b/text/0012-event-container.md new file mode 100644 index 0000000..0e486bc --- /dev/null +++ b/text/0012-event-container.md @@ -0,0 +1,160 @@ +# Event Container + +## Summary + +A programmable container that intercepts a variety of input events. + +## Motivation + +There are many widgets within a GUI library that need to handle a wider variety of input events than is capable from a button. From a left button press and release, to a right button press and release, middle button press and release, and mouse enter and exit. + +## Guide-level explanation + +### File icon example + +One such use case for the event container is a file icon in a file manager. The left button press initiates a selection and drag, a left button release initiates a drop if the icon was dragged, a right button release opens a context menu, a middle click opens the file, a mouse enter causes the file icon to glow, and a mouse exit stops glowing the icon. + +```rs +for (entity, metadata) in self.files.iter() { + let file = file_icon(metadata) + .on_press(Message::Drag(entity)) + .on_release(Message::Drop) + .on_right_release(Message::Context(entity)) + .on_middle_release(Message::OpenFile(entity)) + .on_mouse_enter(Message::IconEnter(entity)) + .on_mouse_exit(Message::IconExit(entity)); + + icon_widgets.push(file.into()); +} +``` + +### Tabs + +A tabbed interface may also want to provide widgets that are draggable with context on a right click: + +```rs +for (entity, metadata) in self.tabs.iter() { + let tab = tab(metadata) + .on_press(Message::TabDrag(entity)) + .on_release(Message::TabDrop) + .on_right_release(Message::TabContext(entity)) + .on_middle_release(Message::TabClose(entity)) + .on_mouse_enter(Message::TabHighlight(entity)) + .on_mouse_exit(Message::TabUnhighlight(entity)); + + tabs.push(tab); +} +``` + +### Headerbar example + +Another use case for this widget is a headerbar, which is a container placed at the top of the window to serve as a replacement for a window title bar when server-side decorations are disabled. If any part of the container is clicked that does not intercept that click event, then the container itself will receive the click and make it possible to initiate a window drag. + +```rs +let headerbar = event_container(vec![left, center, right]) + .on_press(Message::WindowDrag) + .on_release(Message::WindowMaximize) + .on_right_release(Message::WindowContext); +``` + +## Implementation strategy + +Implementation is very similar to the container widget, but with a handful of possible events that can be emitted: + +```rs +struct EventMessages { + on_press: Option, + on_release: Option, + on_right_press: Option, + on_right_release: Option, +} +``` + +With an update method for publishing these messages on certain input events: + +```rs +fn update( + event: Event, + layout: Layout<'_>, + cursor_position: Point, + shell: &mut Shell<'_, Message>, + messages: &EventMessages, +) -> event::Status { + let contains_bound = || layout.bounds().contains(cursor_position); + + if let Some(message) = messages.on_press.clone() { + if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) = event + { + return if contains_bound() { + shell.publish(message); + event::Status::Captured + } else { + event::Status::Ignored + }; + } + } + + if let Some(message) = messages.on_release.clone() { + if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) = event + { + return if contains_bound() { + shell.publish(message); + event::Status::Captured + } else { + event::Status::Ignored + }; + } + } + + if let Some(message) = messages.on_right_press.clone() { + if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) = + event + { + return if contains_bound() { + shell.publish(message); + event::Status::Captured + } else { + event::Status::Ignored + }; + } + } + + if let Some(message) = messages.on_right_release.clone() { + if let Event::Mouse(mouse::Event::ButtonReleased( + mouse::Button::Right, + )) = event + { + return if contains_bound() { + shell.publish(message); + event::Status::Captured + } else { + event::Status::Ignored + }; + } + } + + event::Status::Ignored +} +``` + +## Drawbacks + +There's some duplicated code between the container widget and event_container widget. + +## Rationale and alternatives + +There doesn't seem to be any alternative approach besides enforcing application authors to bring their own event containers when needing to create a more sophisticated evented widget. + +## Prior Art + +Similar to `gtk::EventBox` from GTK, which is essentially a `gtk::Box` but with events that can be intercepted and programmed with callbacks. The implementation here is much more ergonomic in comparison. + +## Unresolved questions + +Perhaps there's a way to handle events in a more flexible way that isn't soo cumbersome, since directly matching input events is rather tedious. + +## Future Possibilities + +Enables a large number of more complex and featured widgets. \ No newline at end of file From 8f99f8b2069d0f27ed01826305c5c9cfaf7edf1c Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 12 Dec 2022 17:40:40 +0100 Subject: [PATCH 2/2] chore: Update text to reflect requested changes --- text/0012-event-container.md | 160 ----------------------------------- text/0012-mouse-listener.md | 114 +++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 160 deletions(-) delete mode 100644 text/0012-event-container.md create mode 100644 text/0012-mouse-listener.md diff --git a/text/0012-event-container.md b/text/0012-event-container.md deleted file mode 100644 index 0e486bc..0000000 --- a/text/0012-event-container.md +++ /dev/null @@ -1,160 +0,0 @@ -# Event Container - -## Summary - -A programmable container that intercepts a variety of input events. - -## Motivation - -There are many widgets within a GUI library that need to handle a wider variety of input events than is capable from a button. From a left button press and release, to a right button press and release, middle button press and release, and mouse enter and exit. - -## Guide-level explanation - -### File icon example - -One such use case for the event container is a file icon in a file manager. The left button press initiates a selection and drag, a left button release initiates a drop if the icon was dragged, a right button release opens a context menu, a middle click opens the file, a mouse enter causes the file icon to glow, and a mouse exit stops glowing the icon. - -```rs -for (entity, metadata) in self.files.iter() { - let file = file_icon(metadata) - .on_press(Message::Drag(entity)) - .on_release(Message::Drop) - .on_right_release(Message::Context(entity)) - .on_middle_release(Message::OpenFile(entity)) - .on_mouse_enter(Message::IconEnter(entity)) - .on_mouse_exit(Message::IconExit(entity)); - - icon_widgets.push(file.into()); -} -``` - -### Tabs - -A tabbed interface may also want to provide widgets that are draggable with context on a right click: - -```rs -for (entity, metadata) in self.tabs.iter() { - let tab = tab(metadata) - .on_press(Message::TabDrag(entity)) - .on_release(Message::TabDrop) - .on_right_release(Message::TabContext(entity)) - .on_middle_release(Message::TabClose(entity)) - .on_mouse_enter(Message::TabHighlight(entity)) - .on_mouse_exit(Message::TabUnhighlight(entity)); - - tabs.push(tab); -} -``` - -### Headerbar example - -Another use case for this widget is a headerbar, which is a container placed at the top of the window to serve as a replacement for a window title bar when server-side decorations are disabled. If any part of the container is clicked that does not intercept that click event, then the container itself will receive the click and make it possible to initiate a window drag. - -```rs -let headerbar = event_container(vec![left, center, right]) - .on_press(Message::WindowDrag) - .on_release(Message::WindowMaximize) - .on_right_release(Message::WindowContext); -``` - -## Implementation strategy - -Implementation is very similar to the container widget, but with a handful of possible events that can be emitted: - -```rs -struct EventMessages { - on_press: Option, - on_release: Option, - on_right_press: Option, - on_right_release: Option, -} -``` - -With an update method for publishing these messages on certain input events: - -```rs -fn update( - event: Event, - layout: Layout<'_>, - cursor_position: Point, - shell: &mut Shell<'_, Message>, - messages: &EventMessages, -) -> event::Status { - let contains_bound = || layout.bounds().contains(cursor_position); - - if let Some(message) = messages.on_press.clone() { - if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) = event - { - return if contains_bound() { - shell.publish(message); - event::Status::Captured - } else { - event::Status::Ignored - }; - } - } - - if let Some(message) = messages.on_release.clone() { - if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) = event - { - return if contains_bound() { - shell.publish(message); - event::Status::Captured - } else { - event::Status::Ignored - }; - } - } - - if let Some(message) = messages.on_right_press.clone() { - if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) = - event - { - return if contains_bound() { - shell.publish(message); - event::Status::Captured - } else { - event::Status::Ignored - }; - } - } - - if let Some(message) = messages.on_right_release.clone() { - if let Event::Mouse(mouse::Event::ButtonReleased( - mouse::Button::Right, - )) = event - { - return if contains_bound() { - shell.publish(message); - event::Status::Captured - } else { - event::Status::Ignored - }; - } - } - - event::Status::Ignored -} -``` - -## Drawbacks - -There's some duplicated code between the container widget and event_container widget. - -## Rationale and alternatives - -There doesn't seem to be any alternative approach besides enforcing application authors to bring their own event containers when needing to create a more sophisticated evented widget. - -## Prior Art - -Similar to `gtk::EventBox` from GTK, which is essentially a `gtk::Box` but with events that can be intercepted and programmed with callbacks. The implementation here is much more ergonomic in comparison. - -## Unresolved questions - -Perhaps there's a way to handle events in a more flexible way that isn't soo cumbersome, since directly matching input events is rather tedious. - -## Future Possibilities - -Enables a large number of more complex and featured widgets. \ No newline at end of file diff --git a/text/0012-mouse-listener.md b/text/0012-mouse-listener.md new file mode 100644 index 0000000..5abba09 --- /dev/null +++ b/text/0012-mouse-listener.md @@ -0,0 +1,114 @@ +# Mouse Listener + +## Summary + +A programmable container that intercepts a variety of mouse input events. + +## Motivation + +There are many widgets within a GUI library that need to handle a wider variety of input events than is capable from a button. From a left button press and release, to a right button press and release, middle button press and release, and mouse enter and exit. + +## Guide-level explanation + +### File icon example + +One such use case for the event container is a file icon in a file manager. The left button press initiates a selection and drag, a left button release initiates a drop if the icon was dragged, a right button release opens a context menu, a middle click opens the file, a mouse enter causes the file icon to glow, and a mouse exit stops glowing the icon. + +```rs +for (entity, metadata) in self.files.iter() { + let file = mouse_listener(file_icon(metadata)) + .on_press(Message::Drag(entity)) + .on_release(Message::Drop) + .on_right_release(Message::Context(entity)) + .on_middle_release(Message::OpenFile(entity)) + .on_mouse_enter(Message::IconEnter(entity)) + .on_mouse_exit(Message::IconExit(entity)); + + icon_widgets.push(file.into()); +} +``` + +### Tabs + +A tabbed interface may also want to provide widgets that are draggable with context on a right click: + +```rs +for (entity, metadata) in self.tabs.iter() { + let tab = mouse_listener(tab(metadata)) + .on_press(Message::TabDrag(entity)) + .on_release(Message::TabDrop) + .on_right_release(Message::TabContext(entity)) + .on_middle_release(Message::TabClose(entity)) + .on_mouse_enter(Message::TabHighlight(entity)) + .on_mouse_exit(Message::TabUnhighlight(entity)); + + tabs.push(tab); +} +``` + +### Headerbar example + +Another use case for this widget is a headerbar, which is a container placed at the top of the window to serve as a replacement for a window title bar when server-side decorations are disabled. If any part of the container is clicked that does not intercept that click event, then the container itself will receive the click and make it possible to initiate a window drag. + +```rs +let headerbar = mouse_listener(row(vec![left, center, right])) + .on_press(Message::WindowDrag) + .on_release(Message::WindowMaximize) + .on_right_release(Message::WindowContext); +``` + +## Implementation strategy + +The implementation is simple, and only requires thinly wrapping any existing element to intercept mouse events over it. + +```rs +pub struct MouseListener<'a, Message, Renderer> { + content: Element<'a, Message, Renderer>, + + /// Sets the message to emit on a left mouse button press. + on_press: Option, + + /// Sets the message to emit on a left mouse button release. + on_release: Option, + + /// Sets the message to emit on a right mouse button press. + on_right_press: Option, + + /// Sets the message to emit on a right mouse button release. + on_right_release: Option, + + /// Sets the message to emit on a middle mouse button press. + on_middle_press: Option, + + /// Sets the message to emit on a middle mouse button release. + on_middle_release: Option, + + /// Sets the message to emit when the mouse enters the widget. + on_mouse_enter: Option, + + /// Sets the messsage to emit when the mouse exits the widget. + on_mouse_exit: Option, +} +``` + +The update method will emit these messages when their criteria is met. + +## Drawbacks + +N/A + +## Rationale and alternatives + +The alternative is that it'd be required to manually implement the Widget trait whenever you want to have a widget intercept a variety of mouse events outside of the left button release event that the button widget currently provides. This will significantly improve ergonomics for creating more sophisticated widgets. + +## Prior Art + +Similar to `gtk::EventBox` from GTK, which is essentially a `gtk::Box` but with events that can be intercepted and programmed with callbacks. The implementation here is much more ergonomic in comparison. + +## Unresolved questions + +None + +## Future Possibilities + +Enables a large number of more complex and featured widgets. \ No newline at end of file