X-Git-Url: http://git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fmenu%2Fitem%2Flistbox.qc;h=9210e14b384c90e262ece0be368b87e397cd1178;hb=03f978544a8b13a18cef1c7cc3dbcaba1c3aee4c;hp=178b12b9a047ea3645cb89ca8fa09326ddb692f2;hpb=530e06120a7b41f6175b1144fedf6903da8c13b0;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/menu/item/listbox.qc b/qcsrc/menu/item/listbox.qc index 178b12b9a..9210e14b3 100644 --- a/qcsrc/menu/item/listbox.qc +++ b/qcsrc/menu/item/listbox.qc @@ -1,29 +1,40 @@ -#ifdef INTERFACE -CLASS(ListBox) EXTENDS(Item) - METHOD(ListBox, resizeNotify, void(entity, vector, vector, vector, vector)) - METHOD(ListBox, configureListBox, void(entity, float, float)) - METHOD(ListBox, draw, void(entity)) - METHOD(ListBox, keyDown, float(entity, float, float, float)) - METHOD(ListBox, mousePress, float(entity, vector)) - METHOD(ListBox, mouseDrag, float(entity, vector)) - METHOD(ListBox, mouseRelease, float(entity, vector)) - METHOD(ListBox, focusLeave, void(entity)) +#ifndef ITEM_LISTBOX_H +#define ITEM_LISTBOX_H +#include "../item.qc" +CLASS(ListBox, Item) + METHOD(ListBox, resizeNotify, void(entity, vector, vector, vector, vector)); + METHOD(ListBox, configureListBox, void(entity, float, float)); + METHOD(ListBox, draw, void(entity)); + METHOD(ListBox, keyDown, float(entity, float, float, float)); + METHOD(ListBox, mouseMove, float(entity, vector)); + METHOD(ListBox, mousePress, float(entity, vector)); + METHOD(ListBox, mouseDrag, float(entity, vector)); + METHOD(ListBox, mouseRelease, float(entity, vector)); + METHOD(ListBox, focusLeave, void(entity)); ATTRIB(ListBox, focusable, float, 1) + ATTRIB(ListBox, focusedItem, int, -1) + ATTRIB(ListBox, focusedItemAlpha, float, 0.3) + METHOD(ListBox, setFocusedItem, void(entity, int)); + ATTRIB(ListBox, mouseMoveOffset, float, -1) // let know where the cursor is when the list scrolls without moving the cursor ATTRIB(ListBox, allowFocusSound, float, 1) - ATTRIB(ListBox, selectedItem, float, 0) + ATTRIB(ListBox, selectedItem, int, 0) ATTRIB(ListBox, size, vector, '0 0 0') ATTRIB(ListBox, origin, vector, '0 0 0') ATTRIB(ListBox, scrollPos, float, 0) // measured in window heights, fixed when needed + ATTRIB(ListBox, scrollPosTarget, float, 0) + METHOD(ListBox, isScrolling, bool(entity)); + ATTRIB(ListBox, needScrollToItem, float, -1) + METHOD(ListBox, scrollToItem, void(entity, int)); ATTRIB(ListBox, previousValue, float, 0) ATTRIB(ListBox, pressed, float, 0) // 0 = normal, 1 = scrollbar dragging, 2 = item dragging, 3 = released ATTRIB(ListBox, pressOffset, float, 0) - METHOD(ListBox, updateControlTopBottom, void(entity)) + METHOD(ListBox, updateControlTopBottom, void(entity)); ATTRIB(ListBox, controlTop, float, 0) ATTRIB(ListBox, controlBottom, float, 0) ATTRIB(ListBox, controlWidth, float, 0) - ATTRIB(ListBox, dragScrollTimer, float, 0) ATTRIB(ListBox, dragScrollPos, vector, '0 0 0') + ATTRIB(ListBox, selectionDoesntMatter, bool, false) // improves scrolling by keys for lists that don't need to show an active selection ATTRIB(ListBox, src, string, string_null) // scrollbar ATTRIB(ListBox, color, vector, '1 1 1') @@ -40,19 +51,20 @@ CLASS(ListBox) EXTENDS(Item) ATTRIB(ListBox, lastClickedItem, float, -1) ATTRIB(ListBox, lastClickedTime, float, 0) - METHOD(ListBox, drawListBoxItem, void(entity, float, vector, float)) // item number, width/height, selected - METHOD(ListBox, clickListBoxItem, void(entity, float, vector)) // item number, relative clickpos - METHOD(ListBox, doubleClickListBoxItem, void(entity, float, vector)) // item number, relative clickpos - METHOD(ListBox, setSelected, void(entity, float)) + METHOD(ListBox, drawListBoxItem, void(entity, int, vector, bool, bool)); // item number, width/height, isSelected, isFocused + METHOD(ListBox, clickListBoxItem, void(entity, float, vector)); // item number, relative clickpos + METHOD(ListBox, doubleClickListBoxItem, void(entity, float, vector)); // item number, relative clickpos + METHOD(ListBox, setSelected, void(entity, float)); + METHOD(ListBox, focusedItemChangeNotify, void(entity)); - METHOD(ListBox, getLastFullyVisibleItemAtScrollPos, float(entity, float)) - METHOD(ListBox, getFirstFullyVisibleItemAtScrollPos, float(entity, float)) + METHOD(ListBox, getLastFullyVisibleItemAtScrollPos, float(entity, float)); + METHOD(ListBox, getFirstFullyVisibleItemAtScrollPos, float(entity, float)); // NOTE: override these four methods if you want variable sized list items - METHOD(ListBox, getTotalHeight, float(entity)) - METHOD(ListBox, getItemAtPos, float(entity, float)) - METHOD(ListBox, getItemStart, float(entity, float)) - METHOD(ListBox, getItemHeight, float(entity, float)) + METHOD(ListBox, getTotalHeight, float(entity)); + METHOD(ListBox, getItemAtPos, float(entity, float)); + METHOD(ListBox, getItemStart, float(entity, float)); + METHOD(ListBox, getItemHeight, float(entity, float)); // NOTE: if getItemAt* are overridden, it may make sense to cache the // start and height of the last item returned by getItemAtPos and fast // track returning their properties for getItemStart and getItemHeight. @@ -75,9 +87,45 @@ ENDCLASS(ListBox) #endif #ifdef IMPLEMENTATION +bool ListBox_isScrolling(entity me) +{ + return (me.scrollPos != me.scrollPosTarget); +} + +void ListBox_scrollToItem(entity me, int i) +{ + // scroll doesn't work properly until itemHeight is set to the correct value + // at the first resizeNotify call + if(me.itemHeight == 1) // initial temporary value of itemHeight is 1 + { + me.needScrollToItem = i; + return; + } + + i = bound(0, i, me.nItems - 1); + + // scroll the list to make sure the selected item is visible + // (even if the selected item doesn't change). + if(i < me.getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos)) + { + // above visible area + me.scrollPosTarget = me.getItemStart(me, i); + } + else if(i > me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos)) + { + // below visible area + if(i == me.nItems - 1) + me.scrollPosTarget = me.getTotalHeight(me) - 1; + else + me.scrollPosTarget = me.getItemStart(me, i + 1) - 1; + } +} + void ListBox_setSelected(entity me, float i) { - me.selectedItem = bound(0, i, me.nItems - 1); + i = bound(0, i, me.nItems - 1); + me.scrollToItem(me, i); + me.selectedItem = i; } void ListBox_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize) { @@ -109,27 +157,30 @@ float ListBox_getItemHeight(entity me, float i) float ListBox_getLastFullyVisibleItemAtScrollPos(entity me, float pos) { - return me.getItemAtPos(me, pos + 1.001) - 1; + return me.getItemAtPos(me, pos + 0.999) - 1; } float ListBox_getFirstFullyVisibleItemAtScrollPos(entity me, float pos) { - return me.getItemAtPos(me, pos - 0.001) + 1; + return me.getItemAtPos(me, pos + 0.001) + 1; } float ListBox_keyDown(entity me, float key, float ascii, float shift) { - me.dragScrollTimer = time; if(key == K_MWHEELUP) { - me.scrollPos = max(me.scrollPos - 0.5, 0); - me.setSelected(me, min(me.selectedItem, me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos))); + me.scrollPosTarget = max(me.scrollPosTarget - 0.5, 0); } else if(key == K_MWHEELDOWN) { - me.scrollPos = min(me.scrollPos + 0.5, me.getTotalHeight(me) - 1); - me.setSelected(me, max(me.selectedItem, me.getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos))); + me.scrollPosTarget = min(me.scrollPosTarget + 0.5, max(0, me.getTotalHeight(me) - 1)); } else if(key == K_PGUP || key == K_KP_PGUP) { + if(me.selectionDoesntMatter) + { + me.scrollPosTarget = max(me.scrollPosTarget - 0.5, 0); + return 1; + } + float i = me.selectedItem; float a = me.getItemHeight(me, i); for (;;) @@ -145,6 +196,12 @@ float ListBox_keyDown(entity me, float key, float ascii, float shift) } else if(key == K_PGDN || key == K_KP_PGDN) { + if(me.selectionDoesntMatter) + { + me.scrollPosTarget = min(me.scrollPosTarget + 0.5, me.nItems * me.itemHeight - 1); + return 1; + } + float i = me.selectedItem; float a = me.getItemHeight(me, i); for (;;) @@ -159,27 +216,52 @@ float ListBox_keyDown(entity me, float key, float ascii, float shift) me.setSelected(me, i - 1); } else if(key == K_UPARROW || key == K_KP_UPARROW) + { + if(me.selectionDoesntMatter) + { + me.scrollPosTarget = max(me.scrollPosTarget - me.itemHeight, 0); + return 1; + } + me.setSelected(me, me.selectedItem - 1); + } else if(key == K_DOWNARROW || key == K_KP_DOWNARROW) + { + if(me.selectionDoesntMatter) + { + me.scrollPosTarget = min(me.scrollPosTarget + me.itemHeight, me.nItems * me.itemHeight - 1); + return 1; + } + me.setSelected(me, me.selectedItem + 1); + } else if(key == K_HOME || key == K_KP_HOME) - { - me.scrollPos = 0; me.setSelected(me, 0); - } else if(key == K_END || key == K_KP_END) - { - me.scrollPos = max(0, me.getTotalHeight(me) - 1); me.setSelected(me, me.nItems - 1); - } else return 0; return 1; } +float ListBox_mouseMove(entity me, vector pos) +{ + me.mouseMoveOffset = -1; + if(pos_x < 0) return 0; + if(pos_y < 0) return 0; + if(pos_x >= 1) return 0; + if(pos_y >= 1) return 0; + if(pos_x < 1 - me.controlWidth) + me.mouseMoveOffset = pos.y; + else + { + me.setFocusedItem(me, -1); + me.mouseMoveOffset = -1; + } + return 1; +} float ListBox_mouseDrag(entity me, vector pos) { float hit; - float i; me.updateControlTopBottom(me); me.dragScrollPos = pos; if(me.pressed == 1) @@ -194,19 +276,18 @@ float ListBox_mouseDrag(entity me, vector pos) // calculate new pos to v float d; d = (pos.y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.getTotalHeight(me) - 1); - me.scrollPos = me.previousValue + d; + me.scrollPosTarget = me.previousValue + d; } else - me.scrollPos = me.previousValue; - me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1); - me.scrollPos = max(me.scrollPos, 0); - i = min(me.selectedItem, me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos)); - i = max(i, ListBox_getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos)); - me.setSelected(me, i); + me.scrollPosTarget = me.previousValue; + me.scrollPosTarget = min(me.scrollPosTarget, me.getTotalHeight(me) - 1); + me.scrollPosTarget = max(me.scrollPosTarget, 0); } else if(me.pressed == 2) { me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y)); + me.setFocusedItem(me, me.selectedItem); + me.mouseMoveOffset = -1; } return 1; } @@ -218,21 +299,18 @@ float ListBox_mousePress(entity me, vector pos) if(pos.y >= 1) return 0; me.dragScrollPos = pos; me.updateControlTopBottom(me); - me.dragScrollTimer = time; if(pos.x >= 1 - me.controlWidth) { // if hit, set me.pressed, otherwise scroll by one page if(pos.y < me.controlTop) { // page up - me.scrollPos = max(me.scrollPos - 1, 0); - me.setSelected(me, min(me.selectedItem, ListBox_getLastFullyVisibleItemAtScrollPos(me, me.scrollPos))); + me.scrollPosTarget = max(me.scrollPosTarget - 1, 0); } else if(pos.y > me.controlBottom) { // page down - me.scrollPos = min(me.scrollPos + 1, me.getTotalHeight(me) - 1); - me.setSelected(me, max(me.selectedItem, ListBox_getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos))); + me.scrollPosTarget = min(me.scrollPosTarget + 1, me.getTotalHeight(me) - 1); } else { @@ -247,9 +325,21 @@ float ListBox_mousePress(entity me, vector pos) me.pressed = 2; // an item has been clicked. Select it, ... me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y)); + me.setFocusedItem(me, me.selectedItem); } return 1; } +void ListBox_setFocusedItem(entity me, int item) +{ + float focusedItem_save = me.focusedItem; + me.focusedItem = (item < me.nItems) ? item : -1; + if(focusedItem_save != me.focusedItem) + { + me.focusedItemChangeNotify(me); + if(me.focusedItem >= 0) + me.focusedItemAlpha = SKINALPHA_LISTBOX_FOCUSED; + } +} float ListBox_mouseRelease(entity me, vector pos) { if(me.pressed == 1) @@ -263,6 +353,7 @@ float ListBox_mouseRelease(entity me, vector pos) // item dragging mode // select current one one last time... me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y)); + me.setFocusedItem(me, me.selectedItem); // and give it a nice click event if(me.nItems > 0) { @@ -286,6 +377,8 @@ void ListBox_focusLeave(entity me) // by a mouse click on an item of the list // for example showing a dialog on right click me.pressed = 0; + me.setFocusedItem(me, -1); + me.mouseMoveOffset = -1; } void ListBox_updateControlTopBottom(entity me) { @@ -300,20 +393,6 @@ void ListBox_updateControlTopBottom(entity me) } else { - if(frametime) // only do this in draw frames - { - if(me.dragScrollTimer < time) - { - float save; - save = me.scrollPos; - // if selected item is below listbox, increase scrollpos so it is in - me.scrollPos = max(me.scrollPos, me.getItemStart(me, me.selectedItem) + me.getItemHeight(me, me.selectedItem) - 1); - // if selected item is above listbox, decrease scrollpos so it is in - me.scrollPos = min(me.scrollPos, me.getItemStart(me, me.selectedItem)); - if(me.scrollPos != save) - me.dragScrollTimer = time + 0.2; - } - } // if scroll pos is below end of list, fix it me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1); // if scroll pos is above beginning of list, fix it @@ -337,11 +416,36 @@ void ListBox_updateControlTopBottom(entity me) } } } +AUTOCVAR(menu_scroll_averaging_time, float, 0.16, "smooth scroll averaging time"); +// scroll faster while dragging the scrollbar +AUTOCVAR(menu_scroll_averaging_time_pressed, float, 0.06, "smooth scroll averaging time when dragging the scrollbar"); void ListBox_draw(entity me) { float i; vector absSize, fillSize = '0 0 0'; vector oldshift, oldscale; + + // we can't do this in mouseMove as the list can scroll without moving the cursor + if(me.mouseMoveOffset != -1) + me.setFocusedItem(me, me.getItemAtPos(me, me.scrollPos + me.mouseMoveOffset)); + + if(me.needScrollToItem >= 0) + { + me.scrollToItem(me, me.needScrollToItem); + me.needScrollToItem = -1; + } + if(me.scrollPos != me.scrollPosTarget) + { + float averaging_time = (me.pressed == 1) + ? autocvar_menu_scroll_averaging_time_pressed + : autocvar_menu_scroll_averaging_time; + // this formula works with whatever framerate + float f = averaging_time ? exp(-frametime / averaging_time) : 0; + me.scrollPos = me.scrollPos * f + me.scrollPosTarget * (1 - f); + if(fabs(me.scrollPos - me.scrollPosTarget) < 0.001) + me.scrollPos = me.scrollPosTarget; + } + if(me.pressed == 2) me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event me.updateControlTopBottom(me); @@ -367,6 +471,7 @@ void ListBox_draw(entity me) draw_SetClip(); oldshift = draw_shift; oldscale = draw_scale; + float y; i = me.getItemAtPos(me, me.scrollPos); y = me.getItemStart(me, i) - me.scrollPos; @@ -376,7 +481,7 @@ void ListBox_draw(entity me) vector relSize = eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, i); absSize = boxToGlobalSize(relSize, me.size); draw_scale = boxToGlobalSize(relSize, oldscale); - me.drawListBoxItem(me, i, absSize, (me.selectedItem == i)); + me.drawListBoxItem(me, i, absSize, (me.selectedItem == i), (me.focusedItem == i)); y += relSize.y; } draw_ClearClip(); @@ -386,6 +491,10 @@ void ListBox_draw(entity me) SUPER(ListBox).draw(me); } +void ListBox_focusedItemChangeNotify(entity me) +{ +} + void ListBox_clickListBoxItem(entity me, float i, vector where) { // template method @@ -396,8 +505,8 @@ void ListBox_doubleClickListBoxItem(entity me, float i, vector where) // template method } -void ListBox_drawListBoxItem(entity me, float i, vector absSize, float selected) +void ListBox_drawListBoxItem(entity me, int i, vector absSize, bool isSelected, bool isFocused) { - draw_Text('0 0 0', sprintf(_("Item %d"), i), eX * (8 / absSize.x) + eY * (8 / absSize.y), (selected ? '0 1 0' : '1 1 1'), 1, 0); + draw_Text('0 0 0', sprintf(_("Item %d"), i), eX * (8 / absSize.x) + eY * (8 / absSize.y), (isSelected ? '0 1 0' : '1 1 1'), 1, 0); } #endif