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

Conversation

mmstick
Copy link
Contributor

@mmstick mmstick commented Nov 2, 2022

Copy link
Member

@hecrj hecrj left a comment

Choose a reason for hiding this comment

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

Makes sense to me!

I would call it MouseListener instead of Event Container or EventMessages, but besides that I think this would be a nice addition to iced.

@hecrj hecrj added the enhancement New feature or request label Dec 6, 2022
@mmstick
Copy link
Contributor Author

mmstick commented Dec 8, 2022

Almost finished with implementing this. Working on an example to use with it.

@ErnWong
Copy link

ErnWong commented Dec 29, 2022

I just started using iced and this proposal looks very useful!

Maybe this is outside the scope of this RFC, but I was wondering: For simpler use-cases, would it be desirable to also do something similar to the Responsive widget in that we pass the pressed/hovered states directly as closure parameters?

let icon_button = mouse_area(|mouse_state| {
  let icon_style = if mouse_state.is_pressed {
    pressed_icon_style
  } else if mouse_state.is_hovered {
    hovered_icon_stye
  } else {
    normal_icon_style
  };
  button(svg(icon_handle).style(icon_style))
});

I've put some motivations, prior art and the drawbacks of this suggestion in the details below:

Motivation

I would like to draw the view differently depending on the mouse state of something. For example:

  • I'd like to have an "Icon Button" where the color of the svg icon inside it changes on hovering and pressing the button, similarly to how the button's text color changes. Currently the button background and text color can react to hover via the stylesheets, but I'm not sure how I would go about the svg icon color without creating a custom widget or adding a derived state to the application state.
  • When the user hovers over the text, I'd like to show a scroll animation for a long elided text much like a marquee text.
  • I'd like to show some additional buttons on an item when the use hovers over the item. E.g., hovering over an image shows a button to zoom in.

While this is doable with the current MouseListener widget proposal by tracking additional states ourselves, the source of truth comes from the mouse position tracked by the operating system and it feels like we shouldn't pollute our application state with information derived externally. (I guess this is subjective and I could be wrong - feel free to let me know 😄). A common minor "bug" I see with applications is how their application state diverges from the source of truth, e.g. the application thinks the mouse is still pressed after the user released the mouse outside the application window, or (in the case of a QML application I worked on) how the application thinks I'm still hovering an item if I move my mouse very quickly over the item and out of the window.

Prior art

  • CSS (and therefore React, Elm, JavaFX, etc.): we can declaratively specify what things look like using the :hover selector, and we don't (usually) need to keep track of the hover state ourselves in the application state.
/* (css pseudocode - might not be entirely accurate) */
button svg {
  color: black;
}
button:hover svg {
  color: grey;
}
  • QML: We have a MouseArea component with a containsMouse property we can easily bind to, e.g. when declaring what color something should have.
Item {
  MouseArea {
    id: theMouseArea
    hoverEnabled: true
    Icon {
      // We can access the hover state as a child of the MouseArea
      color: theMouseArea.containsMouse ? 'green' : 'blue'
    }
  }
  Icon {
    // We can access the hover state even outside the MouseArea, as long
    // as we can refer to the MouseArea via its id
    color: theMouseArea.containsMouse ? 'green' : 'blue'
  }
}
  • egui: I'm not familiar with egui, but it looks like you can ask whether there's any user interaction for any rect when building up the UI. We could use Ui::interact, Ui::allocate_response, etc.
// Haven't tested this so it might not be correct, but it's based on the docs:
let response = ui.allocate_response(egui::vec2(100.0, 200.0), egui::Sense::hover());
let color = if response.hovered() {
  egui::Color32::WHITE
} else {
  egui::Color32::GRAY
}
ui.painter().rect_stroke(response.rect, 0.0, (1.0, color));

Drawbacks

  • Unlike QML and egui where the hover state is accessible outside of the mouse area itself, this approach can only supply the mouse state to the children of the mouse_area widget. I wonder if we could use a similar Id mechanism as in widget operations to query the mouse state outside the mouse area, although I'm not sure how the API would look like.
  • Similar to the Responsive, we'd lose the advantage of having the view only depending on the application state.
  • The icon_button example might no longer be valid as soon as we add transition animations to the colors, assuming we need to track the animation start time in the application state (unless this animation state is tracked internally in the widget state).
  • It might cause unnecessary redraws due to mouse state changes that we don't care about

Alternatives

  • Perhaps this could be a separate widget. Indeed, this would no longer make it just a "mouse listener", and more of a "mouse area".
  • We could perhaps extend the styling/theming system to support these use-cases similar to CSS.
  • Perhaps we can generalize it a bit more, and think about how other "external states" could be supplied to the application. This includes: what keys are currently pressed (assuming it is already tracked somewhere), current date/time, mouse position. These are like the "inputs" you'd get in shadertoy. However, as they are kinda like "global external states", perhaps it's not too bad for them to live in the application state since there would only be one instance of each, whereas our mouse_area example would be per-widget.

Example use cases for getting the currently pressed keys:

  • Showing a virtual on-screen keyboard and highlighting the currently pressed keys.
  • When pressing shift, a slider or knob visually indicates that it is in "fine-adjustment" mode. When the user drags this knob, the knob sends either a CoarseAdjustment or a FineAdjustment message depending on the shift key press state.

@mmstick
Copy link
Contributor Author

mmstick commented Dec 29, 2022

@ErnWong You can find the implementation at iced-rs/iced#1594 to experiment with

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?

@akavel
Copy link

akavel commented Jan 9, 2024

I am curious: given that iced-rs/iced#1594 is now merged, which references this PR, is this RFC now considered accepted and should be merged? or should it be tweaked? or is it still not fully ready or something because of lacking mouse-enter/-leave events implementation? it looks kinda weird and confusing to me, that an implementation was merged, but its RFC is open 🤪🤔 or am I misunderstanding something?

@VAWVAW
Copy link

VAWVAW commented Jan 20, 2024

I it also in scope for this widget to provide a custom iced::mouse::Interaction? I feel like this makes sense if you want to implement some custom button-like behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants