Make Charmap and CrosshairPicker subclasses of the class Picker
authorterencehill <piuntn@gmail.com>
Sat, 2 May 2015 22:30:57 +0000 (00:30 +0200)
committerterencehill <piuntn@gmail.com>
Sat, 2 May 2015 22:30:57 +0000 (00:30 +0200)
qcsrc/menu/classes.qc
qcsrc/menu/xonotic/charmap.qc
qcsrc/menu/xonotic/crosshairpicker.qc
qcsrc/menu/xonotic/picker.qc [new file with mode: 0644]

index 41522d6..a0d31bb 100644 (file)
@@ -69,6 +69,7 @@
 #include "xonotic/image.qc"
 #include "xonotic/playermodel.qc"
 #include "xonotic/checkbox_slider_invalid.qc"
+#include "xonotic/picker.qc"
 #include "xonotic/charmap.qc"
 #include "xonotic/crosshairpicker.qc"
 #include "xonotic/crosshairpreview.qc"
index b72b868..eb83ff8 100644 (file)
@@ -1,33 +1,25 @@
 #ifdef INTERFACE
-CLASS(XonoticCharmap) EXTENDS(Item)
+CLASS(XonoticCharmap) EXTENDS(XonoticPicker)
        METHOD(XonoticCharmap, configureXonoticCharmap, void(entity, entity))
-       METHOD(XonoticCharmap, mousePress, float(entity, vector))
-       METHOD(XonoticCharmap, mouseRelease, float(entity, vector))
-       METHOD(XonoticCharmap, mouseMove, float(entity, vector))
-       METHOD(XonoticCharmap, mouseDrag, float(entity, vector))
-       METHOD(XonoticCharmap, keyDown, float(entity, float, float, float))
        METHOD(XonoticCharmap, focusLeave, void(entity))
        METHOD(XonoticCharmap, resizeNotify, void(entity, vector, vector, vector, vector))
-       METHOD(XonoticCharmap, draw, void(entity))
-       ATTRIB(XonoticCharmap, focusable, float, 1)
-
-       METHOD(XonoticCharmap, moveFocus, void(entity, vector, vector))
-       METHOD(XonoticCharmap, enterChar, void(entity))
+       METHOD(XonoticCharmap, keyDown, float(entity, float, float, float))
        ATTRIB(XonoticCharmap, inputBox, entity, NULL)
        ATTRIB(XonoticCharmap, realFontSize, vector, '0 0 0')
-       ATTRIB(XonoticCharmap, realCellSize, vector, '0 0 0')
-       ATTRIB(XonoticCharmap, focusedCell, vector, '-1 -1 0')
-       ATTRIB(XonoticCharmap, focusedCellTime, float, 0)
-       ATTRIB(XonoticCharmap, pressedCell, vector, '-1 -1 0')
+
+       ATTRIB(XonoticCharmap, rows, float, 10)
+       ATTRIB(XonoticCharmap, columns, float, 14)
+
+       METHOD(XonoticCharmap, cellSelect, void(entity))
+       METHOD(XonoticCharmap, cellIsValid, bool(entity, vector))
+       METHOD(XonoticCharmap, cellDraw, void(entity, vector, vector, float))
+       METHOD(XonoticCharmap, charOffset, vector)
 ENDCLASS(XonoticCharmap)
 entity makeXonoticCharmap(entity controlledInputBox);
 #endif
 
 #ifdef IMPLEMENTATION
 
-const float CHARMAP_COLS = 14;
-const float CHARMAP_ROWS = 10;
-
 string CHARMAP =
        "★◆■▮▰▬◣◤◥◢◀▲▶▼"
        "🌍🌎🌏🚀🌌👽🔫⌖❇❈←↑→↓"
@@ -45,11 +37,11 @@ string CHARMAP =
        "\xEE\x83\x8F\xEE\x83\x90\xEE\x83\x91\xEE\x83\x92\xEE\x83\x93\xEE\x83\x94\xEE\x83\x95"
        "\xEE\x83\x96\xEE\x83\x97\xEE\x83\x98\xEE\x83\x99\xEE\x83\x9A\xEE\x81\x9B\xEE\x81\x9D";
 
-string charmap_cellToChar(vector cell)
+string charmap_cellToChar(entity me, vector cell)
 {
-       string character = substring(CHARMAP, cell.y * CHARMAP_COLS + cell.x, 1);
+       string character = substring(CHARMAP, cell.y * me.columns + cell.x, 1);
 
-       if (character != " ")
+       if(character != " ")
                return character;
        else
                return "";
@@ -66,7 +58,7 @@ entity makeXonoticCharmap(entity controlledInputBox)
 void XonoticCharmap_configureXonoticCharmap(entity me, entity controlledInputBox)
 {
        me.inputBox = controlledInputBox;
-       me.realCellSize = eX / CHARMAP_COLS + eY / CHARMAP_ROWS;
+       me.configureXonoticPicker(me);
 }
 
 void XonoticCharmap_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
@@ -89,158 +81,38 @@ void XonoticCharmap_resizeNotify(entity me, vector relOrigin, vector relSize, ve
 
        if(me.realFontSize.x > maxFontWidth || me.realFontSize.y > maxFontHeight)
                me.realFontSize = eX * maxFontWidth + eY * maxFontHeight;
-}
-
-float XonoticCharmap_mouseMove(entity me, vector coords)
-{
-       vector prevFocusedCell = me.focusedCell;
-       me.focusedCell_x = floor(coords.x * CHARMAP_COLS);
-       me.focusedCell_y = floor(coords.y * CHARMAP_ROWS);
-
-       if(me.focusedCell.x < 0 || me.focusedCell.y < 0 ||
-          me.focusedCell.x >= CHARMAP_COLS || me.focusedCell.y >= CHARMAP_ROWS)
-       {
-               me.focusedCell = '-1 -1 0';
-               return 0;
-       }
-
-       if(me.focusedCell != prevFocusedCell)
-               me.focusedCellTime = time;
-
-       return 1;
-}
-
-float XonoticCharmap_mouseDrag(entity me, vector coords)
-{
-       return me.mouseMove(me, coords);
-}
-
-float XonoticCharmap_mousePress(entity me, vector coords)
-{
-       me.mouseMove(me, coords);
-
-       if(me.focusedCell.x >= 0)
-       {
-               me.pressed = 1;
-               me.pressedCell = me.focusedCell;
-       }
 
-       return 1;
+       me.charOffset = eX * me.realCellSize.x / 2 + eY * ((me.realCellSize.y - me.realFontSize.y) / 2);
 }
 
-float XonoticCharmap_mouseRelease(entity me, vector coords)
+float XonoticCharmap_keyDown(entity me, float key, float ascii, float shift)
 {
-       if(!me.pressed)
-               return 0;
-
-       me.mouseMove(me, coords);
-
-       if(me.focusedCell == me.pressedCell)
-               me.enterChar(me);
-
-       me.pressed = 0;
-       return 1;
+       if(SUPER(XonoticCharmap).keyDown(me, key, ascii, shift))
+               return 1;
+       return me.inputBox.keyDown(me.inputBox, key, ascii, shift);
 }
 
-float XonoticCharmap_keyDown(entity me, float key, float ascii, float shift)
+void XonoticCharmap_cellSelect(entity me)
 {
-       switch(key)
-       {
-               case K_LEFTARROW:
-               case K_KP_LEFTARROW:
-                       me.moveFocus(me, me.focusedCell, '-1 0 0');
-                       return 1;
-               case K_RIGHTARROW:
-               case K_KP_RIGHTARROW:
-                       me.moveFocus(me, me.focusedCell, '1 0 0');
-                       return 1;
-               case K_UPARROW:
-               case K_KP_UPARROW:
-                       me.moveFocus(me, me.focusedCell, '0 -1 0');
-                       return 1;
-               case K_DOWNARROW:
-               case K_KP_DOWNARROW:
-                       me.moveFocus(me, me.focusedCell, '0 1 0');
-                       return 1;
-               case K_HOME:
-               case K_KP_HOME:
-                       me.focusedCell = '0 0 0';
-                       return 1;
-               case K_END:
-               case K_KP_END:
-                       me.focusedCell_x = CHARMAP_COLS - 1;
-                       me.focusedCell_y = CHARMAP_ROWS - 1;
-                       return 1;
-               case K_ENTER:
-               case K_KP_ENTER:
-               case K_INS:
-               case K_KP_INS:
-                       me.enterChar(me);
-                       return 1;
-               default:
-                       return me.inputBox.keyDown(me.inputBox, key, ascii, shift);
-       }
+       string character = charmap_cellToChar(me, me.focusedCell);
+       if(character != "")
+               me.inputBox.enterText(me.inputBox, character);
 }
 
-void XonoticCharmap_moveFocus(entity me, vector initialCell, vector step)
+bool XonoticCharmap_cellIsValid(entity me, vector cell)
 {
-       me.focusedCell_x = mod(me.focusedCell.x + step.x + CHARMAP_COLS, CHARMAP_COLS);
-       me.focusedCell_y = mod(me.focusedCell.y + step.y + CHARMAP_ROWS, CHARMAP_ROWS);
-
-       if(me.focusedCell != initialCell) // Recursion break
-               if(charmap_cellToChar(me.focusedCell) == "")
-                       me.moveFocus(me, initialCell, step);
+       if(charmap_cellToChar(me, cell) == "")
+               return false;
+       return true;
 }
 
-void XonoticCharmap_enterChar(entity me)
+void XonoticCharmap_cellDraw(entity me, vector cell, vector cellPos, float highlightedTime)
 {
-       string character = charmap_cellToChar(me.focusedCell);
-       if(character != "")
-               me.inputBox.enterText(me.inputBox, character);
+       draw_CenterText(cellPos + me.charOffset, charmap_cellToChar(me, cell), me.realFontSize, SKINCOLOR_CHARMAP_CHAR, SKINALPHA_CHARMAP_CHAR, 0);
 }
 
 void XonoticCharmap_focusLeave(entity me)
 {
        me.inputBox.saveCvars(me.inputBox);
 }
-
-void XonoticCharmap_draw(entity me)
-{
-       string character;
-       vector cell, cellPos, charPos;
-       cell = '0 0 0';
-       cellPos = '0 0 0';
-       charPos = '0 0 0';
-
-       float CHAR_OFFSET_X = me.realCellSize.x / 2;
-       float CHAR_OFFSET_Y = (me.realCellSize.y - me.realFontSize.y) / 2;
-
-       for(cell_y = 0; cell.y < CHARMAP_ROWS; ++cell.y)
-       {
-               charPos_y = cell.y / CHARMAP_ROWS + CHAR_OFFSET_Y;
-               for(cell_x = 0; cell.x < CHARMAP_COLS; ++cell.x)
-               {
-                       character = charmap_cellToChar(cell);
-
-                       if(character == "")
-                               continue;
-
-                       // Draw focused cell
-                       if(cell == me.focusedCell && me.focused)
-                       {
-                               if(!me.pressed || me.focusedCell == me.pressedCell)
-                               {
-                                       cellPos_x = mod(me.focusedCell.x, CHARMAP_COLS) / CHARMAP_COLS;
-                                       cellPos_y = mod(me.focusedCell.y, CHARMAP_ROWS) / CHARMAP_ROWS;
-                                       draw_Fill(cellPos, me.realCellSize, SKINCOLOR_LISTBOX_FOCUSED, getHighlightAlpha(SKINALPHA_LISTBOX_FOCUSED, SKINFADEALPHA_LISTBOX_FOCUSED, me.focusedCellTime));
-                               }
-                       }
-
-                       // Draw character
-                       charPos_x = cell.x / CHARMAP_COLS + CHAR_OFFSET_X;
-                       draw_CenterText(charPos, character, me.realFontSize, SKINCOLOR_CHARMAP_CHAR, SKINALPHA_CHARMAP_CHAR, 0);
-               }
-       }
-       SUPER(XonoticCharmap).draw(me);
-}
 #endif
index 9236e61..ed937fe 100644 (file)
@@ -1,37 +1,24 @@
 #ifdef INTERFACE
-CLASS(XonoticCrosshairPicker) EXTENDS(Item)
+CLASS(XonoticCrosshairPicker) EXTENDS(XonoticPicker)
        METHOD(XonoticCrosshairPicker, configureXonoticCrosshairPicker, void(entity))
-       METHOD(XonoticCrosshairPicker, mousePress, float(entity, vector))
-       METHOD(XonoticCrosshairPicker, mouseRelease, float(entity, vector))
-       METHOD(XonoticCrosshairPicker, mouseMove, float(entity, vector))
-       METHOD(XonoticCrosshairPicker, mouseDrag, float(entity, vector))
-       METHOD(XonoticCrosshairPicker, keyDown, float(entity, float, float, float))
-       METHOD(XonoticCrosshairPicker, draw, void(entity))
-       ATTRIB(XonoticCrosshairPicker, focusable, float, 1)
-       ATTRIB(XonoticCrosshairPicker, disabled, float, 0)
-       ATTRIB(XonoticCrosshairPicker, alpha, float, 1)
-       ATTRIB(XonoticCrosshairPicker, disabledAlpha, float, SKINALPHA_DISABLED)
 
-       METHOD(XonoticCrosshairPicker, moveFocus, void(entity, vector, vector))
-       METHOD(XonoticCrosshairPicker, setCrosshair, void(entity))
-       ATTRIB(XonoticCrosshairPicker, realCellSize, vector, '0 0 0')
-       ATTRIB(XonoticCrosshairPicker, focusedCell, vector, '-1 -1 0')
-       ATTRIB(XonoticCrosshairPicker, focusedCellTime, float, 0)
-       ATTRIB(XonoticCrosshairPicker, pressedCell, vector, '-1 -1 0')
+       ATTRIB(XonoticCrosshairPicker, rows, float, 3)
+       ATTRIB(XonoticCrosshairPicker, columns, float, 12)
+
+       METHOD(XonoticCrosshairPicker, cellSelect, void(entity))
+       METHOD(XonoticCrosshairPicker, cellIsValid, bool(entity, vector))
+       METHOD(XonoticCrosshairPicker, cellDraw, void(entity, vector, vector, float))
 ENDCLASS(XonoticCrosshairPicker)
 entity makeXonoticCrosshairPicker();
 #endif
 
 #ifdef IMPLEMENTATION
 
-const float CROSSHAIRPICKER_COLS = 12;
-const float CROSSHAIRPICKER_ROWS = 3;
-
-string crosshairpicker_cellToCrosshair(vector cell)
+string crosshairpicker_cellToCrosshair(entity me, vector cell)
 {
-       float crosshair = 31 + cell.y * CROSSHAIRPICKER_COLS + cell.x;
+       float crosshair = 31 + cell.y * me.columns + cell.x;
 
-       if (crosshair >= 31 && crosshair < 31 + CROSSHAIRPICKER_COLS * CROSSHAIRPICKER_ROWS)
+       if (crosshair >= 31 && crosshair < 31 + me.columns * me.rows)
                return ftos(crosshair);
        else
                return "";
@@ -47,179 +34,44 @@ entity makeXonoticCrosshairPicker()
 
 void XonoticCrosshairPicker_configureXonoticCrosshairPicker(entity me)
 {
-       me.realCellSize = eX / CROSSHAIRPICKER_COLS + eY / CROSSHAIRPICKER_ROWS;
+       me.configureXonoticPicker(me);
 }
 
-float XonoticCrosshairPicker_mouseMove(entity me, vector coords)
+void XonoticCrosshairPicker_cellSelect(entity me)
 {
-       vector prevFocusedCell = me.focusedCell;
-       me.focusedCell_x = floor(coords.x * CROSSHAIRPICKER_COLS);
-       me.focusedCell_y = floor(coords.y * CROSSHAIRPICKER_ROWS);
-
-       if(me.focusedCell.x < 0 || me.focusedCell.y < 0 ||
-          me.focusedCell.x >= CROSSHAIRPICKER_COLS || me.focusedCell.y >= CROSSHAIRPICKER_ROWS)
-       {
-               me.focusedCell = '-1 -1 0';
-               return 0;
-       }
-
-       if(me.focusedCell != prevFocusedCell)
-               me.focusedCellTime = time;
-
-       return 1;
+       cvar_set("crosshair", crosshairpicker_cellToCrosshair(me, me.focusedCell));
 }
 
-float XonoticCrosshairPicker_mouseDrag(entity me, vector coords)
+bool XonoticCrosshairPicker_cellIsValid(entity me, vector cell)
 {
-       return me.mouseMove(me, coords);
+       if(crosshairpicker_cellToCrosshair(me, cell) == "")
+               return false;
+       return true;
 }
 
-float XonoticCrosshairPicker_mousePress(entity me, vector coords)
-{
-       me.mouseMove(me, coords);
-
-       if(me.focusedCell.x >= 0)
-       {
-               me.pressed = 1;
-               me.pressedCell = me.focusedCell;
-       }
-
-       return 1;
-}
-
-float XonoticCrosshairPicker_mouseRelease(entity me, vector coords)
-{
-       if(!me.pressed)
-               return 0;
-
-       me.mouseMove(me, coords);
-
-       if(me.focusedCell == me.pressedCell)
-               me.setCrosshair(me);
-
-       me.pressed = 0;
-       return 1;
-}
-
-float XonoticCrosshairPicker_keyDown(entity me, float key, float ascii, float shift)
-{
-       switch(key)
-       {
-               case K_LEFTARROW:
-               case K_KP_LEFTARROW:
-                       me.moveFocus(me, me.focusedCell, '-1 0 0');
-                       return 1;
-               case K_RIGHTARROW:
-               case K_KP_RIGHTARROW:
-                       me.moveFocus(me, me.focusedCell, '1 0 0');
-                       return 1;
-               case K_UPARROW:
-               case K_KP_UPARROW:
-                       me.moveFocus(me, me.focusedCell, '0 -1 0');
-                       return 1;
-               case K_DOWNARROW:
-               case K_KP_DOWNARROW:
-                       me.moveFocus(me, me.focusedCell, '0 1 0');
-                       return 1;
-               case K_HOME:
-               case K_KP_HOME:
-                       me.focusedCell = '0 0 0';
-                       return 1;
-               case K_END:
-               case K_KP_END:
-                       me.focusedCell_x = CROSSHAIRPICKER_COLS - 1;
-                       me.focusedCell_y = CROSSHAIRPICKER_ROWS - 1;
-                       return 1;
-               case K_ENTER:
-               case K_KP_ENTER:
-               case K_INS:
-               case K_KP_INS:
-                       me.setCrosshair(me);
-                       return 1;
-       }
-       return 0;
-}
-
-void XonoticCrosshairPicker_moveFocus(entity me, vector initialCell, vector step)
-{
-       me.focusedCell_x = mod(me.focusedCell.x + step.x + CROSSHAIRPICKER_COLS, CROSSHAIRPICKER_COLS);
-       me.focusedCell_y = mod(me.focusedCell.y + step.y + CROSSHAIRPICKER_ROWS, CROSSHAIRPICKER_ROWS);
-
-       if(me.focusedCell != initialCell) // Recursion break
-               if(crosshairpicker_cellToCrosshair(me.focusedCell) == "")
-                       me.moveFocus(me, initialCell, step);
-}
-
-void XonoticCrosshairPicker_setCrosshair(entity me)
-{
-       cvar_set("crosshair", crosshairpicker_cellToCrosshair(me.focusedCell));
-}
-
-void XonoticCrosshairPicker_draw(entity me)
+void XonoticCrosshairPicker_cellDraw(entity me, vector cell, vector cellPos, float highlightedTime)
 {
        vector sz, rgb;
-       float save;
-
-       me.focusable = !me.disabled;
+       string cross = strcat("/gfx/crosshair", crosshairpicker_cellToCrosshair(me, cell));
+       sz = draw_PictureSize(cross);
+       sz = globalToBoxSize(sz, me.size);
 
-       save = draw_alpha;
-       if(me.disabled)
-               draw_alpha *= me.disabledAlpha;
+       float ar = sz.x / sz.y;
+       sz.x = me.realCellSize.x;
+       sz.y = sz.x / ar;
+       sz = sz * 0.95;
 
-       string crosshair;
-       vector cell, cellPos, crosshairPos;
-       cell = '0 0 0';
-       cellPos = '0 0 0';
-       crosshairPos = '0 0 0';
+       rgb = '1 1 1';
 
+       vector crosshairPos = cellPos + 0.5 * me.realCellSize;
+       draw_Picture(crosshairPos - 0.5 * sz, cross, sz, rgb, me.alpha);
 
-       for(cell_y = 0; cell.y < CROSSHAIRPICKER_ROWS; ++cell.y)
+       if(cvar("crosshair_dot"))
        {
-               crosshairPos_y = cell.y / CROSSHAIRPICKER_ROWS + 0.5 * me.realCellSize.y;
-               for(cell_x = 0; cell.x < CROSSHAIRPICKER_COLS; ++cell.x)
-               {
-                       crosshair = crosshairpicker_cellToCrosshair(cell);
-
-                       if(crosshair == "")
-                               continue;
-
-                       // Draw focused cell
-                       if(cell == me.focusedCell && me.focused)
-                       {
-                               if(!me.pressed || me.focusedCell == me.pressedCell)
-                               {
-                                       cellPos_x = mod(me.focusedCell.x, CROSSHAIRPICKER_COLS) / CROSSHAIRPICKER_COLS;
-                                       cellPos_y = mod(me.focusedCell.y, CROSSHAIRPICKER_ROWS) / CROSSHAIRPICKER_ROWS;
-                                       draw_Fill(cellPos, me.realCellSize, SKINCOLOR_LISTBOX_FOCUSED, getHighlightAlpha(SKINALPHA_LISTBOX_FOCUSED, SKINFADEALPHA_LISTBOX_FOCUSED, me.focusedCellTime));
-                               }
-                       }
-
-                       // Draw crosshair
-                       crosshairPos_x = cell.x / CROSSHAIRPICKER_COLS + 0.5 * me.realCellSize.x;
-                       string cross = strcat("/gfx/crosshair", crosshairpicker_cellToCrosshair(cell));
-                       sz = draw_PictureSize(cross);
-                       sz = globalToBoxSize(sz, me.size);
+               if(cvar("crosshair_dot_color_custom") && (cvar_string("crosshair_dot_color") != "0"))
+                       rgb = stov(cvar_string("crosshair_dot_color"));
 
-                       float ar = sz.x / sz.y;
-                       sz.x = me.realCellSize.x;
-                       sz.y = sz.x / ar;
-
-                       sz = sz * 0.95;
-
-                       rgb = '1 1 1';
-                       draw_Picture(crosshairPos - 0.5 * sz, cross, sz, rgb, me.alpha);
-                       if(cvar("crosshair_dot"))
-                       {
-                               if(cvar("crosshair_dot_color_custom") && (cvar_string("crosshair_dot_color") != "0"))
-                                       rgb = stov(cvar_string("crosshair_dot_color"));
-
-                               draw_Picture(crosshairPos - 0.5 * sz * cvar("crosshair_dot_size"), "/gfx/crosshairdot", sz * cvar("crosshair_dot_size"), rgb, me.alpha);
-                       }
-               }
+               draw_Picture(crosshairPos - 0.5 * sz * cvar("crosshair_dot_size"), "/gfx/crosshairdot", sz * cvar("crosshair_dot_size"), rgb, me.alpha);
        }
-
-       draw_alpha = save;
-
-       SUPER(XonoticCrosshairPicker).draw(me);
 }
 #endif
diff --git a/qcsrc/menu/xonotic/picker.qc b/qcsrc/menu/xonotic/picker.qc
new file mode 100644 (file)
index 0000000..b03ddec
--- /dev/null
@@ -0,0 +1,196 @@
+#ifdef INTERFACE
+CLASS(XonoticPicker) EXTENDS(Item)
+       METHOD(XonoticPicker, configureXonoticPicker, void(entity))
+       METHOD(XonoticPicker, mousePress, float(entity, vector))
+       METHOD(XonoticPicker, mouseRelease, float(entity, vector))
+       METHOD(XonoticPicker, mouseMove, float(entity, vector))
+       METHOD(XonoticPicker, mouseDrag, float(entity, vector))
+       METHOD(XonoticPicker, keyDown, float(entity, float, float, float))
+       METHOD(XonoticPicker, draw, void(entity))
+       ATTRIB(XonoticPicker, focusable, float, 1)
+       ATTRIB(XonoticPicker, disabled, float, 0)
+       ATTRIB(XonoticPicker, alpha, float, 1)
+       ATTRIB(XonoticPicker, disabledAlpha, float, SKINALPHA_DISABLED)
+
+       ATTRIB(XonoticPicker, rows, float, 3)
+       ATTRIB(XonoticPicker, columns, float, 2)
+
+       METHOD(XonoticPicker, moveFocus, void(entity, vector, vector))
+       METHOD(XonoticPicker, cellSelect, void(entity))
+       METHOD(XonoticPicker, cellDraw, void(entity, vector, vector, float))
+       METHOD(XonoticPicker, cellIsValid, bool(entity, vector))
+       ATTRIB(XonoticPicker, realCellSize, vector, '0 0 0')
+       ATTRIB(XonoticPicker, focusedCell, vector, '-1 -1 0')
+       ATTRIB(XonoticPicker, focusedCellTime, float, 0)
+       ATTRIB(XonoticPicker, pressedCell, vector, '-1 -1 0')
+ENDCLASS(XonoticPicker)
+entity makeXonoticPicker();
+#endif
+
+#ifdef IMPLEMENTATION
+
+entity makeXonoticPicker()
+{
+       entity me;
+       me = spawnXonoticPicker();
+       me.configureXonoticPicker(me);
+       return me;
+}
+
+void XonoticPicker_configureXonoticPicker(entity me)
+{
+       me.realCellSize = eX / me.columns + eY / me.rows;
+}
+
+float XonoticPicker_mouseMove(entity me, vector coords)
+{
+       vector prevFocusedCell = me.focusedCell;
+       me.focusedCell_x = floor(coords.x * me.columns);
+       me.focusedCell_y = floor(coords.y * me.rows);
+
+       if(me.focusedCell.x < 0 || me.focusedCell.y < 0 ||
+          me.focusedCell.x >= me.columns || me.focusedCell.y >= me.rows)
+       {
+               me.focusedCell = '-1 -1 0';
+               return 0;
+       }
+
+       if(me.focusedCell != prevFocusedCell)
+               me.focusedCellTime = time;
+
+       return 1;
+}
+
+float XonoticPicker_mouseDrag(entity me, vector coords)
+{
+       return me.mouseMove(me, coords);
+}
+
+float XonoticPicker_mousePress(entity me, vector coords)
+{
+       me.mouseMove(me, coords);
+
+       if(me.focusedCell.x >= 0)
+       {
+               me.pressed = 1;
+               me.pressedCell = me.focusedCell;
+       }
+
+       return 1;
+}
+
+float XonoticPicker_mouseRelease(entity me, vector coords)
+{
+       if(!me.pressed)
+               return 0;
+
+       me.mouseMove(me, coords);
+
+       if(me.focusedCell == me.pressedCell)
+               me.cellSelect(me);
+
+       me.pressed = 0;
+       return 1;
+}
+
+float XonoticPicker_keyDown(entity me, float key, float ascii, float shift)
+{
+       switch(key)
+       {
+               case K_LEFTARROW:
+               case K_KP_LEFTARROW:
+                       me.moveFocus(me, me.focusedCell, '-1 0 0');
+                       return 1;
+               case K_RIGHTARROW:
+               case K_KP_RIGHTARROW:
+                       me.moveFocus(me, me.focusedCell, '1 0 0');
+                       return 1;
+               case K_UPARROW:
+               case K_KP_UPARROW:
+                       me.moveFocus(me, me.focusedCell, '0 -1 0');
+                       return 1;
+               case K_DOWNARROW:
+               case K_KP_DOWNARROW:
+                       me.moveFocus(me, me.focusedCell, '0 1 0');
+                       return 1;
+               case K_HOME:
+               case K_KP_HOME:
+                       me.focusedCell = '0 0 0';
+                       return 1;
+               case K_END:
+               case K_KP_END:
+                       me.focusedCell_x = me.columns - 1;
+                       me.focusedCell_y = me.rows - 1;
+                       return 1;
+               case K_ENTER:
+               case K_KP_ENTER:
+               case K_INS:
+               case K_KP_INS:
+                       me.cellSelect(me);
+                       return 1;
+       }
+       return 0;
+}
+
+void XonoticPicker_moveFocus(entity me, vector initialCell, vector step)
+{
+       me.focusedCell_x = mod(me.focusedCell.x + step.x + me.columns, me.columns);
+       me.focusedCell_y = mod(me.focusedCell.y + step.y + me.rows, me.rows);
+
+       if(me.focusedCell != initialCell) // Recursion break
+               if(!me.cellIsValid(me, me.focusedCell))
+                       me.moveFocus(me, initialCell, step);
+}
+
+void XonoticPicker_cellSelect(entity me)
+{
+}
+
+bool XonoticPicker_cellIsValid(entity me, vector cell)
+{
+       return true;
+}
+
+void XonoticPicker_cellDraw(entity me, vector cell, vector cellPos, float highlightTime)
+{
+}
+
+void XonoticPicker_draw(entity me)
+{
+       float save;
+
+       me.focusable = !me.disabled;
+
+       save = draw_alpha;
+       if(me.disabled)
+               draw_alpha *= me.disabledAlpha;
+
+       vector cell, cellPos;
+       cell = '0 0 0';
+       cellPos = '0 0 0';
+
+       for(cell_y = 0; cell.y < me.rows; ++cell.y)
+       {
+               cellPos_y = mod(cell.y, me.rows) / me.rows;
+               for(cell_x = 0; cell.x < me.columns; ++cell.x)
+               {
+                       if(!me.cellIsValid(me, cell))
+                               continue;
+
+                       cellPos_x = mod(cell.x, me.columns) / me.columns;
+
+                       if(cell == me.focusedCell && me.focused)
+                       {
+                               if(!me.pressed || me.focusedCell == me.pressedCell)
+                                       draw_Fill(cellPos, me.realCellSize, SKINCOLOR_LISTBOX_FOCUSED, getHighlightAlpha(SKINALPHA_LISTBOX_FOCUSED, SKINFADEALPHA_LISTBOX_FOCUSED, me.focusedCellTime));
+                       }
+
+                       me.cellDraw(me, cell, cellPos, (me.focusedCell == cell) ? me.focusedCellTime : 0);
+               }
+       }
+
+       draw_alpha = save;
+
+       SUPER(XonoticPicker).draw(me);
+}
+#endif