Applied patch by Denis Washington which implements "diagonal mouse

support" for menus (see ticket #284). Thanks a lot!


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@24193 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Stefano Ceccherini 2008-03-01 22:15:23 +00:00
parent 84b29b5f5f
commit dc93a6b1ee
2 changed files with 181 additions and 19 deletions

View File

@ -180,8 +180,13 @@ private:
void _Hide();
BMenuItem* _Track(int* action, long start = -1);
void _UpdateNavigationArea(BPoint position,
BRect& navAreaRectAbove, BRect& navAreaBelow);
void _UpdateStateOpenSelect(BMenuItem* item,
bigtime_t& openTime, const int32 &mouseSpeed);
BPoint position, BRect& navAreaRectAbove,
BRect& navAreaBelow, bigtime_t& selectedTime,
bigtime_t& navigationAreaTime);
void _UpdateStateClose(BMenuItem* item, const BPoint& where,
const uint32& buttons);

View File

@ -1384,6 +1384,8 @@ BMenu::_Hide()
}
const static bigtime_t kOpenSubmenuDelay = 225000;
const static bigtime_t kNavigationAreaTimeout = 1000000;
const static bigtime_t kHysteresis = 200000; // TODO: Test and reduce if needed.
const static int32 kMouseMotionThreshold = 15;
// TODO: Same as above. Actually, we could get rid of the kHysteresis
@ -1394,7 +1396,9 @@ BMenu::_Track(int *action, long start)
{
// TODO: cleanup
BMenuItem *item = NULL;
bigtime_t openTime = system_time();
BRect navAreaRectAbove, navAreaRectBelow;
bigtime_t selectedTime = system_time();
bigtime_t navigationAreaTime = 0;
fState = MENU_STATE_TRACKING;
if (fSuper != NULL)
@ -1432,6 +1436,9 @@ BMenu::_Track(int *action, long start)
bool overSub = _OverSubmenu(fSelected, screenLocation);
item = _HitTestItems(location, B_ORIGIN);
if (overSub) {
navAreaRectAbove = BRect();
navAreaRectBelow = BRect();
// Since the submenu has its own looper,
// we can unlock ours. Doing so also make sure
// that our window gets any update message to
@ -1453,7 +1460,8 @@ BMenu::_Track(int *action, long start)
if (!LockLooper())
break;
} else if (item != NULL) {
_UpdateStateOpenSelect(item, openTime, mouseSpeed);
_UpdateStateOpenSelect(item, location, navAreaRectAbove,
navAreaRectBelow, selectedTime, navigationAreaTime);
if (!releasedOnce)
releasedOnce = true;
} else if (_OverSuper(screenLocation)) {
@ -1528,26 +1536,164 @@ BMenu::_Track(int *action, long start)
void
BMenu::_UpdateStateOpenSelect(BMenuItem* item, bigtime_t& openTime,
const int32 &mouseSpeed)
BMenu::_UpdateNavigationArea(BPoint position, BRect& navAreaRectAbove,
BRect& navAreaRectBelow)
{
#define NAV_AREA_THRESHOLD 8
// The navigation area is a region in which mouse-overs won't select
// the item under the cursor. This makes it easier to navigate to
// submenus, as the cursor can be moved to submenu items directly instead
// of having to move it horizontally into the submenu first. The concept
// is illustrated below:
//
// +-------+----+---------+
// | | /| |
// | | /*| |
// |[2]--> | /**| |
// | |/[4]| |
// |------------| |
// | [1] | [6] |
// |------------| |
// | |\[5]| |
// |[3]--> | \**| |
// | | \*| |
// | | \| |
// | +----|---------+
// | |
// +------------+
//
// [1] Selected item, cursor position ('position')
// [2] Upper navigation area rectangle ('navAreaRectAbove')
// [3] Lower navigation area rectangle ('navAreaRectBelow')
// [4] Upper navigation area
// [5] Lower navigation area
// [6] Submenu
//
// The rectangles are used to calculate if the cursor is in the actual
// navigation area (see _UpdateStateOpenSelect()).
if (fSelected == NULL)
return;
BMenu *submenu = fSelected->Submenu();
if (submenu != NULL) {
BRect menuBounds = ConvertToScreen(Bounds());
fSelected->Submenu()->LockLooper();
BRect submenuBounds = fSelected->Submenu()->ConvertToScreen(
fSelected->Submenu()->Bounds());
fSelected->Submenu()->UnlockLooper();
if (menuBounds.left < submenuBounds.left) {
navAreaRectAbove.Set(position.x + NAV_AREA_THRESHOLD,
submenuBounds.top, menuBounds.right,
position.y);
navAreaRectBelow.Set(position.x + NAV_AREA_THRESHOLD,
position.y, menuBounds.right,
submenuBounds.bottom);
} else {
navAreaRectAbove.Set(menuBounds.left,
submenuBounds.top, position.x - NAV_AREA_THRESHOLD,
position.y);
navAreaRectBelow.Set(menuBounds.left,
position.y, position.x - NAV_AREA_THRESHOLD,
submenuBounds.bottom);
}
} else {
navAreaRectAbove = BRect();
navAreaRectBelow = BRect();
}
}
void
BMenu::_UpdateStateOpenSelect(BMenuItem* item, BPoint position,
BRect& navAreaRectAbove, BRect& navAreaRectBelow, bigtime_t& selectedTime,
bigtime_t& navigationAreaTime)
{
if (fState == MENU_STATE_CLOSED)
return;
if (item != fSelected) {
if (mouseSpeed < kMouseMotionThreshold) {
if (navigationAreaTime == 0)
navigationAreaTime = system_time();
position = ConvertToScreen(position);
bool inNavAreaRectAbove = navAreaRectAbove.Contains(position);
bool inNavAreaRectBelow = navAreaRectBelow.Contains(position);
if (!inNavAreaRectAbove && !inNavAreaRectBelow) {
_SelectItem(item, false);
openTime = system_time();
} else {
//printf("Mouse moving too fast (%ld), ignoring...\n", mouseSpeed);
navAreaRectAbove = BRect();
navAreaRectBelow = BRect();
selectedTime = system_time();
navigationAreaTime = 0;
return;
}
BRect menuBounds = ConvertToScreen(Bounds());
fSelected->Submenu()->LockLooper();
BRect submenuBounds = fSelected->Submenu()->ConvertToScreen(
fSelected->Submenu()->Bounds());
fSelected->Submenu()->UnlockLooper();
float x_offset;
// navAreaRectAbove and navAreaRectBelow have the same X
// position and width, so it doesn't matter which one we use to
// calculate the X offset
if (menuBounds.left < submenuBounds.left)
x_offset = position.x - navAreaRectAbove.left;
else
x_offset = navAreaRectAbove.right - position.x;
bool inNavArea;
if (inNavAreaRectAbove) {
float y_offset = navAreaRectAbove.bottom - position.y;
float ratio = navAreaRectAbove.Width() / navAreaRectAbove.Height();
inNavArea = y_offset <= x_offset / ratio;
} else {
float y_offset = navAreaRectBelow.bottom - position.y;
float ratio = navAreaRectBelow.Width() / navAreaRectBelow.Height();
inNavArea = y_offset >= (navAreaRectBelow.Height() - x_offset / ratio);
}
bigtime_t systime = system_time();
if (!inNavArea || (navigationAreaTime > 0 && systime -
navigationAreaTime > kNavigationAreaTimeout)) {
// Don't delay opening of submenu if the user had
// to wait for the navigation area timeout anyway
_SelectItem(item, inNavArea);
if (inNavArea) {
_UpdateNavigationArea(position, navAreaRectAbove,
navAreaRectBelow);
} else {
navAreaRectAbove = BRect();
navAreaRectBelow = BRect();
}
selectedTime = system_time();
navigationAreaTime = 0;
}
} else if (fSelected->Submenu() != NULL &&
system_time() - selectedTime > kOpenSubmenuDelay) {
_SelectItem(fSelected, true);
if (!navAreaRectAbove.IsValid() && !navAreaRectBelow.IsValid()) {
position = ConvertToScreen(position);
_UpdateNavigationArea(position, navAreaRectAbove, navAreaRectBelow);
}
} else if (system_time() > kHysteresis + openTime && item->Submenu() != NULL
&& item->Submenu()->Window() == NULL) {
// Open the submenu if it's not opened yet, but only if
// the mouse pointer stayed over there for some time
// (hysteresis)
_SelectItem(item);
}
if (fState != MENU_STATE_TRACKING)
fState = MENU_STATE_TRACKING;
}
@ -1561,9 +1707,14 @@ BMenu::_UpdateStateClose(BMenuItem* item, const BPoint& where,
return;
if (buttons != 0 && _IsStickyMode()) {
if (item == NULL)
if (item == NULL) {
if (item != fSelected) {
LockLooper();
_SelectItem(item, false);
UnlockLooper();
}
fState = MENU_STATE_CLOSED;
else
} else
_SetStickyMode(false);
} else if (buttons == 0 && !_IsStickyMode()) {
if (fExtraRect != NULL && fExtraRect->Contains(where)) {
@ -1571,8 +1722,14 @@ BMenu::_UpdateStateClose(BMenuItem* item, const BPoint& where,
fExtraRect = NULL;
// Setting this to NULL will prevent this code
// to be executed next time
} else
} else {
if (item != fSelected) {
LockLooper();
_SelectItem(item, false);
UnlockLooper();
}
fState = MENU_STATE_CLOSED;
}
}
}