]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/menu/item/listbox.c
Merge branch 'master' into terencehill/music_player
[xonotic/xonotic-data.pk3dir.git] / qcsrc / menu / item / listbox.c
index c32057da8d2665e10655220354fd0aa85bee5dbb..d4eaab2a7b8277e51866df26756550f5c6378c8c 100644 (file)
@@ -38,13 +38,40 @@ CLASS(ListBox) EXTENDS(Item)
        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, setSelected, void(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))
+       // 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.
+       // The "hot" code path calls getItemAtPos first, then will query
+       // getItemStart and getItemHeight on it soon.
+       // When overriding, the following consistency rules must hold:
+       // getTotalHeight() == SUM(getItemHeight(i), i, 0, me.nItems-1)
+       // getItemStart(i+1) == getItemStart(i) + getItemHeight(i)
+       //   for 0 <= i < me.nItems-1
+       // getItemStart(0) == 0
+       // getItemStart(getItemAtPos(p)) <= p
+       //   if p >= 0
+       // getItemAtPos(p) == 0
+       //   if p < 0
+       // getItemStart(getItemAtPos(p)) + getItemHeight(getItemAtPos(p)) > p
+       //   if p < getTotalHeigt()
+       // getItemAtPos(p) == me.nItems - 1
+       //   if p >= getTotalHeight()
 ENDCLASS(ListBox)
 #endif
 
 #ifdef IMPLEMENTATION
 void ListBox_setSelected(entity me, float i)
 {
-       me.selectedItem = floor(0.5 + bound(0, i, me.nItems - 1));
+       me.selectedItem = bound(0, i, me.nItems - 1);
 }
 void ListBox_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
 {
@@ -56,23 +83,75 @@ void ListBox_configureListBox(entity me, float theScrollbarWidth, float theItemH
        me.scrollbarWidth = theScrollbarWidth;
        me.itemHeight = theItemHeight;
 }
+
+float ListBox_getTotalHeight(entity me)
+{
+       return me.nItems * me.itemHeight;
+}
+float ListBox_getItemAtPos(entity me, float pos)
+{
+       return floor(pos / me.itemHeight);
+}
+float ListBox_getItemStart(entity me, float i)
+{
+       return me.itemHeight * i;
+}
+float ListBox_getItemHeight(entity me, float i)
+{
+       return me.itemHeight;
+}
+
+float ListBox_getLastFullyVisibleItemAtScrollPos(entity me, float pos)
+{
+       return me.getItemAtPos(me, pos + 1.001) - 1;
+}
+float ListBox_getFirstFullyVisibleItemAtScrollPos(entity me, float pos)
+{
+       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, floor((me.scrollPos + 1) / me.itemHeight - 1)));
+               me.setSelected(me, min(me.selectedItem, me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos)));
        }
        else if(key == K_MWHEELDOWN)
        {
-               me.scrollPos = min(me.scrollPos + 0.5, me.nItems * me.itemHeight - 1);
-               me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
+               me.scrollPos = min(me.scrollPos + 0.5, me.getTotalHeight(me) - 1);
+               me.setSelected(me, max(me.selectedItem, me.getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos)));
        }
        else if(key == K_PGUP || key == K_KP_PGUP)
-               me.setSelected(me, me.selectedItem - 1 / me.itemHeight);
+       {
+               float i = me.selectedItem;
+               float a = me.getItemHeight(me, i);
+               for(;;)
+               {
+                       --i;
+                       if (i < 0)
+                               break;
+                       a += me.getItemHeight(me, i);
+                       if (a >= 1)
+                               break;
+               }
+               me.setSelected(me, i + 1);
+       }
        else if(key == K_PGDN || key == K_KP_PGDN)
-               me.setSelected(me, me.selectedItem + 1 / me.itemHeight);
+       {
+               float i = me.selectedItem;
+               float a = me.getItemHeight(me, i);
+               for(;;)
+               {
+                       ++i;
+                       if (i >= me.nItems)
+                               break;
+                       a += me.getItemHeight(me, i);
+                       if (a >= 1)
+                               break;
+               }
+               me.setSelected(me, i - 1);
+       }
        else if(key == K_UPARROW || key == K_KP_UPARROW)
                me.setSelected(me, me.selectedItem - 1);
        else if(key == K_DOWNARROW || key == K_KP_DOWNARROW)
@@ -84,7 +163,7 @@ float ListBox_keyDown(entity me, float key, float ascii, float shift)
        }
        else if(key == K_END || key == K_KP_END)
        {
-               me.scrollPos = max(0, me.nItems * me.itemHeight - 1);
+               me.scrollPos = max(0, me.getTotalHeight(me) - 1);
                me.setSelected(me, me.nItems - 1);
        }
        else
@@ -108,20 +187,20 @@ 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.nItems * me.itemHeight - 1);
+                       d = (pos_y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.getTotalHeight(me) - 1);
                        me.scrollPos = me.previousValue + d;
                }
                else
                        me.scrollPos = me.previousValue;
-               me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
+               me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1);
                me.scrollPos = max(me.scrollPos, 0);
-               i = min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1));
-               i = max(i, ceil(me.scrollPos / me.itemHeight));
+               i = min(me.selectedItem, me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos));
+               i = max(i, ListBox_getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos));
                me.setSelected(me, i);
        }
        else if(me.pressed == 2)
        {
-               me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
+               me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos_y));
        }
        return 1;
 }
@@ -141,13 +220,13 @@ float ListBox_mousePress(entity me, vector pos)
                {
                        // page up
                        me.scrollPos = max(me.scrollPos - 1, 0);
-                       me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
+                       me.setSelected(me, min(me.selectedItem, ListBox_getLastFullyVisibleItemAtScrollPos(me, me.scrollPos)));
                }
                else if(pos_y > me.controlBottom)
                {
                        // page down
-                       me.scrollPos = min(me.scrollPos + 1, me.nItems * me.itemHeight - 1);
-                       me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
+                       me.scrollPos = min(me.scrollPos + 1, me.getTotalHeight(me) - 1);
+                       me.setSelected(me, max(me.selectedItem, ListBox_getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos)));
                }
                else
                {
@@ -161,7 +240,7 @@ float ListBox_mousePress(entity me, vector pos)
                // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
                me.pressed = 2;
                // an item has been clicked. Select it, ...
-               me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
+               me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos_y));
        }
        return 1;
 }
@@ -177,11 +256,11 @@ float ListBox_mouseRelease(entity me, vector pos)
                me.pressed = 3; // do that here, so setSelected can know the mouse has been released
                // item dragging mode
                // select current one one last time...
-               me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
+               me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos_y));
                // and give it a nice click event
                if(me.nItems > 0)
                {
-                       me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eY * (me.selectedItem * me.itemHeight - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.itemHeight));
+                       me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eY * (me.getItemStart(me, me.selectedItem) - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, me.selectedItem)));
                }
        }
        me.pressed = 0;
@@ -198,7 +277,7 @@ void ListBox_updateControlTopBottom(entity me)
 {
        float f;
        // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
-       if(me.nItems * me.itemHeight <= 1)
+       if(me.getTotalHeight(me) <= 1)
        {
                // we don't need no stinkin' scrollbar, we don't need no view control...
                me.controlTop = 0;
@@ -214,23 +293,23 @@ void ListBox_updateControlTopBottom(entity me)
                                float save;
                                save = me.scrollPos;
                                // if selected item is below listbox, increase scrollpos so it is in
-                               me.scrollPos = max(me.scrollPos, me.selectedItem * me.itemHeight - 1 + me.itemHeight);
+                               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.selectedItem * me.itemHeight);
+                               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.nItems * me.itemHeight - 1);
+               me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1);
                // if scroll pos is above beginning of list, fix it
                me.scrollPos = max(me.scrollPos, 0);
                // now that we know where the list is scrolled to, find out where to draw the control
-               me.controlTop = max(0, me.scrollPos / (me.nItems * me.itemHeight));
-               me.controlBottom = min((me.scrollPos + 1) / (me.nItems * me.itemHeight), 1);
+               me.controlTop = max(0, me.scrollPos / me.getTotalHeight(me));
+               me.controlBottom = min((me.scrollPos + 1) / me.getTotalHeight(me), 1);
 
                float minfactor;
-               minfactor = 1 * me.controlWidth / me.size_y * me.size_x;
+               minfactor = 2 * me.controlWidth / me.size_y * me.size_x;
                f = me.controlBottom - me.controlTop;
                if(f < minfactor) // FIXME good default?
                {
@@ -258,7 +337,7 @@ void ListBox_draw(entity me)
        if(me.controlWidth)
        {
                draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
-               if(me.nItems * me.itemHeight > 1)
+               if(me.getTotalHeight(me) > 1)
                {
                        vector o, s;
                        o = eX * (1 - me.controlWidth) + eY * me.controlTop;
@@ -274,16 +353,17 @@ void ListBox_draw(entity me)
        draw_SetClip();
        oldshift = draw_shift;
        oldscale = draw_scale;
-       absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
-       draw_scale = boxToGlobalSize(eX * (1 - me.controlWidth) + eY * me.itemHeight, oldscale);
-       for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
+       float y;
+       i = me.getItemAtPos(me, me.scrollPos);
+       y = me.getItemStart(me, i) - me.scrollPos;
+       for(; i < me.nItems && y < 1; ++i)
        {
-               float y;
-               y = i * me.itemHeight - me.scrollPos;
-               if(y >= 1)
-                       break;
                draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
+               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));
+               y += relSize_y;
        }
        draw_ClearClip();