Jump to content

  • Log In with Google      Sign In   
  • Create Account

Representing keyboard and mouse focus


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
11 replies to this topic

#1 AaronWizardstar   Members   -  Reputation: 244

Like
0Likes
Like

Posted 08 August 2012 - 05:05 PM

I'm designing a GUI system where I use a Widget class to represent everything. A widget can have child widgets (buttons in a panel, label in a button) and my game feeds the root widget input events from SFML or whatever. When given an event the widget does an action in response then (optionally) hands the event down to its children.

[source lang="cpp"]class Widget private Widget parent private Array<Widget> children // True if mouse hit this widget or any of its children public bool mouseDown(int mx, int my) bool gotClicked = false bool blockChildren = false bool childGotClicked = false if visible and enabled if contains(mx, my) gotClicked = true blockChildren = onMouseDown(mx, my) if not blockChildren // Children are ordered front to back. // Children are not necessarily inside the widget. for c in children childGotClicked = c.mouseDown( x - this.x, y - this.y) if childGotClicked gotClicked = childGotClicked break return gotClicked // True if mouse event is blocked from the children protected virtual bool onMouseDown(int mx, int my)[/source]

In addition to mouseDown, widgets will respond to mouse drag and mouse up events. I'm going to ignore mouse move events so that I don't have to worry about enter and leave actions, and because touchscreens are teh futures.

I'm thinking about good ways to handle keyboard and mouse focus. A widget gets keyboard focus either programatically or by being clicked, and has all keyboard events forwarded to it. Mouse focus is something I'm thinking of in terms of widgets like scrollbars responding to mouse drag events even when the mouse is outside of them. I figure that would be like one widget receiving all mouse drag events, so the widget would have mouse focus in the same way a widget can have keyboard focus until the next mouse-up. Plus I want the ancestors of the focused widget (either keyboard or mouse) to see the relevant event first like with the mouseDown event.

One idea I came up with is representing focus state with four variables: hasKeyFocus, hasMouseFocus, keyFocusedChildIndex, and mouseFocusedChildIndex. A widget responds to a key if its hasKeyFocus is true or its keyFocusedChildIndex is greater than -1, and the widget passes the event along to its child at children[keyFocusedChildIndex]. A widget uses its hasMouseFocus and mouseFocusedChildIndex variables in the same way for mouse drag and mouse up events.

Is this a good way to handle focus for key and mouse drag events?

Sponsor:

#2 ApochPiQ   Moderators   -  Reputation: 15736

Like
1Likes
Like

Posted 08 August 2012 - 05:41 PM

Since, by definition, only one widget can ever have focus at a time, why not just store a reference to that widget someplace centralized?

#3 AaronWizardstar   Members   -  Reputation: 244

Like
0Likes
Like

Posted 08 August 2012 - 05:55 PM

I said I want all ancestors of the focused widget to see the events directed at it. Suppose we have a widget tree like this:
  • Main Panel
    • Panel A
    • Button A
    • Button B
  • Panel B
If Button A had the focus, I'd want the event processing to go Main Panel > Panel A > Button A, in that order, instead of the event going straight to Button A.

As for keeping the information in a centralized location, I want as little overhead as possible. I don't want to wrap my widgets in a "gui manager" class. The application just needs to keep a reference to one or more root widgets, where a widget can be anything from a single button to a large control panel.

#4 Mussi   Crossbones+   -  Reputation: 1969

Like
0Likes
Like

Posted 08 August 2012 - 06:27 PM

As for keeping the information in a centralized location, I want as little overhead as possible. I don't want to wrap my widgets in a "gui manager" class. The application just needs to keep a reference to one or more root widgets, where a widget can be anything from a single button to a large control panel.

If anything, I think a central location would actually reduce overhead. If you don't want a manager or globals, you could go for a static members.

#5 ApochPiQ   Moderators   -  Reputation: 15736

Like
1Likes
Like

Posted 09 August 2012 - 12:26 AM

I'm not clear on what you gain by complicating your focused event dispatch in this manner?

#6 AaronWizardstar   Members   -  Reputation: 244

Like
0Likes
Like

Posted 09 August 2012 - 03:24 PM

I'm not clear on what you gain by complicating your focused event dispatch in this manner?

How is focus normally represented in GUIs then? And how are dragging scrollbars normally handled?

#7 ApochPiQ   Moderators   -  Reputation: 15736

Like
1Likes
Like

Posted 09 August 2012 - 08:04 PM

Exactly as I described. You track which single and specific widget has focus, and nothing else.

Dragging widgets is typically done with a secondary type of "focus" that (at least in the Windows world) is referred to as "mouse capture."

#8 AaronWizardstar   Members   -  Reputation: 244

Like
0Likes
Like

Posted 10 August 2012 - 04:43 PM

I'd still like the ancestors of the focused widget to see the event before the widget itself sees it though.

One example I had in mind for my project is a panel with a draggable row of pictures. I can either click on a picture to "select" it and have some data show up somewhere, or I can grab and drag anywhere in the whole row panel to scroll it.

That is, I'd want to be able to click-and-drag anywhere inside the panel - including inside a picture - and drag the row of pictures around. Clicking and not moving the mouse while over a picture would be interpreted as selecting the picture.

I figured I could make this work by letting pictures under the mouse (if any) get the focus but let the parent panel see the events sent to them. That way if the mouse is dragged beyond a certain threshold I can have the panel do its scrolling action even when it's a picture that has the actual focus.

Edited by AaronWizardstar, 10 August 2012 - 04:44 PM.


#9 ApochPiQ   Moderators   -  Reputation: 15736

Like
1Likes
Like

Posted 10 August 2012 - 05:35 PM

That's easy enough, just use something like this pseudocode:

on_gui_event(event)
    pass_event_to_widget(current_focus_widget, event)

pass_event_to_widget(widget, event)
    if(widget.parent)
        if(pass_event_to_widget(widget.parent, event))
            return true

    if(widget.wants_event(event))
        widget.handle_event(event)
        return true

    return false


#10 Amr0   Members   -  Reputation: 1110

Like
0Likes
Like

Posted 11 August 2012 - 06:38 AM

I'd still like the ancestors of the focused widget to see the event before the widget itself sees it though.

One example I had in mind for my project is a panel with a draggable row of pictures. I can either click on a picture to "select" it and have some data show up somewhere, or I can grab and drag anywhere in the whole row panel to scroll it.
[...]


Hi. I'd like to 1-up ApochPiQ's recommendation on following the design of a single focus widget at a time. Using a hierarchical focus approach can complicate things. Consider: you have a text box in a scrollable window. The user clicks on the text box and presses the right arrow key to move the caret in the text box, but the parent window sees this and scrolls to the right. Chaos.

The 1-or-zero focus window/widget approach, which happens to be what all GUI libraries I've come across use, has proven to be functional and versatile enough to implement whatever functionality you can think of.

Regarding your picture panel, the functionality of scrolling when the user clicks and drags and selecting when the user clicks but does not drag can be implemented using the single focus widget approach in a number of ways. The first obvious way is to not use widgets for the pictures in the panel and instead have the panel draw the pictures itself, as well as perform the required mouse hit-testing. This approach is used a lot. For example, tabs in a tab control are not individual widgets. There is no set criteria, but in general, if it's "a component in a list" kind of thing and doesn't require complex internal user interactions, it's not a widget, but an internal component of the parent widget.

So, the picture panel detects when the user clicks, and waits for the user to release the mouse button. If during this wait the user moves the mouse a certain number of pixels away from the original click location, it initiates a scroll operation. Otherwise, the picture is selected when the mouse button is released.

#11 AaronWizardstar   Members   -  Reputation: 244

Like
0Likes
Like

Posted 11 August 2012 - 05:22 PM

That's easy enough, just use something like this pseudocode:

on_gui_event(event)
	pass_event_to_widget(current_focus_widget, event)
pass_event_to_widget(widget, event)
	if(widget.parent)
		if(pass_event_to_widget(widget.parent, event))
			return true
	if(widget.wants_event(event))
		widget.handle_event(event)
		return true
	return false

So rather than pass events to the root widget, where a widget first processes the event then decides which child to pass the event to, I'd pass events to the focused widget, where a widget first sends the even to its parent then processes the event.

Regarding your picture panel, the functionality of scrolling when the user clicks and drags and selecting when the user clicks but does not drag can be implemented using the single focus widget approach in a number of ways. The first obvious way is to not use widgets for the pictures in the panel and instead have the panel draw the pictures itself, as well as perform the required mouse hit-testing. This approach is used a lot. For example, tabs in a tab control are not individual widgets. There is no set criteria, but in general, if it's "a component in a list" kind of thing and doesn't require complex internal user interactions, it's not a widget, but an internal component of the parent widget.

Hm. My example of the scrollable row of pictures would have been implemented with a panel that contains a row layout widget, with the row layout widget containing a bunch of image widgets, If the panel becomes a single widget without focusable sub-widgets then would it still be possible for me to reuse the image and row layout widgets? I want to leverage composition as much as possible throughout my GUI system, e.g. buttons contain text labels.

#12 Amr0   Members   -  Reputation: 1110

Like
0Likes
Like

Posted 12 August 2012 - 02:43 AM

A button contains a text label. It also contains an icon, a background, an optional drop-down arrow, various states (hot, disabled), and so on. These are visual "widgets". To be reusable, they don't have to be real widgets (meaning fully interactive widgets that process mouse and keyboard input, have their own message queues or whatever functionality you have for what you call a widget). They can be "visual elements". I assume your "raw layout widget" is a widget that arranges other widgets in rows? If so, wouldn't it be more reusable if it were a simple class which takes as input a rectangle, spacing parameters, and a list of sized items, and outputs a list of arranged rectangles? It could do hit-testing and even animations.

But that's not the point. The point is that for input processing, a single input processor (widget) at a time is more intuitive, predictable, and less bug-prone. But then again, using a hierarchical focus approach is not necessarily WRONG. As long as you understand what's going on in your system and have a clear understanding of how input messages are being passed around and processed, and if you feel it's more intuitive for you, then go ahead and use it. Just with all design choices, if you later find that you are doing a lot of shortcuts and hacks in your code, it's an indication that the design may be flawed. So pick something, work with that, and see how it works for you.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS