disable

Zini

06-09-2007 15:13:35

When I disable a widget (a window in my case) and show it afterwards, it stays invisible. Is that a bug? I was under the impression that the disable method only makes the window ignore events and user interaction.

kungfoomasta

06-09-2007 16:15:15

If the button you are using uses texture X.png, the texture X.disabled.png is applied to the widget on disable. Any time you try to set the texture of a widget and the texture does not exist, either on disk, in a SkinSet, or in Ogre::TextureManager, the Widget is not rendered (invisible). My guess is that you do not have the disabled texture for the widget. Could be because I have not made these for the qgui skinset yet.. :oops:

Zini

06-09-2007 16:41:55

Correct. I copied qgui.window.png to qgui.window.disabled.png and now it works. Thanks.

Edit: But only for small amounts of "works". The window is visible, but it reacts to mouse clicks by taking the focus.

kungfoomasta

06-09-2007 17:04:09

Sounds like a bug. You're saying that the window is disabled, but can be brought to the front by clicking on it? (bringing into focus, ie EVENT_GAIN_FOCUS) I'll try to repro this tonight.

Zini

06-09-2007 17:26:12

No idea, if it jumps to the front. But I had the focus in another window and after clicking on the disabled window, the other window wouldn't take keyboard input anymore.

Since you mentioning it, I have another feature request. Can the raise-on-click-behaviour be made configurable? (on/off, maybe on a per-window basis)

kungfoomasta

06-09-2007 17:33:57

I can see where that would be useful, especially if you have static windows.

Something like this?


bool mBringToFrontOnFocus;

Window::setBringToFrontOnFocus(bool BringToFront);


after clicking on the disabled window

It may appear like you clicked on the disabled window, but I bet you clicked behind it, and another widget (Sheet or other) got focus. Disabled widgets are pretty locked down unless I missed something.

Zini

06-09-2007 17:39:27

Right. Must be the sheet. So I should disable the sheet too.

Edit: setBringToFrontOnFocus sounds good

kungfoomasta

06-09-2007 17:51:12

I haven't tested disabling the sheet, I wonder if any problems will arise from that.

I've been thinking, maybe I should let disabled widgets be visible in terms of detection, and also make them clickable, however don't change active widget if clicking on a disabled widget. Currently Disabled Widgets are not detectable, for example, I make use of mWidget->getTargetWidget(Point p), but if this widget is hidden or disabled, the function will immediately return NULL.

Regarding the Sheet.. I think I want to prevent the sheet from being disabled. Unless you find a good reason not to do this, but I think it interferes with my current design.

Zini

06-09-2007 18:12:28

Then we should probably think of something else. Having a disabled window keeping mouse input, but not reacting on the clicks, won't do it for me. What if you want to have some passive HUD-elements in front of all other stuff (like a big red warning message)? Such a message shouldn't stop you from using windows behind it.

Maybe you could change the implementation of the sheet in a way, that it never accepts mouse clicks?

kungfoomasta

06-09-2007 18:24:25

What if you want to have some passive HUD-elements in front of all other stuff (like a big red warning message)?

Good point. I will disable ability to disable/enable Sheet, that should solve our problems. I'll just note it in the comments of the function that the Sheet widget cannot be disabled/enabled.

Zini

10-09-2007 09:42:23

The sheet is still accepting mouse clicks. I thought that was already sorted out.

And what happend to the setBringToFrontOnFocus method?

kungfoomasta

10-09-2007 16:17:13


void Widget::disable()
{
if((mWidgetType == Widget::TYPE_SHEET))
return;

mEnabled = false;

setTexture(mDisabledTextureName,false);

WidgetEventArgs args(this);
fireEvent(EVENT_DISABLED,args);
}


I will disable ability to disable/enable Sheet

As you can see, it is impossible to disable a sheet. :wink:

And according to the SVN checkout I did last night, I am seeing Window having this method: "void setBringToFrontOnFocus(bool BringToFront);"

Used here:

void Window::onGainFocus(const EventArgs& args)
{
if(mBringToFrontOnFocus)
mQuadContainer->moveWindowGroupToEnd(this);
}


Don't remember when I added this code, but it wasn't last night. Not sure why you aren't seeing the window function. As for sheet, we agreed you cannot disable it, so it will always accept mouse clicks. (disabling a sheet may crash the way GUIManager looks for mouse over widget)

[edit] I do need to do something about firing the event. fireEvent method does not fire the event if the widget is disabled, thus fireEvent(EVENT_DISABLED,...) won't call any of it's event handlers..
[/edit]

Zini

10-09-2007 17:07:35

No idea either, why the search function didn't show up the method. I can confrim that I have it in my copy of QuickGUI. Very strange ...


As for sheet, we agreed you cannot disable it, so it will always accept mouse clicks. (disabling a sheet may crash the way GUIManager looks for mouse over widget)


That's something I certainly not agreed on. In fact this is a major problem for me, because now when clicking on the background outside of any window the current window will lose the input focus. That is a behaviour I absolutely don't want.

Just an idea: Maybe we can add another method similar to setBringToFrontOnFocus ?

setGainFocusOnClick maybe?

And since we are at it, maybe setBringToFrontOnClick would be more useful than setBringToFrontOnFocus?

kungfoomasta

10-09-2007 17:19:26

And since we are at it, maybe setBringToFrontOnClick would be more useful than setBringToFrontOnFocus?

Don't remember if this is implemented, but there should be a Widget::focus, where the user can give focus to a Widget. There may be times when you want to give a window focus, but not bring it to the front.

So we'll need to support this in addition to setGainFocusOnClick

The GUIManager will have to query setGainFocusOnClick, to know if it can set the widget as the active widget or not. (active widget receives the gain focus event, etc.)

Also, in your scenario, are you able to click other widgets while the window is open? I was thinking we could have a *locked* property, where the window remains in front and nothing else can be clicked until the window goes away. (Similar to error message, or Save/Don't Save/Cancel type windows) If I may ask, what is your scenario?

Zini

10-09-2007 17:29:51

My scenario is basically that I come from a vastly different GUI background than Windoze and I find the Windoze style window/focus behaviour totally retarded.

OK, enough Windows flaming. What I like to be able to do, is the following:

Using one window with the keyboard and at the same time using a different window with the mouse or operating with the mouse in the 3D world (i.e. outside the GUI).

kungfoomasta

10-09-2007 18:37:13

Are you using 2 different Render Windows?

Zini

10-09-2007 19:03:25

No, only one.

Edit: Just to make this clear. The functionallity described above is the reason, why I have a problem with the sheet taking the focus. Essentially I want total control over where the keyboard input is going to.

I need the setBringToFrontOnFocus or setBringToFrontOnClick method, because I don't want the user/player to change the Z-order of windows.

kungfoomasta

10-09-2007 19:50:07

ok, these should be easy enough to add. They'll be in SVN by tomorrow, unless I encounter some issues implementing them. (hopefully not)

kungfoomasta

11-09-2007 08:09:59

I have added your requests into SVN, but I was unsure if you wanted the widget to receive events. As it is now, you can make a widget not transfer focus, but it still receives mouse up/down/click events, and does not invoke any lose_focus or gain_focus events, or change the ActiveWidget. In my mind this is a good feature, but if you prove otherwise, I can easily make it so that these widgets do not receive any events. The functionality is untested, let me know if it needs fixing. :)

Zini

11-09-2007 08:23:56

Almost perfect. What a relieve! Thank you very much.

The only problem I encountered is that TextBoxes still show the cursor, when clicking on them, even if they are set to not gaining the focus. This is even true for read-only TextBoxes.

kungfoomasta

11-09-2007 16:18:47

The TextBox onMouseDown is set to start blinking the cursor. I modified the handler to immediately return if mGainFocusOnClick is false.

kungfoomasta

11-09-2007 16:37:45

Are you just testing the TextBoxes, or will they all have mGainFocusOnClick = false? Unless you manually give them focus, or inject characters to them, they won't handle keyboard input, right?

Zini

11-09-2007 17:11:01

Originally I had only the parent window as mGainFocusOnClick==false, but now I did the same for the TextBoxes and the result is the same (haven't updated yet to your latest commits). However I think we have a more fundamental problem here. The TextBox are set to read-only mode, so they shouldn't get the cursor at all, right?

Your assumption, that the TextBoxes won't handle keyboard input, is correct. It is pure cursor problem.

kungfoomasta

11-09-2007 17:20:56

Yah, I made an incorrect fix to the TextBox. When I get home I will fix it up.

if(mReadOnly)
return;

:)

Zini

12-09-2007 09:18:50

Confirmed. Works now.

kungfoomasta

03-04-2008 20:27:08

Reviving an old thread..

I've decided to remove the gainFocusOnClick functionality, it doesn't feel intuitive. I do realize what you were trying to accomplish now: Clicking on the Sheet to interact with the scene, and still wanting to maintain current focus, to enter in text or key presses, etc. Instead of making a hacked up disabled Sheet, I will prevent the Sheet from ever having focus. (Child widgets of the sheet can have focus) If for some reason you do want the sheet to have focus, you can imitate this by creating a panel the size of the sheet, and giving that panel focus. This way, every widget you click on (except the sheet) will gain focus.

I haven't thought up how to address the scenario of some on screen warning pop up, and using windows underneath the warning. I want to support Modal windows, but you can only have 1 modal window at a time. I'll have to think up an implementation and interface for this later on.

Zini

03-04-2008 22:47:18


I've decided to remove the gainFocusOnClick functionality,


That unfortunately breaks a significant part of my GUI. The message stuff was only an example and a modal window certainly won't help here. Why should a message stop the player for interacting with other GUI elements? Sorry, but I have to disagree with you here. gainFocusOnClick is necessary.

kungfoomasta

03-04-2008 23:10:59

Hm, I thought this would solve all our problems. Can you share a user scenario where you use this functionality? Its tough keeping this functionality in the code, since its really an exception to the normal idea of "if you click it, it gains focus".

Why should a message stop the player for interacting with other GUI elements?

I don't really understand this.

If you could help me out, here is my revised code for input injection. Please review it and we can discuss how to keep this functionality, if it is needed. (Note that I have only drafted this, I have no compiled and ruled out syntax errors)



bool GUIManager::injectMouseButtonDown(const MouseButtonID& button)
{
// If the mouse is disabled, we do not accept this injection of input
if( !mMouseCursor->getEnabled() )
return false;

// Users can manually inject single/double/triple click input, or have it detected
// from mouse button up/down injections.
if(mDetermineClickEvents)
{
// Record time of MouseDown, for single/double/triple click determination
mTimeOfButtonDown[button] = mTimer->getMilliseconds();

// Check if time since last double click is within triple click time
if((mTimeOfButtonDown[button] - mTimeOfDoubleClick[button]) <= mGUIManagerDesc.tripleClickTime)
{
// We have passed the criteria for a triple click injection, but we have to
// make sure the mouse button is going down on the same widget for all clicks.
if(mMouseButtonDown[button] == mWidgetUnderMouse)
return injectMouseTripleClick(button);
}
// Check if time since last click is within double click time
if((mTimeOfButtonDown[button] - mTimeOfClick[button]) <= mGUIManagerDesc.doubleClickTime)
{
// We have passed the criteria for a double click injection, but we have to
// make sure the mouse button is going down on the same widget for both clicks.
if(mMouseButtonDown[button] == mWidgetUnderMouse)
return injectMouseDoubleClick(button);
}
}

// If we make it here, a simple mouse button down has occurred.

// Modify the button mask
mButtonMask |= (1 << button);

// Record that the mouse button went down on this widget
mMouseButtonDown[button] = mWidgetUnderMouse;

// Create MouseEventArgs, for use with any fired events
MouseEventArgs args(mWidgetUnderMouse);
args.position = mMouseCursor->getPosition();
args.buttonMask = mButtonMask;
args.keyModifiers = mKeyModifiers;

// Create a boolean to track whether or not this injection caused any significant changes.
bool changesMade = false;

// Get the Window under the mouse cursor. If it is not the Sheet, but a child Window,
// make sure it has focus. (bring to front)
Window* win = mActiveSheet->getWindowAtPoint(args.position);
if(win != mActiveSheet)
// FOCUS_GAINED and FOCUS_LOST events will be fired if appropriate.
changesMade |= mActiveSheet->focusWindow(win);

Widget* w = win->getWidgetAtPoint(args.position);
// Set focus to the widget the mouse went down on.
// FOCUS_GAINED and FOCUS_LOST events will be fired if appropriate.
changesMade |= mActiveSheet->focusWidget(w);

w->setGrabbed(true);
// Fire EVENT_MOUSE_BUTTON_DOWN event to the widget in focus
changesMade |= w->fireEvent(Widget::EVENT_MOUSE_BUTTON_DOWN,args);

return changesMade;
}

bool GUIManager::injectMouseButtonUp(const MouseButtonID& button)
{
// If the mouse is disabled, we do not accept this injection of input
if( !mMouseCursor->getEnabled() )
return false;

// Modify the button mask
mButtonMask &= !(1 << button);

// If the mouse button goes up and is not over the same widget
// the mouse button went down on, disregard this injection.
if( mMouseButtonDown[button] != mWidgetUnderMouse )
return false;

// after this point, we know that the user had mouse button down on this widget, and is now doing mouse button up on the same widget.

// Create MouseEventArgs, for use with any fired events
MouseEventArgs args(mActiveSheet->getWidgetInFocus());
args.position = mMouseCursor->getPosition();
args.buttonMask = buttonMask;
args.keyModifiers = mKeyModifiers;

// Check if a widget is currently being dragged.
if(args.widget->getGrabbed())
{
args.widget->setGrabbed(false);
// We do not want the widget to receive an EVENT_MOUSE_BUTTON_UP event if we are dropping
// the widget. Think of Diablo II, dragging a potion to your belt. If we sent the mouse
// button up event the potion would be drank as soon as you dropped it into the belt.
return args.widget->fireEvent(Widget::EVENT_DROPPED,args);
}

// Create a boolean to track whether or not this injection caused any significant changes.
bool changesMade = false;

// Fire EVENT_MOUSE_BUTTON_UP event
changesMade |= args.widget->fireEvent(Widget::EVENT_MOUSE_BUTTON_UP,args);

// Users can manually inject single/double/triple click input, or have it detected
// from mouse button up/down injections.
if(mDetermineClickEvents)
{
if((mTimer->getMilliseconds() - mTimeOfButtonDown[button]) <= mGUIManagerDesc.clickTime)
{
changesMade |= injectMouseClick(button);
}
}

return changesMade;
}

bool GUIManager::injectMouseClick(const MouseButtonID& button)
{
// If the mouse is disabled, we do not accept this injection of input
if( !mMouseCursor->getEnabled() )
return false;

// Modify the button mask
mButtonMask |= (1 << button);

// Record the time the click occurred. Useful for generating double clicks.
mTimeOfClick[button] = mTimer->getMilliseconds();

// Create MouseEventArgs, for use with any fired events
MouseEventArgs args(mActiveSheet->getWidgetInFocus());
args.position = mMouseCursor->getPosition();
args.buttonMask = mButtonMask;
args.keyModifiers = mKeyModifiers;

return args.widget->fireEvent(Widget::EVENT_MOUSE_CLICK,args);
}

bool GUIManager::injectMouseDoubleClick(const MouseButtonID& button)
{
// If the mouse is disabled, we do not accept this injection of input
if( !mMouseCursor->getEnabled() )
return false;

// Modify the button mask
mButtonMask |= (1 << button);

// Record the time the click occurred. Useful for generating triple clicks.
mTimeOfDoubleClick[button] = mTimer->getMilliseconds();

// Create MouseEventArgs, for use with any fired events
MouseEventArgs args(mActiveSheet->getWidgetInFocus());
args.position = mMouseCursor->getPosition();
args.buttonMask = mButtonMask;
args.keyModifiers = mKeyModifiers;

return args.widget->fireEvent(Widget::EVENT_MOUSE_CLICK_DOUBLE,args);
}

bool GUIManager::injectMouseTripleClick(const MouseButtonID& button)
{
// If the mouse is disabled, we do not accept this injection of input
if( !mMouseCursor->getEnabled() )
return false;

// Modify the button mask
mButtonMask |= (1 << button);

// Create MouseEventArgs, for use with any fired events
MouseEventArgs args(mActiveSheet->getWidgetInFocus());
args.position = mMouseCursor->getPosition();
args.buttonMask = mButtonMask;
args.keyModifiers = mKeyModifiers;

return mWidgetContainingMouse->fireEvent(Widget::EVENT_MOUSE_CLICK_TRIPLE,args);
}

bool GUIManager::injectMouseMove(const int& xPixelOffset, const int& yPixelOffset)
{
// If the mouse is disabled, we do not accept this injection of input
if( !mMouseCursor->getEnabled() )
return false;

// See if we should be dragging a widget.
if( (mActiveSheet->getWidgetInFocus() == mWidgetUnderMouse) && (mWidgetUnderMouse->getDragable()) )
{
// Dragging, which uses move function, works with pixel values (uninfluenced by parent dimensions!)
mWidgetUnderMouse->drag(xPixelOffset,yPixelOffset);

return true;
}

// Now get the widget the cursor is over.
Widget* w = mActiveSheet->getWidgetAtPoint(args.position);

// Ignore disabled widgets
if(!w->getEnabled())
return false;

// Create MouseEventArgs, for use with any fired events
MouseEventArgs args(w);
args.position = mMouseCursor->getPosition();
args.moveDelta.x = xPixelOffset;
args.moveDelta.y = yPixelOffset;
args.buttonMask = mButtonMask;
args.keyModifiers = mKeyModifiers;

// Create a boolean to track whether or not this injection caused any significant changes.
bool changesMade = false;

// The Widget underneath the mouse cursor has changed.
if( mWidgetUnderMouse != w )
{
if(mWidgetUnderMouse != NULL)
{
changesMade |= mWidgetUnderMouse->fireEvent(Widget::EVENT_MOUSE_LEAVE,args);
}

// Update pointer
mWidgetContainingMouse = w;

if(mWidgetContainingMouse != NULL)
{
changesMade |= mWidgetUnderMouse->fireEvent(Widget::EVENT_MOUSE_ENTER,args);
}
}

// Notify the widget in focus the cursor has moved.
changesMade |= mActiveSheet->getWidgetInFocus()->fireEvent(Widget::EVENT_MOUSE_MOVE,args);

return changesMade;
}

bool GUIManager::injectMousePosition(const int& xPixelPosition, const int& yPixelPosition)
{
// If the mouse is disabled, we do not accept this injection of input
if( !mMouseCursor->getEnabled() )
return false;

Point oldPos = mMouseCursor->getPosition();
// Update cursor's position as seen on the screen.
mMouseCursor->setPosition(xPixelPosition,yPixelPosition);

// Determine the offset and inject a mouse movement input.
return injectMouseMove(xPixelPosition - oldPos.x,yPixelPosition - oldPos.y);
}

Zini

03-04-2008 23:43:29

I can give you some examples, not from my project, but from two very popular games:

Gothic III (that's an RPG in case you don't know): When you finish a quest and gain exp, a scrolling message appears in the middle of the screen telling you exactly that. This message is totally immune against any kind of mouse input.

World of Warcraft: If you type in a window of any kind (let's say auctions window or mailbox window) and then press a button on the button bar (which does not belong to the window ! ), the focus is not taken away from the window. You can happily continue to type away.

Hope that helps to explain my intentions. I will have a look at your code this weekend (can't do it earlier, because I kinda have a deadline tomorrow) and see if I can come up with any suggestions.

kungfoomasta

04-04-2008 00:10:14

Thanks for the information and scenarios.

For the Gothic III example, does the window get dismissed after some time, or how does it get hidden/closed? Are you able to click anything outside this window, or is it truly a modal window? Are there cases where you could have 2 of these windows at the same time, or does it seem like one slot is reserved for these always-on-top, input disabled windows? I would like to implement the ability to have 1 modal window visible at a time, which can be configured to allow or disallow outside gui interaction. (clicking a widget outside the button)

For the WoW example, this is a really good example. My thoughts on how to implement this without the focusOnClick functionality would be to add to the onMouseButtonUp handler that would set the focus to the previous focused widget. When you mouse over the button it calls a MOUSE_ENTER callback which stores the currently focused widget, and when clicked, does work and then restores focus back to the previously focused widget.

I welcome suggestions for this. Its not really that I don't want to keep supporting it, its just that I don't clearly see how it should be implemented. I want to start focusing on having the code clean and organized. The code base is quite big, and a lot of times I review/update a section of code and forget some specialized base case. I either need to document all special cases with more detail, or have fewer special cases, or both. (You can see the code I posted has a lot of documentation, making it easy to follow)

Thanks again for your input.

Zini

04-04-2008 00:35:21


For the Gothic III example, does the window get dismissed after some time, or how does it get hidden/closed? Are you able to click anything outside this window, or is it truly a modal window? Are there cases where you could have 2 of these windows at the same time, or does it seem like one slot is reserved for these always-on-top, input disabled windows? I would like to implement the ability to have 1 modal window visible at a time, which can be configured to allow or disallow outside gui interaction. (clicking a widget outside the button)


Its not a window, but a simple line of text which scrolls towards the upper border of the screen and fades away. And yes, it is possible to have more than one of them at the same time.And they aren't modal. The GUI system treats them as if they wouldn't be part of the GUI at all. They have no influence no any other window.

Hm, I think I remember a disable function. Haven't thought about that before. Could disable be used to get the behaviour described above?


For the WoW example, this is a really good example. My thoughts on how to implement this without the focusOnClick functionality would be to add to the onMouseButtonUp handler that would set the focus to the previous focused widget. When you mouse over the button it calls a MOUSE_ENTER callback which stores the currently focused widget, and when clicked, does work and then restores focus back to the previously focused widget.


Honestly the


normal idea of "if you click it, it gains focus"


is just a very bad idea IMHO. We couild argue about it, when it comes to regular desktop GUIs. But in game GUIs its not so common anyway. I can't think of another concrete example right now (its been a while since I have been a really active gamer), but I am sure there are a lot.
But if you want to stick with it, the onMouseButtonUp handler seems to be the best solution.


I want to start focusing on having the code clean and organized. The code base is quite big, and a lot of times I review/update a section of code and forget some specialized base case. I either need to document all special cases with more detail, or have fewer special cases, or both. (You can see the code I posted has a lot of documentation, making it easy to follow)


Couldn't agree more ;)


Thanks again for your input.


Thanks for accepting my quite demanding input. ;) When I was reviewing the other GUI systems (before I found QuickGUI), I was already halfway expecting having to write my own to get what I need.

kungfoomasta

04-04-2008 01:32:06

Could disable be used to get the behaviour described above?

When I first implemented disable, I was a little fuzzy in defining the behavior supported when disabled. Now I want to define disabled as "not able to be manipulated by user interaction". This means that via code or handlers, etc, you can do whatever you want to the widget, but you cannot interact with the widget in terms of input injections. (Now that I think about it, I should review my input injection code to make sure disabled widgets don't receive input related events )

To accomplish this I'd need to make sure that my getWidgetAtPoint and getWindowAtPoint functions don't retrieve disabled widgets. Or better yet, make it an option!

getWidgetAtPoint(const Point& p, bool ignoreDisabled=true);

I don't know how to organize or implement the idea of multiple windows that remain on top of everything else, I've only briefly thought about modal windows. Well in the case of 2 popping up at the same time, you could simulate that with 1 window the size of the screen, with LabelAreas in different places, scrolling text and making rows transparent over time.

kungfoomasta

04-04-2008 05:19:08

Here is my revised code. It builds and compiles, although I'm not sure if my "changesMade |=" statements work correctly. Basically I want a boolean to stay true once its set to true. Assuming the disable functionality will suit our needs, I will work with this in the new code base, although I'm up for any suggestions anybody wants to provide.



bool GUIManager::injectMouseButtonDown(const MouseButtonID& button)
{
// If the mouse is disabled, we do not accept this injection of input
if( !mMouseCursor->getEnabled() )
return false;

// Users can manually inject single/double/triple click input, or have it detected
// from mouse button up/down injections.
if(mGUIManagerDesc.determineClickEvents)
{
// Record time of MouseDown, for single/double/triple click determination
mTimeOfButtonDown[button] = mTimer->getMilliseconds();

// Check if time since last double click is within triple click time
if((mTimeOfButtonDown[button] - mTimeOfDoubleClick[button]) <= mGUIManagerDesc.tripleClickTime)
{
// We have passed the criteria for a triple click injection, but we have to
// make sure the mouse button is going down on the same widget for all clicks.
if(mMouseButtonDown[button] == mWidgetUnderMouseCursor)
return injectMouseTripleClick(button);
}
// Check if time since last click is within double click time
if((mTimeOfButtonDown[button] - mTimeOfClick[button]) <= mGUIManagerDesc.doubleClickTime)
{
// We have passed the criteria for a double click injection, but we have to
// make sure the mouse button is going down on the same widget for both clicks.
if(mMouseButtonDown[button] == mWidgetUnderMouseCursor)
return injectMouseDoubleClick(button);
}
}

// If we make it here, a simple mouse button down has occurred.

// Modify the button mask
mButtonMask |= (1 << button);

// Record that the mouse button went down on this widget
mMouseButtonDown[button] = mWidgetUnderMouseCursor;

// Create MouseEventArgs, for use with any fired events
MouseEventArgs args(mWidgetUnderMouseCursor);
args.position = mMouseCursor->getPosition();
args.buttonMask = mButtonMask;
args.keyModifiers = mKeyModifiers;

// Create a boolean to track whether or not this injection caused any significant changes.
bool changesMade = false;

// Get the Window under the mouse cursor. If it is not the Sheet, but a child Window,
// make sure it has focus. (bring to front)
Window* win = mActiveSheet->findWindowAtPoint(args.position);
if(win != mActiveSheet)
// FOCUS_GAINED and FOCUS_LOST events will be fired if appropriate.
changesMade |= mActiveSheet->focusWindow(win);

Widget* w = win->findWidgetAtPoint(args.position);
// Set focus to the widget the mouse went down on.
// FOCUS_GAINED and FOCUS_LOST events will be fired if appropriate.
changesMade |= mActiveSheet->focusWidget(w);

w->setGrabbed(true);
// Fire EVENT_MOUSE_BUTTON_DOWN event to the widget in focus
changesMade |= w->fireEvent(WIDGET_EVENT_MOUSE_BUTTON_DOWN,args);

return changesMade;
}

bool GUIManager::injectMouseButtonUp(const MouseButtonID& button)
{
// If the mouse is disabled, we do not accept this injection of input
if( !mMouseCursor->getEnabled() )
return false;

// Modify the button mask
mButtonMask &= !(1 << button);

// If the mouse button goes up and is not over the same widget
// the mouse button went down on, disregard this injection.
if( mMouseButtonDown[button] != mWidgetUnderMouseCursor )
return false;

// after this point, we know that the user had mouse button down on this widget, and is now doing mouse button up on the same widget.

// Create MouseEventArgs, for use with any fired events
MouseEventArgs args(mActiveSheet->getWidgetInFocus());
args.position = mMouseCursor->getPosition();
args.buttonMask = mButtonMask;
args.keyModifiers = mKeyModifiers;

// Check if a widget is currently being dragged.
if(args.widget->getGrabbed())
{
args.widget->setGrabbed(false);
// We do not want the widget to receive an EVENT_MOUSE_BUTTON_UP event if we are dropping
// the widget. Think of Diablo II, dragging a potion to your belt. If we sent the mouse
// button up event the potion would be drank as soon as you dropped it into the belt.
return args.widget->fireEvent(WIDGET_EVENT_DROPPED,args);
}

// Create a boolean to track whether or not this injection caused any significant changes.
bool changesMade = false;

// Fire EVENT_MOUSE_BUTTON_UP event
changesMade |= args.widget->fireEvent(WIDGET_EVENT_MOUSE_BUTTON_UP,args);

// Users can manually inject single/double/triple click input, or have it detected
// from mouse button up/down injections.
if(mGUIManagerDesc.determineClickEvents)
{
if((mTimer->getMilliseconds() - mTimeOfButtonDown[button]) <= mGUIManagerDesc.clickTime)
{
changesMade |= injectMouseClick(button);
}
}

return changesMade;
}

bool GUIManager::injectMouseClick(const MouseButtonID& button)
{
// If the mouse is disabled, we do not accept this injection of input
if( !mMouseCursor->getEnabled() )
return false;

// Modify the button mask
mButtonMask |= (1 << button);

// Record the time the click occurred. Useful for generating double clicks.
mTimeOfClick[button] = mTimer->getMilliseconds();

// Create MouseEventArgs, for use with any fired events
MouseEventArgs args(mActiveSheet->getWidgetInFocus());
args.position = mMouseCursor->getPosition();
args.buttonMask = mButtonMask;
args.keyModifiers = mKeyModifiers;

return args.widget->fireEvent(WIDGET_EVENT_MOUSE_CLICK,args);
}

bool GUIManager::injectMouseDoubleClick(const MouseButtonID& button)
{
// If the mouse is disabled, we do not accept this injection of input
if( !mMouseCursor->getEnabled() )
return false;

// Modify the button mask
mButtonMask |= (1 << button);

// Record the time the click occurred. Useful for generating triple clicks.
mTimeOfDoubleClick[button] = mTimer->getMilliseconds();

// Create MouseEventArgs, for use with any fired events
MouseEventArgs args(mActiveSheet->getWidgetInFocus());
args.position = mMouseCursor->getPosition();
args.buttonMask = mButtonMask;
args.keyModifiers = mKeyModifiers;

return args.widget->fireEvent(WIDGET_EVENT_MOUSE_CLICK_DOUBLE,args);
}

bool GUIManager::injectMouseTripleClick(const MouseButtonID& button)
{
// If the mouse is disabled, we do not accept this injection of input
if( !mMouseCursor->getEnabled() )
return false;

// Modify the button mask
mButtonMask |= (1 << button);

// Create MouseEventArgs, for use with any fired events
MouseEventArgs args(mActiveSheet->getWidgetInFocus());
args.position = mMouseCursor->getPosition();
args.buttonMask = mButtonMask;
args.keyModifiers = mKeyModifiers;

return mWidgetUnderMouseCursor->fireEvent(WIDGET_EVENT_MOUSE_CLICK_TRIPLE,args);
}

bool GUIManager::injectMouseMove(const int& xPixelOffset, const int& yPixelOffset)
{
// If the mouse is disabled, we do not accept this injection of input
if( !mMouseCursor->getEnabled() )
return false;

// See if we should be dragging a widget.
if( (mActiveSheet->getWidgetInFocus() == mWidgetUnderMouseCursor) && (mWidgetUnderMouseCursor->getDragable()) )
{
// Dragging, which uses move function, works with pixel values (uninfluenced by parent dimensions!)
mWidgetUnderMouseCursor->drag(xPixelOffset,yPixelOffset);

return true;
}

// Now get the widget the cursor is over.
Widget* w = mActiveSheet->findWidgetAtPoint(mMouseCursor->getPosition());

// Ignore disabled widgets
if(!w->getEnabled())
return false;

// Create MouseEventArgs, for use with any fired events
MouseEventArgs args(w);
args.position = mMouseCursor->getPosition();
args.moveDelta.x = xPixelOffset;
args.moveDelta.y = yPixelOffset;
args.buttonMask = mButtonMask;
args.keyModifiers = mKeyModifiers;

// Create a boolean to track whether or not this injection caused any significant changes.
bool changesMade = false;

// The Widget underneath the mouse cursor has changed.
if( mWidgetUnderMouseCursor != w )
{
if(mWidgetUnderMouseCursor != NULL)
{
changesMade |= mWidgetUnderMouseCursor->fireEvent(WIDGET_EVENT_MOUSE_LEAVE,args);
}

// Update pointer
mWidgetUnderMouseCursor = w;

if(mWidgetUnderMouseCursor != NULL)
{
changesMade |= mWidgetUnderMouseCursor->fireEvent(WIDGET_EVENT_MOUSE_ENTER,args);
}
}

// Notify the widget in focus the cursor has moved.
Widget* focusWidget = mActiveSheet->getWidgetInFocus();
if(focusWidget != NULL)
changesMade |= focusWidget->fireEvent(WIDGET_EVENT_MOUSE_MOVE,args);

return changesMade;
}

bool GUIManager::injectMousePosition(const int& xPixelPosition, const int& yPixelPosition)
{
// If the mouse is disabled, we do not accept this injection of input
if( !mMouseCursor->getEnabled() )
return false;

Point oldPos = mMouseCursor->getPosition();
// Update cursor's position as seen on the screen.
mMouseCursor->setPosition(xPixelPosition,yPixelPosition);

// Determine the offset and inject a mouse movement input.
return injectMouseMove(xPixelPosition - oldPos.x,yPixelPosition - oldPos.y);
}

tp

04-04-2008 06:20:28

Disclaimer: I'm not very familiar with the QuickGUI internals, I'll probably try and integrate it into my project later, but as it seems heavy development is underway, I've been focusing on other areas. I have implemented a GUI system of my own previously, though, and I do know that this particular issue really can mean the difference between a great UI system and a bad UI system. I'd like to encourage you to stick with this aspect of the system until it shines :). It's also why I really don't want to make one myself again...

Honestly the


normal idea of "if you click it, it gains focus"


is just a very bad idea IMHO.


I couldn't agree more. I think the best way for a generic library such as QuickGUI would be the separation of keyboard and mouse as much as possible. Having the logic for keyboard focus in the mouse events seems somewhat like a lost cause to me.

Why not implement it in the controls that use the keyboard? You click on a text box, the text box mouse button down event gets fired, and the text box tells the GUI system that it wants to obtain the keyboard focus. In the WoW example, the button bar doesn't do this, so nothing happens to the focus. This would also flexibly allow the control itself to determine when it's done with the keyboard (pressing esc etc.)

Having to tell what a control is NOT supposed to do (like what you presented for the WoW case) is a real pain in the buttocks if you want to build custom controls. Not to mention that the MOUSE_ENTER thing seems quite kludgy to me :twisted:.

Regarding modal windows, the experience from my previous project tells me that a good idea would be a list of modals. You only ever deal with the topmost one (e.g. last in list), but when it closes, you end up with the next one. Regarding input handling, you probably need to stick a special case in there just for modals in order to pull it off properly.

Just my 0.02€...

kungfoomasta

04-04-2008 07:31:24

Why not implement it in the controls that use the keyboard? You click on a text box, the text box mouse button down event gets fired, and the text box tells the GUI system that it wants to obtain the keyboard focus. In the WoW example, the button bar doesn't do this, so nothing happens to the focus. This would also flexibly allow the control itself to determine when it's done with the keyboard (pressing esc etc.)

This sounds like a decent idea, but what if I was a user and wanted to make a window, such that when its in focus and 'c' is pressed, the window closes? Or any other odd behavior I would want in my game UI. How do I know where to send keyboard input? I could store the last text based widget that was focused, and send all keyboard input to it, but it doesn't allow the window scenario above, or things like pressing a spacebar to hit a button.

The list of modal windows sound good to me. There are some more things I need to develop before I get to working this into the input, but its good to think about it in the background. :)

tp

04-04-2008 08:55:55

This sounds like a decent idea, but what if I was a user and wanted to make a window, such that when its in focus and 'c' is pressed, the window closes? Or any other odd behavior I would want in my game UI. How do I know where to send keyboard input? I could store the last text based widget that was focused, and send all keyboard input to it, but it doesn't allow the window scenario above, or things like pressing a spacebar to hit a button.

Closing the window with 'c' would only work if you've previously clicked on the window or a non-text control in it, I guess. Basically, it would probably require you to keep separate track of the focused window as well as the focused control. If the focused window has a focused text control, offer the event there first, otherwise give it to the window. I'm assuming that's how most OS window managers do it.

Child windows containing text controls that are themselves controls in a window might need more work, though, but I'm not sure if that's usually relevant in the scope of a game UI. One example that comes to mind would be a quest log where you have a scrollable subwindow containing the quest text and details, surrounded by the actual quest log controls. Even then it's usually sufficient to give the key events to either the lowest level text field control, or the top level window.

Spacebar hitting a button requires the control to acquire keyboard focus. The WoW toolbars don't do this, and it would simply be a separate control type.

Zini

05-04-2008 11:19:41

Before I go into details I would like to point out, that I could live with Kungfoomasta's idea about the onMouseButtonUp thing. It would have some disadvantages (like the keyboard input being taken away from the original window while the button is pressed and I don't really want to think about drags in this situation). But it seems that changing to the model(s), that tp and me are proposing would mean a lot of extra work, so I could understand if you want to stick with your original design.

However if you want to change it, you should go all the way and make it complete. I think I proposed that a few months ago, but the only real solution here is to re-think, what focus means. How about this:

The GUI has only one type of focus, the keyboard-focus. When you click on a widget, it can either grab the keyboard-focus or do not change the keyword-focus at all. This should be configured by the widget type, with an additional option to override this configuration manually for maximum flexibility.

If a widget has keyboard-focus and receives keyboard-input, it should hand over the input to its parent (if it can't make use of the input by itself).
I agree with tp, that in most cases when receiving keyboard-input the bottom-most and the top-most widget would be enough. But again for maximum flexibility the input should be passed through all the widget from the input-receiving widget to the top-most parent (maybe except the sheet).

Now the mouse-input: The widget affected by the mouse is the widget under the mouse pointer. There is really no point in keeping a window focus for the mouse. Regarding the mouse-input all windows should be equal.
From your previous comments I conclude that some parts of the current implementation need a mouse-focus, but there is no conceptual reason for it. Therefore the implementation should be changed, IMHO.

Note that you can retain the old (Windoze-style) behaviour, if you simply set all widgets to always take the keyboard-focus.

Well, that got a bit more lengthy than I expected. Especially for a Saturday morning. Hope you can still make some use of it.

kungfoomasta

05-04-2008 21:10:05

The focusWindow function's only purpose is to bring the window to the front, and fire off events related to gaining and losing focus. This would enable effects like making the window fade in/out.

I have used your input to make some changes, it sounds reasonable to me. I do keep track of Windows that have "focus", that is, the window that is on top. Widgets however do not have a sense of focus anymore. I have added a member "consumesKeyboardEvents", and added a get/set function for this. The Sheet class has a "setKeyboardListener" and "getKeyboardListener" class, which is used to inject keyboard events. I will add in support for propogation of events like before, so it will be user configurable, and if you wanted a keyboard event passed up to the parent it would be your call. I haven't added this into the new code base yet.

Also when I was making these changes, I realized that I will need to store the last widget that was clicked on, for the purpose of the mouse wheel input. For example if you clicked on window w1, and moused over window w2, and scrolled the mouse wheel, w1 would receive the input. I don't want to require users to have to mouse over the console (for example) in order to scroll the text up or down.

Zini

05-04-2008 21:29:16

Sounds reasonable. I am looking forward to see the new code. But no need to hurry on my account. Currently I am standing knee-deep in sticky AI-stuff and I won't be able to port to / test a new QuickGUI version until I have sorted it out.


Also when I was making these changes, I realized that I will need to store the last widget that was clicked on, for the purpose of the mouse wheel input. For example if you clicked on window w1, and moused over window w2, and scrolled the mouse wheel, w1 would receive the input.


Any chance to have an option to turn this off? Personally I find this behaviour extremely counter-intuitive and frustrating.

kungfoomasta

05-04-2008 22:11:37

I can post the code later today or tomorrow for anybody who might want to see what I'm doing. I did add in the ability to configure the scrolling, so that you can decide if the last clicked widget gets wheel events, or the widget under the cursor gets it.

The revision of the Input injection system is just one of the many things I need to implement before letting people use the new code. I'm also revising the rendering and how QuickGUI class instances are created, and it will be a wihle longer before its considered usable.

Zini

05-04-2008 22:55:35


I can post the code later today or tomorrow for anybody who might want to see what I'm doing. I did add in the ability to configure the scrolling, so that you can decide if the last clicked widget gets wheel events, or the widget under the cursor gets it.


As I said, no need to hurry.


The revision of the Input injection system is just one of the many things I need to implement before letting people use the new code. I'm also revising the rendering and how QuickGUI class instances are created, and it will be a wihle longer before its considered usable.


Thanks.