Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/usability/data/gui/scripts/GUISheet.lua @ 7928

Last change on this file since 7928 was 7928, checked in by landauf, 13 years ago

more improvements for keyboard control of menus:

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