| [6718] | 1 | -- GUISheet.lua | 
|---|
| [5491] | 2 |  | 
|---|
|  | 3 | local P = {} | 
|---|
| [6718] | 4 | _G[_REQUIREDNAME or "GUISheet"] = P | 
|---|
|  | 5 | P.__index = P | 
|---|
| [5491] | 6 |  | 
|---|
| [6718] | 7 | -- Don't use directly --> use HUDSheet.new or MenuSheet.new | 
|---|
|  | 8 | function P.new(_name) | 
|---|
|  | 9 | local newSheet = { name = _name } | 
|---|
|  | 10 | setmetatable(newSheet, P) | 
|---|
|  | 11 | return newSheet | 
|---|
| [5491] | 12 | end | 
|---|
|  | 13 |  | 
|---|
| [6718] | 14 | -- Override this function if you need to do work on load | 
|---|
| [6720] | 15 | function P:onLoad() | 
|---|
| [5523] | 16 | end | 
|---|
|  | 17 |  | 
|---|
| [8079] | 18 | -- Override this function if you need to do work on show | 
|---|
|  | 19 | function P:onShow() | 
|---|
|  | 20 | end | 
|---|
|  | 21 |  | 
|---|
|  | 22 | -- Override this function if you need to do work on hide | 
|---|
|  | 23 | function P:onHide() | 
|---|
|  | 24 | end | 
|---|
|  | 25 |  | 
|---|
|  | 26 | -- Override this function if you need to do work on quit | 
|---|
|  | 27 | function P:onQuit() | 
|---|
|  | 28 | end | 
|---|
|  | 29 |  | 
|---|
|  | 30 | -- Override this function if you want to react on keystrokes | 
|---|
|  | 31 | function P:onKeyPressed() | 
|---|
|  | 32 | end | 
|---|
|  | 33 |  | 
|---|
|  | 34 | -- Override this function if you want to update the gui after the window was resized | 
|---|
|  | 35 | function P:onWindowResized() | 
|---|
|  | 36 | end | 
|---|
|  | 37 |  | 
|---|
| [6747] | 38 | -- show function for the GUI | 
|---|
|  | 39 | function P:show() | 
|---|
|  | 40 | self.window:show() | 
|---|
|  | 41 | self.bVisible = true | 
|---|
|  | 42 |  | 
|---|
| [8079] | 43 | -- set the selected button's state | 
|---|
|  | 44 | self:setSelectedButtonsStateToSelected() | 
|---|
|  | 45 |  | 
|---|
| [6747] | 46 | self:onShow() | 
|---|
|  | 47 | end | 
|---|
|  | 48 |  | 
|---|
| [5491] | 49 | -- hide function for the GUI | 
|---|
| [6595] | 50 | function P:hide() | 
|---|
| [5491] | 51 | self.window:hide() | 
|---|
| [6718] | 52 | self.bVisible = false | 
|---|
| [6747] | 53 |  | 
|---|
|  | 54 | self:onHide() | 
|---|
| [5491] | 55 | end | 
|---|
|  | 56 |  | 
|---|
| [8079] | 57 | function P:quit() | 
|---|
|  | 58 | -- reset the selected button | 
|---|
|  | 59 | if self.buttons then | 
|---|
|  | 60 | self:resetSelection() | 
|---|
|  | 61 | end | 
|---|
| [5491] | 62 |  | 
|---|
| [8079] | 63 | self:onQuit() | 
|---|
| [7403] | 64 | end | 
|---|
|  | 65 |  | 
|---|
| [6595] | 66 | function P:load() | 
|---|
| [6718] | 67 | -- Load the layout that describes the sheet | 
|---|
| [11795] | 68 | self.window = winMgr:loadLayoutFromFile(self.name .. ".layout") | 
|---|
| [6718] | 69 | if self.window == nil then | 
|---|
|  | 70 | error("Could not load layout file for GUI sheet '"..self.name.."'") | 
|---|
|  | 71 | end | 
|---|
|  | 72 | -- Hide it at first | 
|---|
|  | 73 | self:hide() | 
|---|
|  | 74 | -- Allow sheets to do some work upon loading | 
|---|
| [6720] | 75 | self:onLoad() | 
|---|
| [6748] | 76 |  | 
|---|
|  | 77 | -- Also load additional sheets to avoid display lags | 
|---|
| [8862] | 78 | if self.loadAlong and orxonox.GUIManager:preloadMenuSheets() then | 
|---|
| [6748] | 79 | for k, sheet in pairs(self.loadAlong) do | 
|---|
|  | 80 | loadSheet(sheet) | 
|---|
|  | 81 | end | 
|---|
|  | 82 | end | 
|---|
| [5559] | 83 | return self | 
|---|
| [5491] | 84 | end | 
|---|
|  | 85 |  | 
|---|
| [8079] | 86 | -- Handles key pressed while the gui sheed is displayed | 
|---|
|  | 87 | function P:keyPressed() | 
|---|
|  | 88 | if self.buttons then | 
|---|
|  | 89 | if code == "208" then     -- key down | 
|---|
|  | 90 | self:moveSelectionRow(1) | 
|---|
|  | 91 | elseif code == "200" then -- key up | 
|---|
|  | 92 | self:moveSelectionRow(-1) | 
|---|
|  | 93 | elseif code == "205" then -- key right | 
|---|
|  | 94 | self:moveSelectionColumn(1) | 
|---|
|  | 95 | elseif code == "203" then -- key left | 
|---|
|  | 96 | self:moveSelectionColumn(-1) | 
|---|
|  | 97 | elseif code == "28" or code == "156"  then -- key enter or key numpad enter | 
|---|
|  | 98 | self:pressSelectedButton() | 
|---|
|  | 99 | end | 
|---|
|  | 100 | end | 
|---|
|  | 101 |  | 
|---|
|  | 102 | self:onKeyPressed() | 
|---|
| [7689] | 103 | end | 
|---|
|  | 104 |  | 
|---|
| [8079] | 105 | function P:windowResized() | 
|---|
|  | 106 | self:onWindowResized() | 
|---|
|  | 107 | end | 
|---|
|  | 108 |  | 
|---|
|  | 109 |  | 
|---|
|  | 110 | ------------------------------------------------------------------------------- | 
|---|
|  | 111 | -- Keyboard control ----------------------------------------------------------- | 
|---|
|  | 112 | ------------------------------------------------------------------------------- | 
|---|
|  | 113 |  | 
|---|
|  | 114 | -- Initializes the buttons table, used to control the menu with the keyboard | 
|---|
|  | 115 | function P:initButtons(rows, columns) | 
|---|
|  | 116 | self.rows = rows | 
|---|
|  | 117 | self.columns = columns | 
|---|
|  | 118 | self.buttons = {} | 
|---|
|  | 119 | self.selectedRow = 0 | 
|---|
|  | 120 | self.selectedColumn = 0 | 
|---|
|  | 121 | self.ratio = 1 | 
|---|
|  | 122 | end | 
|---|
|  | 123 |  | 
|---|
|  | 124 | -- ratio: the button's with divided by the button's height (used to calculate distance between buttons - adjust this until you get the desired behavior) | 
|---|
|  | 125 | function P:setRatio(ratio) | 
|---|
|  | 126 | self.ratio = ratio | 
|---|
|  | 127 | end | 
|---|
|  | 128 |  | 
|---|
|  | 129 | -- Defines the button for a given position in the table. The upper-left button is at position (1, 1) | 
|---|
|  | 130 | function P:setButton(row, column, button) | 
|---|
|  | 131 | if not self.buttons then | 
|---|
|  | 132 | -- init the table | 
|---|
|  | 133 | self:initButtons(row, column) | 
|---|
|  | 134 | elseif row > self.rows or column > self.columns then | 
|---|
|  | 135 | -- rearrange the table | 
|---|
|  | 136 | local maxRows = math.max(self.rows, row) | 
|---|
|  | 137 | local maxColumns = math.max(self.columns, column) | 
|---|
|  | 138 |  | 
|---|
|  | 139 | for r = self.rows, 1, -1 do | 
|---|
|  | 140 | for c = self.columns, 1, -1 do | 
|---|
|  | 141 | local b = self:getButton(r, c) | 
|---|
|  | 142 | if b then | 
|---|
|  | 143 | self.buttons[(r - 1) * self.columns + (c - 1)] = nil | 
|---|
|  | 144 | self.buttons[(r - 1) * maxColumns + (c - 1)] = b | 
|---|
|  | 145 | end | 
|---|
|  | 146 | end | 
|---|
|  | 147 | end | 
|---|
|  | 148 |  | 
|---|
|  | 149 | self.rows = maxRows | 
|---|
|  | 150 | self.columns = maxColumns | 
|---|
|  | 151 | end | 
|---|
|  | 152 |  | 
|---|
|  | 153 | self.buttons[(row - 1) * self.columns + (column - 1)] = button | 
|---|
|  | 154 | end | 
|---|
|  | 155 |  | 
|---|
|  | 156 | -- Returns the button at a given position in the table. The upper-left button is at position (1, 1) | 
|---|
|  | 157 | function P:getButton(row, column) | 
|---|
|  | 158 | if self.buttons then | 
|---|
|  | 159 | return self.buttons[(row - 1) * self.columns + (column - 1)] | 
|---|
|  | 160 | else | 
|---|
|  | 161 | return nil | 
|---|
|  | 162 | end | 
|---|
|  | 163 | end | 
|---|
|  | 164 |  | 
|---|
|  | 165 | -- Returns the selected button | 
|---|
|  | 166 | function P:getSelectedButton() | 
|---|
|  | 167 | if self:hasSelection() then | 
|---|
|  | 168 | return self:getButton(self.selectedRow, self.selectedColumn) | 
|---|
|  | 169 | else | 
|---|
|  | 170 | return nil | 
|---|
|  | 171 | end | 
|---|
|  | 172 | end | 
|---|
|  | 173 |  | 
|---|
|  | 174 | -- Presses the selected button if any | 
|---|
|  | 175 | function P:pressSelectedButton() | 
|---|
|  | 176 | if self:getSelectedButton() then | 
|---|
|  | 177 | self.pressedEnter = true | 
|---|
|  | 178 | self:getSelectedButton().callback() | 
|---|
|  | 179 | self.pressedEnter = false | 
|---|
|  | 180 | end | 
|---|
|  | 181 | end | 
|---|
|  | 182 |  | 
|---|
|  | 183 | -- Sets the selection to a given row and column. The upper-left button is at position (1, 1) | 
|---|
|  | 184 | function P:setSelection(row, column) | 
|---|
|  | 185 | if not self.buttons then | 
|---|
|  | 186 | return | 
|---|
|  | 187 | end | 
|---|
|  | 188 |  | 
|---|
|  | 189 | assert(row > 0 and column > 0 and row <= self.rows and column <= self.columns, "(" .. row .. "/" .. column .. ") is not in the valid bounds of the table (1/1)-(" .. self.rows .. "/" .. self.columns .. ")") | 
|---|
|  | 190 |  | 
|---|
|  | 191 | self:setSelectedButtonsStateToNormal() | 
|---|
|  | 192 |  | 
|---|
|  | 193 | self.selectedRow = row | 
|---|
|  | 194 | self.selectedColumn = column | 
|---|
|  | 195 |  | 
|---|
|  | 196 | self:setSelectedButtonsStateToSelected() | 
|---|
|  | 197 | end | 
|---|
|  | 198 |  | 
|---|
|  | 199 | -- Sets the selection to the button closest to the given row and column. The upper-left button is at position (1, 1) | 
|---|
|  | 200 | function P:setSelectionNear(row, column) | 
|---|
|  | 201 | if not self.buttons then | 
|---|
|  | 202 | return | 
|---|
|  | 203 | end | 
|---|
|  | 204 |  | 
|---|
|  | 205 | assert(row > 0 and column > 0 and row <= self.rows and column <= self.columns, "(" .. row .. "/" .. column .. ") is not in the valid bounds of the table (1/1)-(" .. self.rows .. "/" .. self.columns .. ")") | 
|---|
|  | 206 |  | 
|---|
|  | 207 | if self:getButton(row, column) then | 
|---|
|  | 208 | self:setSelection(row, column) | 
|---|
|  | 209 | else | 
|---|
|  | 210 | local min = 1000000 | 
|---|
|  | 211 | local minRow, minColumn | 
|---|
|  | 212 |  | 
|---|
|  | 213 | for r = 1, self.rows do | 
|---|
|  | 214 | for c = 1, self.columns do | 
|---|
|  | 215 | if self:getButton(r, c) then | 
|---|
|  | 216 | local distance = math.sqrt((row - r)^2 + ((column - c) * self.ratio)^2) | 
|---|
|  | 217 | if distance < min then | 
|---|
|  | 218 | min = distance; minRow = r; minColumn = c | 
|---|
|  | 219 | end | 
|---|
|  | 220 | end | 
|---|
|  | 221 | end | 
|---|
|  | 222 | end | 
|---|
|  | 223 |  | 
|---|
|  | 224 | if minRow and minColumn then | 
|---|
|  | 225 | self:setSelection(minRow, minColumn) | 
|---|
|  | 226 | else | 
|---|
|  | 227 | self:resetSelection() | 
|---|
|  | 228 | end | 
|---|
|  | 229 | end | 
|---|
|  | 230 | end | 
|---|
|  | 231 |  | 
|---|
|  | 232 | -- Moves the selection by a given number of rows (a positive value means down, a negative value means up) | 
|---|
|  | 233 | function P:moveSelectionRow(relRow) | 
|---|
|  | 234 | self:moveSelection(relRow, "selectedRow", "selectedColumn", "rows", "columns", true) | 
|---|
|  | 235 | end | 
|---|
|  | 236 |  | 
|---|
|  | 237 | -- Moves the selection by a given number of columns (a positive value means right, a negative value means left) | 
|---|
|  | 238 | function P:moveSelectionColumn(relColumn) | 
|---|
|  | 239 | self:moveSelection(relColumn, "selectedColumn", "selectedRow", "columns", "rows", false) | 
|---|
|  | 240 | end | 
|---|
|  | 241 |  | 
|---|
|  | 242 | -- Generic move function, the values are determined at runtime depending on the arguments | 
|---|
|  | 243 | function P:moveSelection(relMove, selectedThis, selectedOther, limitThis, limitOther, isRow) | 
|---|
|  | 244 | if not self.buttons then | 
|---|
|  | 245 | return | 
|---|
|  | 246 | end | 
|---|
|  | 247 |  | 
|---|
|  | 248 | -- if there's no selection yet, prepare it such that the selection enters the table from the desired side | 
|---|
|  | 249 | if self.selectedRow > 0 or self.selectedColumn > 0 then | 
|---|
|  | 250 | self:setSelectedButtonsStateToNormal() | 
|---|
|  | 251 | else | 
|---|
|  | 252 | if relMove > 0 then | 
|---|
|  | 253 | self[selectedThis] = 0 | 
|---|
|  | 254 | self[selectedOther] = 1 | 
|---|
|  | 255 | elseif relMove < 0 then | 
|---|
|  | 256 | self[selectedThis] = self[limitThis] + 1 | 
|---|
|  | 257 | self[selectedOther] = 1 | 
|---|
|  | 258 | else | 
|---|
|  | 259 | return | 
|---|
|  | 260 | end | 
|---|
|  | 261 | end | 
|---|
|  | 262 |  | 
|---|
|  | 263 | -- move the selection according to the parameters | 
|---|
|  | 264 | self[selectedThis] = self[selectedThis] + relMove | 
|---|
|  | 265 |  | 
|---|
|  | 266 | -- wrap around on overflow or underflow | 
|---|
|  | 267 | while self[selectedThis] > self[limitThis] do self[selectedThis] = self[selectedThis] - self[limitThis] end | 
|---|
|  | 268 | while self[selectedThis] <= 0              do self[selectedThis] = self[selectedThis] + self[limitThis] end | 
|---|
|  | 269 |  | 
|---|
|  | 270 | -- if the button is deactivated, search the button closest to the desired location | 
|---|
|  | 271 | if self:getSelectedButton() == nil then | 
|---|
|  | 272 | local min = 1000000 | 
|---|
|  | 273 | local minV1, minV2 | 
|---|
|  | 274 | local limit, step | 
|---|
|  | 275 |  | 
|---|
|  | 276 | if relMove > 0 then | 
|---|
|  | 277 | limit = self[limitThis] | 
|---|
|  | 278 | step = 1 | 
|---|
|  | 279 | else | 
|---|
|  | 280 | limit = 1 | 
|---|
|  | 281 | step = -1 | 
|---|
|  | 282 | end | 
|---|
|  | 283 |  | 
|---|
|  | 284 | for v1 = self[selectedThis], limit, step do | 
|---|
|  | 285 | for v2 = 1, self[limitOther] do | 
|---|
|  | 286 | local button | 
|---|
|  | 287 | if isRow == true then | 
|---|
|  | 288 | button = self:getButton(v1, v2) | 
|---|
|  | 289 | else | 
|---|
|  | 290 | button = self:getButton(v2, v1) | 
|---|
|  | 291 | end | 
|---|
|  | 292 | if button then | 
|---|
|  | 293 | local distance | 
|---|
|  | 294 | if isRow == true then | 
|---|
|  | 295 | distance = math.sqrt((self[selectedThis] - v1)^2 + ((self[selectedOther] - v2) * self.ratio)^2) | 
|---|
|  | 296 | else | 
|---|
|  | 297 | distance = math.sqrt(((self[selectedThis] - v1) * self.ratio)^2 + (self[selectedOther] - v2)^2) | 
|---|
|  | 298 | end | 
|---|
|  | 299 | if distance < min then | 
|---|
|  | 300 | min = distance; minV1 = v1; minV2 = v2 | 
|---|
|  | 301 | end | 
|---|
|  | 302 | end | 
|---|
|  | 303 | end | 
|---|
|  | 304 | end | 
|---|
|  | 305 |  | 
|---|
|  | 306 | if minV1 and minV2 then | 
|---|
|  | 307 | self[selectedThis] = minV1 | 
|---|
|  | 308 | self[selectedOther] = minV2 | 
|---|
|  | 309 | elseif self:hasButtons() then | 
|---|
|  | 310 | -- no suitable button found - wrap around and search again | 
|---|
|  | 311 | if relMove > 0 then | 
|---|
|  | 312 | self[selectedThis] = 0 | 
|---|
|  | 313 | else | 
|---|
|  | 314 | self[selectedThis] = self[limitThis] + 1 | 
|---|
|  | 315 | end | 
|---|
|  | 316 | self:moveSelection(relMove, selectedThis, selectedOther, limitThis, limitOther, isRow) | 
|---|
|  | 317 | end | 
|---|
|  | 318 | end | 
|---|
|  | 319 |  | 
|---|
|  | 320 | self:setSelectedButtonsStateToSelected() | 
|---|
|  | 321 | end | 
|---|
|  | 322 |  | 
|---|
|  | 323 | -- Resets the selection | 
|---|
|  | 324 | function P:resetSelection() | 
|---|
|  | 325 | self:setSelectedButtonsStateToNormal() | 
|---|
|  | 326 |  | 
|---|
|  | 327 | self.selectedRow = 0 | 
|---|
|  | 328 | self.selectedColumn = 0 | 
|---|
|  | 329 | end | 
|---|
|  | 330 |  | 
|---|
|  | 331 | -- Checks if there's at least one button in the table | 
|---|
|  | 332 | function P:hasButtons() | 
|---|
|  | 333 | local count = 0 | 
|---|
|  | 334 | for r = 1, self.rows do | 
|---|
|  | 335 | for c = 1, self.columns do | 
|---|
|  | 336 | if self:getButton(r, c) then | 
|---|
|  | 337 | count = count + 1 | 
|---|
|  | 338 | end | 
|---|
|  | 339 | end | 
|---|
|  | 340 | end | 
|---|
|  | 341 |  | 
|---|
|  | 342 | return (count > 0) | 
|---|
|  | 343 | end | 
|---|
|  | 344 |  | 
|---|
|  | 345 | -- Determines if a button is selected | 
|---|
|  | 346 | function P:hasSelection() | 
|---|
|  | 347 | if self.selectedRow and self.selectedRow > 0 and self.selectedColumn and self.selectedColumn > 0 then | 
|---|
|  | 348 | return true | 
|---|
|  | 349 | else | 
|---|
|  | 350 | return false | 
|---|
|  | 351 | end | 
|---|
|  | 352 | end | 
|---|
|  | 353 |  | 
|---|
|  | 354 | -- Sets the selected button's state to normal | 
|---|
|  | 355 | function P:setSelectedButtonsStateToNormal() | 
|---|
|  | 356 | self:setSelectedButtonsState("Normal") | 
|---|
|  | 357 | end | 
|---|
|  | 358 |  | 
|---|
|  | 359 | -- Sets the selected button's state to selected | 
|---|
|  | 360 | function P:setSelectedButtonsStateToSelected() | 
|---|
|  | 361 | self:setSelectedButtonsState("Selected") | 
|---|
|  | 362 | end | 
|---|
|  | 363 |  | 
|---|
|  | 364 | -- Sets the selected button's state to pushed | 
|---|
|  | 365 | function P:setSelectedButtonsStateToPushed() | 
|---|
|  | 366 | self:setSelectedButtonsState("Pushed") | 
|---|
|  | 367 | end | 
|---|
|  | 368 |  | 
|---|
|  | 369 | -- Sets the selected button's state | 
|---|
|  | 370 | function P:setSelectedButtonsState(state) | 
|---|
|  | 371 | if self:getSelectedButton() then | 
|---|
|  | 372 | local element = self:getSelectedButton().button | 
|---|
|  | 373 | local offset = getElementStateOffset(element) | 
|---|
|  | 374 |  | 
|---|
|  | 375 | if offset then | 
|---|
|  | 376 | element:setProperty("NormalImageRightEdge",  string.sub(element:getProperty("NormalImageRightEdge"),  1, offset) .. state) | 
|---|
|  | 377 | element:setProperty("NormalImageLeftEdge",   string.sub(element:getProperty("NormalImageLeftEdge"),   1, offset) .. state) | 
|---|
|  | 378 | element:setProperty("NormalImageBackground", string.sub(element:getProperty("NormalImageBackground"), 1, offset) .. state) | 
|---|
|  | 379 | end | 
|---|
|  | 380 | end | 
|---|
|  | 381 | end | 
|---|
|  | 382 |  | 
|---|
|  | 383 | -- Gets the offset of the button's current state | 
|---|
|  | 384 | function getElementStateOffset(element) | 
|---|
|  | 385 | local property = element:getProperty("NormalImageRightEdge") | 
|---|
|  | 386 |  | 
|---|
|  | 387 | if string.sub(property, string.len(property) - 5, string.len(property)) == "Normal" then | 
|---|
|  | 388 | return -7 | 
|---|
|  | 389 | elseif string.sub(property, string.len(property) - 7, string.len(property)) == "Selected" then | 
|---|
|  | 390 | return -9 | 
|---|
|  | 391 | elseif string.sub(property, string.len(property) - 5, string.len(property)) == "Pushed" then | 
|---|
|  | 392 | return -7 | 
|---|
|  | 393 | else | 
|---|
|  | 394 | return nil | 
|---|
|  | 395 | end | 
|---|
|  | 396 | end | 
|---|
|  | 397 |  | 
|---|
| [5661] | 398 | return P | 
|---|