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

Provide Ui methods that allow for user input to be captured externally. #613

Open
mitchmindtree opened this issue Nov 2, 2015 · 6 comments

Comments

@mitchmindtree
Copy link
Contributor

This would be useful when conrod is used alongside other non-conrod user interface code (i.e within turbine).

To achieve this, we could simply add a CaptureSource enum, i.e.

/// Indicates where the user input is being captured from.
pub enum CaptureSource {
    /// The user input is captured by external means.
    External,
    /// The user input is captured by an internal widget.
    Internal(widget::Index),
}

We could use this within the Capturing enum's Captured variant in place of the widget::Index.

I imagine the methods would look something like this:

ui.capture_mouse();
ui.uncapture_mouse();
ui.capture_keyboard();
ui.uncapture_keyboard();

When capture_mouse or capture_keyboard are called, the Ui would force its maybe_captured_mouse/keyboard field to Some(Captured(External)) whether or not the user input was captured or not. The methods return a bool, indicating whether or not the user input was already captured.

We could also add try_capture_mouse and try_capture_keyboard methods which would only capture the input if they were not already captured, returning a bool indicating whether or not the user input was successfully captured.

@bvssvni
Copy link
Member

bvssvni commented Nov 2, 2015

Hmm... this was a bit harder than anticipated. Haven't thought this through yet, so just posting ideas:

  • The split between capturing mouse and keyboard might be problematic. Too many states to keep track of?
  • Would like a solution where you direct all events to the controller that has the attention of the user.
  • When directing events externally, simply don't call ui.handle_event.
  • Can we use Input::Focus?
  • Would like a solution to prevent widgets from taking the user as hostage. It should be possible to recover even if there is a bug in a widget.
  • If we implement tabs to move between fields, then I think it is OK to cycle within Conrod widgets. However, it might be some cases where this is a problem...

@bvssvni
Copy link
Member

bvssvni commented Nov 2, 2015

What if we build in the logic of focusing events on the background? Could handle_event just return Option<&E: GenericEvent>?

  • Conrod could do the filtering and pass on the events that are not handled by the UI.
  • When clicking outside with the mouse, both mouse and keyboard are redirected.

@bvssvni
Copy link
Member

bvssvni commented Nov 2, 2015

I am starting to believe that your idea is better. We could implement it, then try it out and see if it works. If it doesn't, then we'll try something else.

@psFried
Copy link
Contributor

psFried commented Nov 4, 2015

It sounds like the essential question here is, what owns the decision of whether a particular event applies to a given widget? Right now, each widget has visibility to all events and the widget decides whether or not keyboard/mouse input applies to it. Do I understand that correctly? Going forward, think it makes sense that we have clarity on what owns that decision.

If the Widget owns the decision as to which key/mouse events apply to it, then it doesn't seem to make sense to call Ui::try_capture_keyboard. Widget::capture_keyboard would seem to make more sense, and the Ui shouldn't then care which Widget is capturing.

On the other hand, we could have the Ui determine which keyboard/mouse events apply to which widgets. In this case, it would seem to make sense to just call something like Ui::try_capture_keyboard(widget::Index). In this case, I would think that the Ui should not even provide visibility to key/mouse events that don't apply to a Widget. If the Ui knows/decides which Widget is capturing, then why provide input events to widgets that aren't capturing?

I think either approach is valid, but it seems like currently, it's really caught in the middle. I've been thinking a lot about this because of the difficulties in coming up with a clean solution for #606 . Any thoughts on pros/cons of either approach? Or do you think I'm just barking up the wrong tree with this question?

@mitchmindtree
Copy link
Contributor Author

Hey @psFried, you're not barking up the wrong tree at all! Input like this is always highly appreciated 😸

Just to clarify, this issue proposes to add one extra way of capturing input - the External variant. This is what I was proposing the Ui::capture_mouse/keyboard methods for, though perhaps a better name would be Ui::capture_mouse/keyboard_externally. This would only be exposed via the Ui and in turn only exposed to top-level users of the widgets. Btw, the Ui cannot be accessed directly in an implementation of the Widget trait, so we don't have to worry about widgets secretly calling this.

At the moment conrod is designed so that only one widget may capture either kind of user input at a time. This widget is tracked within the Ui with its maybe_captured_mouse/keyboard fields. This decision makes it easier to reduce capturing conflicts (perhaps introduced accidentally by 3rd party widget designers) as it means it is only possible for a single widget to occupy the maybe_captured_mouse/keyboard fields. It also means that the Ui is the clear owner of the decision making process on who may or may not capture input as it is the sole owner of those two fields.

Side note: A user should never need to capture more than one widget at once, as they can only click one thing at a time or enter text into one thing at a time. If they do come across an instance where multiple widgets need to be captured (let's say typing into multiple TextBoxes at once) this behaviour should be introduced by wrapping the multiple widgets in a single widget (called MultiEntryTextBox for example) so that it is always clear who is the single receiver of input.

The Ui is the type that passes on the user input to each widget when it comes time to Widget::update. It first checks for capturing widgets before passing on the user input (see here). This way we can say that the Ui is also the clear owner of the process of distributing user input to widgets.

Seeing as we may only have a single capturer of a kind of input at a time, it doesn't make sense for a widget to be able to immediately steal input whenever it wants, especially if it is already captured by another widget. Thus we want to make sure that although a widget can make a request to capture input, the Ui gets the final say. To do this, we currently provide methods on UiCell (a wrapper around the Ui exposing a subset of its functionality necessary for use by widget developers - see here for more info). The methods only allow making a request to capture input (which is denied if input is already being captured by another widget) and never forcing capture (there's an issue to change these method names to better suited ones here). This is different to the user-facing capture_mouse/keyboard_externally as proposed here, which should have the opportunity of stealing capturing in case some third party, non-conrod code spontaneously needs to direct input away from conrod as a result of some external event or UI process.

Sorry for the rambling, hopefully this clarifies things a bit - let me know if there are still issues! I'll try and add this stuff to the guide #505 when it happens one day heh.

Thanks a lot for putting some thought into tab switching btw, would certainly be nice to have :)

@psFried
Copy link
Contributor

psFried commented Nov 5, 2015

@mitchmindtree Thanks, that does clarify things. I think overall, it makes sense to me that each Widget should not even receive Input if it is not capturing. The Ui could determine which Widget should begin capturing whenever the mouse is clicked. That would save every Widget from having to do the same check to see if it should ask the Ui to be given focus. Seems like that would simplify the issue of adding methods to ui to change what is capturing keyboard/mouse, and I think it would also simplify adding the ability to tab between input fields.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants