]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/menu/item/listbox.qc
Merge branch 'master' into terencehill/menu_gametype_tooltips_2
[xonotic/xonotic-data.pk3dir.git] / qcsrc / menu / item / listbox.qc
index 5f2e03b2463ebfe94b2edd96e768980ee398f0a3..9210e14b384c90e262ece0be368b87e397cd1178 100644 (file)
@@ -2,34 +2,39 @@
 #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))
+       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)
-       ATTRIB(ListBox, focusedItemPos, vector, '0 0 0')
+       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, 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, 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')
@@ -46,19 +51,20 @@ CLASS(ListBox, Item)
        ATTRIB(ListBox, lastClickedItem, float, -1)
        ATTRIB(ListBox, lastClickedTime, float, 0)
 
-       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, 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.
@@ -81,8 +87,21 @@ ENDCLASS(ListBox)
 #endif
 
 #ifdef IMPLEMENTATION
-void ListBox_setSelected(entity me, float i)
+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
@@ -98,10 +117,14 @@ void ListBox_setSelected(entity me, float i)
                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)
+{
+       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)
@@ -148,10 +171,16 @@ float ListBox_keyDown(entity me, float key, float ascii, float shift)
        }
        else if(key == K_MWHEELDOWN)
        {
-               me.scrollPosTarget = min(me.scrollPosTarget + 0.5, me.getTotalHeight(me) - 1);
+               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 (;;)
@@ -167,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 (;;)
@@ -181,9 +216,25 @@ 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.setSelected(me, 0);
        else if(key == K_END || key == K_KP_END)
@@ -194,18 +245,17 @@ float ListBox_keyDown(entity me, float key, float ascii, float shift)
 }
 float ListBox_mouseMove(entity me, vector pos)
 {
-       float focusedItem_save = me.focusedItem;
-       me.focusedItem = -1;
+       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.focusedItem = me.getItemAtPos(me, me.scrollPos + pos.y);
-               me.focusedItemPos = eY * pos.y;
-               if(focusedItem_save != me.focusedItem)
-                       me.focusedItemAlpha = SKINALPHA_LISTBOX_FOCUSED;
+               me.setFocusedItem(me, -1);
+               me.mouseMoveOffset = -1;
        }
        return 1;
 }
@@ -235,8 +285,9 @@ float ListBox_mouseDrag(entity me, vector pos)
        }
        else if(me.pressed == 2)
        {
-               me.focusedItem = -1;
                me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
+               me.setFocusedItem(me, me.selectedItem);
+               me.mouseMoveOffset = -1;
        }
        return 1;
 }
@@ -274,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)
@@ -290,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)
                {
@@ -313,7 +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.focusedItem = -1;
+       me.setFocusedItem(me, -1);
+       me.mouseMoveOffset = -1;
 }
 void ListBox_updateControlTopBottom(entity me)
 {
@@ -351,22 +416,34 @@ 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 PI = 3.1415926535897932384626433832795028841971693993751058209749445923;
-               // this formula is guaranted to work with whatever framerate
-               float f = sin(PI / 2 * pow(frametime, 0.65));
-               me.scrollPos = me.scrollPos * (1 - f) + me.scrollPosTarget * f;
-
-               // update focusedItem while scrolling
-               if(me.focusedItem >= 0)
-                       me.mouseMove(me, me.focusedItemPos);
+               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)
@@ -414,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