Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: Event Container #12

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions text/0012-mouse-listener.md
Original file line number Diff line number Diff line change
@@ -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<Message>,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bikeshedding: I wonder if the name on_press would be confusing as it seems to have a different meaning to a Button's on_press, since the Button's on_press seems to be about the mouse_or_touch_down + mouse_or_touch_up sequence whereas MouseListener's is just about mouse_down. Do you think this consistency is important enough that we should rename either MouseListener's on_press or the Button's on_press?


/// Sets the message to emit on a left mouse button release.
on_release: Option<Message>,

/// Sets the message to emit on a right mouse button press.
on_right_press: Option<Message>,

/// Sets the message to emit on a right mouse button release.
on_right_release: Option<Message>,

/// Sets the message to emit on a middle mouse button press.
on_middle_press: Option<Message>,

/// Sets the message to emit on a middle mouse button release.
on_middle_release: Option<Message>,

/// Sets the message to emit when the mouse enters the widget.
on_mouse_enter: Option<Message>,

/// Sets the messsage to emit when the mouse exits the widget.
on_mouse_exit: Option<Message>,
}
```

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.