double click

Alexpi

03-11-2007 20:29:30

im doing a inventory with quickgui 0.9.6.

is there any difference betwen Image and Panel? now, im using both but... i cant see the difference :P.

Other question. How can i detect double click? and EVENT_MOUSE_CLICK detect a click if it is make very fast, is possible to change it to slower clicks? :P

kungfoomasta

03-11-2007 21:16:34

Panels can create a lot of child widgets, and Images can't. Also, Panels support scrolling.

Clicking needs some tweaking and user-supported customization like the time to recognize a click. Double Clicking isn't yet implemented. I plan to update/add these in, but not anytime soon. Are these really important to your project, or can you work around it for the time being? :)

Alexpi

04-11-2007 00:57:31

at the moment, isnt very important...i think that i can wait :P but.. how many time? 2 weeks, 2 months.... 2 years? xD

kungfoomasta

04-11-2007 01:21:03

Probably in the months range, maybe sooner. I need to look up how to implement double clicking. For example, when you single click, should you also fire the mouse button down/up events? And likewise, when you double click, should you get 2 mouse button down/up events, and 2 clicks? I'll probably peek at existing solutions to find what works.

Alexpi

04-11-2007 10:58:28

For example, when you single click, should you also fire the mouse button down/up events? And likewise, when you double click, should you get 2 mouse button down/up events

with a metod that return the timeLastEvent, i mean, in event down, this metod return the time elapsed since the last event down.

then, using this metod the users can to know if happened a click (time returned by metod is very low) and then they can ignored the down event.

and 2 clicks?

Well, i think that the best solution is get 1 click event, and if there are another click, get 1 double click event, but not other click event.

kungfoomasta

09-11-2007 20:14:00

I am planning to tackle this problem over the weekend, if time allows. My plan is to have users inject mouse down and up events like normal, but checking for mouse_down, mouse_click, or mouse_double_click will be performed in the injectTime method. Think of the injectTime method as an update function, that should be called every frame.

I will make click and double click times configurable. (I try to make everything configurable :wink: ) This should be a great addition to QuickGUI.

On a somewhat unrelated note, I haven't forgotten about modifier keys and numpad not injecting correctly, I will address these when I can. :wink:

kungfoomasta

09-11-2007 21:38:26

Double clicking is actually a complex problem, depending on your requirements. In my case, I don't want to send the mouse_down, mouse_click, and mouse_double_click at once, I want the events to be exclusive.

I looked at RBGUI, which appears non-exclusive. Didn't look at CEGUI, it would probably be hard to find anyway. Here is my current logic, after some thinking about it.


InjectTime function:

if useMouseTimer
mouseTimer += timeElapsed

if(mouseTimer > doubleClickTime)
handleEvent(stack) // handle all events on stack!
// can be strange combinations like: <[click][mouse_down] top>
stack.clear
mouseTimer = 0
useMouseTimer = false

InjectMouseButtonDown function:

if stack.empty
push MOUSE_BUTTON_DOWN
useMouseTimer = true
mouseTimer = 0
return

if stack.top == MOUSE_BUTTON_CLICK && mouseClickWidget != mouseDownWidget
return

if stack.top == MOUSE_BUTTON_DOUBLE_CLICK
return

push MOUSE_BUTTON_DOWN

InjectMouseButtonUp function:

if mouseUpWidget != mouseDownWidget
return

if stack.empty
push MOUSE_BUTTON_UP
useMouseTimer = true
mouseTimer = 0
else // stack.front must be MOUSE_BUTTON_DOWN
pop
push MOUSE_BUTTON_CLICK
if top and top + 1 are MOUSE_BUTTON_CLICK
pop
pop
push MOUSE_BUTTON_DOUBLE_CLICK


The one limitation I have is that mouse_down, mouse_up, and mouse_click will not register until doubleClickTime time has elapsed. Fortunately, the time for double clicks should be really short. (maybe 500ms?)

If anybody sees problems, or can think up a better solution, please post them here.

Zini

10-11-2007 09:56:15

If am not sure, if I understand the purpose of this part:


if stack.empty
push MOUSE_BUTTON_UP
useMouseTimer = true
mouseTimer = 0


(taken from InjectMouseButtonUp)

And how are drag-operations are fitting in? It looks like your are throwing away mouse operations, after a double click time-out happens, no matter what kind of operation it was. Or are drags handled completely separate?

You might also want to take the mouse position into consideration. Moving the mouse too much while double-clicking usually cancels the operation.

Regarding the 500ms delay: I think that is slightly too short for a double click (from experience I would say that something around 700-800 ms allows a more comfortable mouse usage)
On the other hand with heavy-duty GUI usage, a 500ms delay at single clicks might already be long enough to be noticeable.
Sorry, I can't offer you any advice here, because I have never worked with a GUI system, that does not produce a single click event prior to a double click event.

kungfoomasta

10-11-2007 20:59:27

The reason for that code is that I have to be careful in deciding when to start using the mouse timer to check for mouse actions. Single and Double click are a result of a sequence of mouse actions that must occur within a time frame. If I adjust the time frame, some actions won't be registered properly. Basically, I try to start the timer when the first mouse up or mouse down occurs, in a possible sequence of events.

I don't believe dragging will be affected. I plan to take the functionality out of injectMouseButtonDown and injectMouseButtonUp and implement _handleMouseDown, _handleMouseUp, _handleMouseClick, _handleMouseDoubleClick. For dragging, the mouse down action is taken, and the widget is *grabbed*, if it can be grabbed. When mouse move occurs, I check if I have grabbed a widget, and if the widget can be dragged, drag the widget.

I wasn't aware that its acceptable to have single click registered before a double click. We'll see how i progress in implementing this.. maybe I'll have to settle for something like that. Thanks for the comment! :)

Zini

11-11-2007 08:44:04

Basically, I try to start the timer when the first mouse up or mouse down occurs, in a possible sequence of events.
)


That is exactly the point. What is the time between a double click? The time between first down and second up? Or the time between first up and second down? (I would go for the first version)
Anyway, when you start the timer it is either at first down (not relevant for the given piece of pseudo-code) or it is first up and in this case the stack should already contain a MOUSE_BUTTON_DOWN. Looks to me like you are trying to mix the two different variations.

kungfoomasta

11-11-2007 08:52:38

After doubleClickTime has elapsed, the stack of events will be cleared. So if you mouse down on something, dragged it somewhere, or just held it down for a period of time longer than doubleClickTime, and then later released the mouse button, the mouse up would be the only event on the stack.

Hopefully this scenario outlines my thinking:

Events:
1. mouse_down - event is added to stack, timer starts
2. doubleClickTime has elapsed - timer ends, the mouse_down event is handled, and the event stack is cleared.
3. mouse_up - event added to stack, timer starts
...

This is going for the first down till second up approach. I hope to start working on this within the next 24 hours.

Zini

11-11-2007 09:24:19

I can follow your argumentation. I still think that it is not required to start the timer in this case, because this specific up event can always be handled instantly,

(You say you are going for the first down-second up approach. That means if the time between first-down and second-up exceeds the double click duration, then it won't be a double click.

So, if after the first down too much time elapses and the stack is empty, this down event and the following up event shouldn't be handled as a part of a double click, since the user is already above the allowed time limit. There is no situation, where you have to wait before handling the up event).

But it doesn't hurt to wait either and it should result in cleaner, more uniform handling of up/down events, so I am fine with it.

Zini

11-11-2007 09:29:17

On a related topic: We already talked about key modifiers (shift/ctrl/alt) for key input. What about key modifiers for mouse input? A simple double-click should be distinguishable from a shift-double-click.

kungfoomasta

11-11-2007 19:06:06

Hey Zini!

I can follow your argumentation. I still think that it is not required to start the timer in this case, because this specific up event can always be handled instantly,

Last night after I posted I thought about this, and came to the same conclusion, just not as fast as you did. :P I should just handle the mouse_up instantly, since it can't contribute to any known combinations.

On a related topic: We already talked about key modifiers (shift/ctrl/alt) for key input. What about key modifiers for mouse input? A simple double-click should be distinguishable from a shift-double-click.

Another good point, I'll include the key modifiers in with the KeyEventArgs and MouseEventArgs structure.

kungfoomasta

12-11-2007 03:33:11

One issue I ran into is how to determine if the injection was handled by the GUI. Since I won't know until mDoubleClickTime has elapsed, I'm not sure what to report.

bool injectMouseButtonDown(...);

I added a function

bool Widget::getNumberOfHandlers(Event e);

but a mouse_down can be 1 of 3 events: mouse_down, mouse_click, mouse_double_click.

Any ideas for this?

kungfoomasta

12-11-2007 08:02:27

I checked out CEGUI for some ideas:


//
// Handling for multi-click generation
//
MouseClickTracker& tkr = d_clickTrackerPimpl->click_trackers[button];

tkr.d_click_count++;

// if multi-click requirements are not met
if ((tkr.d_timer.elapsed() > d_dblclick_timeout) ||
(!tkr.d_click_area.isPointInRect(ma.position)) ||
(tkr.d_target_window != dest_window) ||
(tkr.d_click_count > 3))
{
// reset to single down event.
tkr.d_click_count = 1;

// build new allowable area for multi-clicks
tkr.d_click_area.setPosition(ma.position);
tkr.d_click_area.setSize(d_dblclick_size);
tkr.d_click_area.offset(Point(-(d_dblclick_size.d_width / 2), -(d_dblclick_size.d_height / 2)));

// set target window for click events on this tracker
tkr.d_target_window = dest_window;
}

// set click count in the event args
ma.clickCount = tkr.d_click_count;

// loop backwards until event is handled or we run out of windows.
while ((!ma.handled) && (dest_window != 0))
{
ma.window = dest_window;

if (dest_window->wantsMultiClickEvents())
{
switch (tkr.d_click_count)
{
case 1:
dest_window->onMouseButtonDown(ma);
break;

case 2:
dest_window->onMouseDoubleClicked(ma);
break;

case 3:
dest_window->onMouseTripleClicked(ma);
break;
}
}
// current target window does not want multi-clicks,
// so just send a mouse down event instead.
else
{
dest_window->onMouseButtonDown(ma);
}

dest_window = getNextTargetWindow(dest_window);
}

// reset timer for this tracker.
tkr.d_timer.restart();


They don't rely on injecting time, instead they use their own timers, which is something I might consider. The main problem I see is that this approach once again is non-exclusive. CEGUI supports up to triple click, so if you mouse_down on a widget 3 times times, it will receive the following events:

mouse_down
mouse_click
mouse_double_click
mouse_triple_click

It's also worthwhile to note they reset the timer after ever click.

Is a non-exclusive method hard to achieve in an elegant fashion? It seems like the above behavior would limit having unique event handlers for click/double click/triple click.

My main problem right now is that I don't know how to tell the user that a single mouse_down injection will be handled, since this injection and be result in 1 of 3 different events.

If anybody has some ideas please post them.. maybe something will pop into my head after some more thought.

Zini

12-11-2007 09:29:04

I would go for a more relaxed approach. Don't tell the user, if the mouse click was handled. Tell him, if it may get handled, that is, if the mouse is over a widget that is able to receive mouse clicks.
If the user clicks on an interactive widget, the GUI should be the only part of the program interested in the input anyway, so no harm is done, if it always keeps the clicks.
I don't remember, if that is already implemented. If not you might want to think about adding a flag to the widgets, which makes them "immune" to mouse input, so it is easier to decide, what is only a passive HUD element (like points display or HP-bar) and what is a part of the real GUI.

kungfoomasta

12-11-2007 17:21:08

Awesome, thanks for the input! I was tempted to give in and follow the CEGUI route, it's actually not so bad, at least from the double/triple click behavior I've seen on Windows. :P (Single click to select an icon, double click to open it. So if you double click, you select and open. Likewise for text, triple click will position cursor, highligh word, then highlight row)

When mouse_down occurs, if the cursor is over a widget that has any defined mouse_down, mouse_click, or mouse_double_click handlers, true will be returned. I am making the assumption that clicking over a widget with clicking related handlers will be treated as a consumed event by the GUI.

I guess I should add in support for triple click, since I've seen it used, other libraries implement it, and I could find a use for it personally. Instead of a double_click timer, i will have a click_timer. Every time the mouse button goes down, you have X more time to make another click. And this quickens the response from 700ms (for example!) to 350ms.

hotdot

26-11-2007 04:26:31

Can we manually inject all events, for keyboard, mouse and gamepads inputs ? As exemple, in our framework, we have a manager which handles all inputs (mouse, keyb, game pads) in buffered mode and sends messages to be catched to another manager which is dedicated to events. This enables any objects derived from EventListener interface to register itself to the event manager to ear key, mouse and gamepad events. Our input manager decides when a double click has been generated and we think it would be great to have the ability to invoke for example QuickGUI::InjectMouseDoubleClick(OIS::Event) or other input injections...

So is this the case right now ? if not it probably should, because serious dev will not stick to the example application of Ogre.

kungfoomasta

26-11-2007 05:03:12

The behavior is not related to Ogre, its more a question of:

1. Should I determine a double click on my own and inject it into QuickGUI? OR
2. Should I inject mouse up/down events into QuickGUI, and it will determine wether or not its a double/triple click, etc.?

In your case, its still doable to inject mouse down/up events, and have everything work correctly. If there is a "injectDoubleClick", would you expect the injectMouseButtonDown/Up methods to not generate a double click event? I think injecting mouse up/down events should be sufficient, and it won't require the user to determine single/double click events on their own.

Regarding the gamepads, I use Wii and RumblePad2 in my own projects, but they are simulating a mouse, ie left/right click. Are you saying you want to support pressing "start", "select", and other buttons while interacting with UI widgets?

hotdot

26-11-2007 15:21:16

Wel if you inject events yourself, it would have to only do what you just injected, mainly for the reason that : if i know its a single click, i send a single click to the ui and expect it to be a single click, if my system detects a double click then double click is the result i want. So there should be only the result of the injection method. For the start and select etc... a very good layer of keymapping would be very nice indeed, if the gui system could invoke a customaizable keymaping interface, i guess it could be a big advantage. The way i see it would be to make an interface just like in unreal or quake, it could also map to callbacks. if foro instance you do a mouse sensitivity widget it would be good to put a callback so that the input class can get its mouse sensitivity modified. I could probably do a Visio schema of it and send it to you directly via email.

kungfoomasta

26-11-2007 18:36:06

My only issue is that requiring users to figure out their own single/double/triple click events and injecting them would lower the usability of the library. If you wiki'd your code in determining single/double/triple clicks, it wouldn't be so bad to require injection of these events. Also, is your code exclusive? Do you detect a double click before a triple click? (not desired!)

I've played unreal, but it was a while ago, I'm unfamiliar with the interface and how it deals with input. If you could provide a specific scenario of a gamepad, input, and how it interacts with a specific UI widget, it would help me see what you're trying to accomplish. :)

kungfoomasta

26-11-2007 19:59:10

After fixing console issues I plan on starting on single/double/triple click. Here is my approach:


public: void GUIManager::injectMouseButtonDown(MouseButtonID id);
protected: void GUIManager::doMouseButtonDown(MouseButtonID id);
protected: void GUIManager::doMouseClick(MouseButtonID id);
protected: void GUIManager::doMouseDoubleClick(MouseButtonID id);
protected: void GUIManager::doMouseTripleClick(MouseButtonID id);


I will add the following default events:

EVENT_MOUSE_DOUBLE_CLICK
EVENT_MOUSE_TRIPLE_CLICK

And if this would be helpful to you, I could add this:


public: void GUIManager::injectJoyButtonDown(int buttonID);
public: void GUIManager::injectJoyButtonUp(int buttonID);


EVENT_JOY_BUTTON_DOWN
EVENT_JOY_BUTTON_UP

I hope this is acceptable.