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
Line 
1-- GUISheet.lua
2
3local P = {}
4_G[_REQUIREDNAME or "GUISheet"] = P
5P.__index = P
6
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
12end
13
14-- Override this function if you need to do work on load
15function P:onLoad()
16end
17
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
26-- Override this function if you need to do work on quit
27function P:onQuit()
28end
29
30-- show function for the GUI
31function P:show()
32    self.window:show()
33    self.bVisible = true
34
35    -- set the selected button's state
36    self:setSelectedButtonsStateToSelected()
37
38    self:onShow()
39end
40
41-- hide function for the GUI
42function P:hide()
43    self.window:hide()
44    self.bVisible = false
45
46    self:onHide()
47end
48
49function P:quit()
50    -- reset the selected button
51    if self.buttons then
52        self:resetSelection()
53    end
54
55    self:onQuit()
56end
57
58function P:load()
59    -- Load the layout that describes the sheet
60    self.window = winMgr:loadWindowLayout(self.name .. ".layout")
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
67    self:onLoad()
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
75    return self
76end
77
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
82            self:moveSelectionRow(1)
83        elseif code == "200" then -- key up
84            self:moveSelectionRow(-1)
85        elseif code == "205" then -- key right
86            self:moveSelectionColumn(1)
87        elseif code == "203" then -- key left
88            self:moveSelectionColumn(-1)
89        elseif code == "28"  then -- key enter
90            self:pressSelectedButton()
91        end
92    end
93
94    self.onKeyPressed()
95end
96
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
107function P:initButtons(rows, columns)
108    self.rows = rows
109    self.columns = columns
110    self.buttons = {}
111    self.selectedRow = 0
112    self.selectedColumn = 0
113    self.ratio = 1
114end
115
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
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)
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)
130
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
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)
150    if self.buttons then
151        return self.buttons[(row - 1) * self.columns + (column - 1)]
152    else
153        return nil
154    end
155end
156
157-- Returns the selected button
158function P:getSelectedButton()
159    if self:hasSelection() then
160        return self:getButton(self.selectedRow, self.selectedColumn)
161    else
162        return nil
163    end
164end
165
166-- Presses the selected button if any
167function P:pressSelectedButton()
168    if self:getSelectedButton() then
169        self.pressedEnter = true
170        self:getSelectedButton().callback()
171        self.pressedEnter = false
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)
177    if not self.buttons then
178        return
179    end
180
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
183    self:setSelectedButtonsStateToNormal()
184
185    self.selectedRow = row
186    self.selectedColumn = column
187
188    self:setSelectedButtonsStateToSelected()
189end
190
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
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)
236    if not self.buttons then
237        return
238    end
239
240    -- if there's no selection yet, prepare it such that the selection enters the table from the desired side
241    if self.selectedRow > 0 or self.selectedColumn > 0 then
242        self:setSelectedButtonsStateToNormal()
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
252        end
253    end
254
255    -- move the selection according to the parameters
256    self[selectedThis] = self[selectedThis] + relMove
257
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
261
262    -- if the button is deactivated, search the button closest to the desired location
263    if self:getSelectedButton() == nil then
264        local min = 1000000
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
310    end
311
312    self:setSelectedButtonsStateToSelected()
313end
314
315-- Resets the selection
316function P:resetSelection()
317    self:setSelectedButtonsStateToNormal()
318
319    self.selectedRow = 0
320    self.selectedColumn = 0
321end
322
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
337-- Determines if a button is selected
338function P:hasSelection()
339    if self.selectedRow and self.selectedRow > 0 and self.selectedColumn and self.selectedColumn > 0 then
340        return true
341    else
342        return false
343    end
344end
345
346-- Sets the selected button's state to normal
347function P:setSelectedButtonsStateToNormal()
348    self:setSelectedButtonsState("Normal")
349end
350
351-- Sets the selected button's state to selected
352function P:setSelectedButtonsStateToSelected()
353    self:setSelectedButtonsState("Selected")
354end
355
356-- Sets the selected button's state to pushed
357function P:setSelectedButtonsStateToPushed()
358    self:setSelectedButtonsState("Pushed")
359end
360
361-- Sets the selected button's state
362function P:setSelectedButtonsState(state)
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
390return P
Note: See TracBrowser for help on using the repository browser.