LUADOC - Farming Simulator 19

Script v1.7.1.0

Engine v1.7.1.0

Foundation Reference

FocusManager

Description
The FocusManager controls which element in the menu system is currently focused and allows menu control with only keyboard or gamepad. For each participating gui element the focus state and the next focused gui element in each direction is stored. This data is set up directly in the xml file of the gui screen and loaded through the loadElementFromXML() function or manually loaded within code by using the loadElementFromCustomValues() method. Focus handling is independent for every screen in the GUI. To swap screens the setGui() method has to be used. The focus system is then controlled with 5 actions: MENU_UP, MENU_DOWN, MENU_RIGHT and MENU_LEFT to change the currently focused element in the specified direction and MENU_ACCEPT to activate the currently focused element. When using dynamically changing objects which cannot be set directly in the XML file of the screen the method createLinkageSystemForElements() can be used to set up direction links between the passed elements automatically.
Functions

checkElementDistance

Description
Checks the distance between two GuiElements with the aim of incrementally finding the closest other element in a direction within a screen view.
Definition
checkElementDistance(curElement Current, other Other, dirX Scan, dirY Scan, curElementOffsetY Position, closestOther Previously, closestDistanceSq Squared)
Arguments
curElementCurrentchecking GuiElement
otherOtherGuiElement to compare
dirXScandirection vector x component, normalized to unit length
dirYScandirection vector y component, normalized to unit length
curElementOffsetYPositiony offset of current element's bounding volume, used when checking for wrap-around
closestOtherPreviouslyclosest other GuiElement
closestDistanceSqSquareddistance from the current checking element to the previously closest other GuiElement
Code
483function FocusManager.checkElementDistance(curElement, other, dirX, dirY, curElementOffsetY, closestOther, closestDistanceSq)
484 local retOther = closestOther
485 local retDistSq = closestDistanceSq
486
487 local elementBox = curElement:getBorders()
488 elementBox[2] = elementBox[2] + curElementOffsetY
489 elementBox[4] = elementBox[4] + curElementOffsetY
490 local elementCenter = curElement:getCenter()
491 elementCenter[2] = elementCenter[2] + curElementOffsetY
492
493 if other ~= curElement and not other.disabled and other:getIsVisible() and other:canReceiveFocus() and not (other:isChildOf(curElement) or curElement:isChildOf(other)) then
494 local otherBox = other:getBorders()
495 local otherCenter = other:getCenter()
496
497 -- get vector between bounding box points
498 local elementDirX, elementDirY = FocusManager.getShortestBoundingBoxVector(elementBox, otherBox, otherCenter)
499
500 -- test direction and distance of bounding box points
501 local boxDistanceSq = MathUtil.vector2LengthSq(elementDirX, elementDirY)
502 local dot = MathUtil.dotProduct(elementDirX, elementDirY, 0, dirX, dirY, 0)
503 if boxDistanceSq < FocusManager.EPSILON then -- boundaries touch, use center points for direction check
504 dot = MathUtil.dotProduct(otherCenter[1] - elementCenter[1], otherCenter[2] - elementCenter[2], 0, dirX, dirY, 0)
505 end
506
507 if dot > 0 then -- other element lies in scanning direction
508 local useOther = false
509
510 -- when two elements are equally close, choose the one further up (-y) and/or further left (-x)
511 if closestOther and math.abs(closestDistanceSq - boxDistanceSq) < FocusManager.EPSILON then
512 -- also compare dot products
513 local closestBox = closestOther:getBorders()
514 local closestCenter = closestOther:getCenter()
515 local toClosestX, toClosestY = FocusManager.getShortestBoundingBoxVector(elementBox, closestBox, closestCenter)
516 local closestDot = MathUtil.dotProduct(toClosestX, toClosestY, 0, dirX, dirY, 0)
517
518 if math.abs(closestDot - dot) < FocusManager.EPSILON then -- same distance and angle as previous best
519 -- when going up, go right first, etc. --> ensure symmetric paths in all directions
520 if dirY > 0 then
521 useOther = other.absPosition[1] > closestOther.absPosition[1]
522 elseif dirY < 0 then
523 useOther = other.absPosition[1] < closestOther.absPosition[1]
524 elseif dirX > 0 then
525 useOther = other.absPosition[2] > closestOther.absPosition[2]
526 elseif dirX < 0 then
527 useOther = other.absPosition[2] < closestOther.absPosition[2]
528 end
529 elseif dot > closestDot then -- when distance is equal and angles differ, prefer the one closer to the movement direction
530 useOther = true
531 end
532 elseif boxDistanceSq < closestDistanceSq then
533 useOther = true
534 end
535
536 if useOther then
537 retOther = other
538 retDistSq = boxDistanceSq
539 end
540 end
541 end
542
543 return retOther, retDistSq
544end

getClosestPointOnBoundingBox

Description
Given a point and bounding box, get the closest other point on the bounding box circumference. If the point lies within the bounding box, it is returned unchanged.
Definition
getClosestPointOnBoundingBox(x Point, y Point, boxMinX Bounding, boxMinY Bounding, boxMaxX Bounding, boxMaxY Bounding)
Arguments
xPointX
yPointY
boxMinXBoundingbox minimum point X
boxMinYBoundingbox minimum point Y
boxMaxXBoundingbox maximum point X
boxMaxYBoundingbox maximum point Y
Return Values
Closestpointx, y
Code
437function FocusManager.getClosestPointOnBoundingBox(x, y, boxMinX, boxMinY, boxMaxX, boxMaxY)
438 local px, py = x, y
439 if x < boxMinX then
440 px = boxMinX
441 elseif x > boxMaxX then
442 px = boxMaxX
443 end
444
445 if y < boxMinY then
446 py = boxMinY
447 elseif y > boxMaxY then
448 py = boxMaxY
449 end
450
451 return px, py
452end

getDirectionForAxisValue

Description
Get a direction value for a given menu input action and value
Definition
getDirectionForAxisValue()
Code
340function FocusManager.getDirectionForAxisValue(inputAction, value)
341 if value == nil then
342 return nil
343 end
344
345 local direction = nil
346 if inputAction == InputAction.MENU_AXIS_UP_DOWN then
347 if value < 0 then
348 direction = FocusManager.BOTTOM
349 elseif value > 0 then
350 direction = FocusManager.TOP
351 end
352 elseif inputAction == InputAction.MENU_AXIS_LEFT_RIGHT then
353 if value < 0 then
354 direction = FocusManager.LEFT
355 elseif value > 0 then
356 direction = FocusManager.RIGHT
357 end
358 end
359
360 return direction
361end

getElementById

Description
Get a focusable GuiElement in the current view by its ID.
Definition
getElementById()
Code
108function FocusManager:getElementById(id)
109 return self.currentFocusData.idToElementMapping[id]
110end

getFocusedElement

Description
Get the currently focused GuiElement
Definition
getFocusedElement()
Code
114function FocusManager:getFocusedElement()
115 return self.currentFocusData.focusElement
116end

getFocusOverrideFunction

Description
Get a closure override function for elements' getFocusOverride() methods.
Definition
getFocusOverrideFunction(forDirections List, substitute Element, useSubstituteForFocus (Optional))
Arguments
forDirectionsListof directions to override
substituteElementto substitute as focus target in overridden direction
useSubstituteForFocus(Optional)If true, the substitute parameter will be used as the origin for finding the next focus target in the overridden direction.
Code
881function FocusManager:getFocusOverrideFunction(forDirections, substitute, useSubstituteForFocus)
882 if forDirections == nil or #forDirections < 1 then
883 return function(elementSelf, dir) return false, nil end
884 end
885
886 local f = function(elementSelf, dir)
887 for _, overrideDirection in pairs(forDirections) do
888 if dir == overrideDirection then
889 if useSubstituteForFocus then
890 local next = self:getNextFocusElement(substitute, dir)
891 if next then
892 return true, next
893 end
894 else
895 return true, substitute
896 end
897 end
898 end
899
900 return false, nil
901 end
902
903 return f
904end

getNestedFocusTarget

Description
Get an element's focus target at the deepest nesting depth, e.g. when multiple nested layouts point down to their child elements until only a single element is left which points to itself.
Definition
getNestedFocusTarget(element GuiElement, direction Focus)
Arguments
elementGuiElementwhose focus target needs to be retrieved
directionFocusnavigation direction
Return Values
Focustarget
Code
605function FocusManager.getNestedFocusTarget(element, direction)
606 local target = element
607 local prevTarget = nil
608 while target and prevTarget ~= target do
609 prevTarget = target
610 target = target:getFocusTarget(FocusManager.OPPOSING_DIRECTIONS[direction], direction)
611 end
612
613 return target
614end

getNextFocusElement

Description
Find the next other element to the one provided in a given navigation direction
Definition
getNextFocusElement(element GUI, direction Direction)
Arguments
elementGUIelement which needs a focus link
directionDirectionconstant [TOP | BOTTOM | LEFT | RIGHT]
Return Values
NextGUIelement in given direction which can be linked, actual scanning direction used (may change in wrap around scenarios)
Code
551function FocusManager:getNextFocusElement(element, direction)
552 -- if there is a configured next element, return that
553 local nextFocusId = element.focusChangeData[direction]
554 if nextFocusId then
555 return self.currentFocusData.idToElementMapping[nextFocusId], direction
556 end
557 -- otherwise, find the next one based on proximity:
558 local dirX, dirY = unpack(FocusManager.DIRECTION_VECTORS[direction])
559
560 local closestOther = nil
561 local closestDistance = math.huge
562
563 for _, other in pairs(self.currentFocusData.idToElementMapping) do
564 closestOther, closestDistance = FocusManager.checkElementDistance(element, other, dirX, dirY, 0, closestOther, closestDistance)
565 end
566
567 if closestOther == nil then
568 -- wrap around
569 if direction == FocusManager.LEFT then
570 -- look up instead
571 closestOther, direction = self:getNextFocusElement(element, FocusManager.TOP)
572 elseif direction == FocusManager.RIGHT then
573 -- look down instead
574 closestOther, direction = self:getNextFocusElement(element, FocusManager.BOTTOM)
575 else
576 -- get the right test elements
577 local validWrapElements = self.currentFocusData.idToElementMapping -- screen wrap around
578 if element.parent and element.parent.wrapAround then -- local box/area wrap around if required
579 validWrapElements = element.parent.elements
580 end
581
582 local wrapOffsetY = 0
583 if direction == FocusManager.TOP then
584 wrapOffsetY = -1.2 - element.size[2] -- below screen must be <-1 to work in all cases, even though screen space is defined within [0, 1]
585 elseif direction == FocusManager.BOTTOM then
586 wrapOffsetY = 1.2 + element.size[2] -- above screen
587 end
588
589 -- try wrapping around
590 for _, other in pairs(validWrapElements) do
591 closestOther, closestDistance = FocusManager.checkElementDistance(element, other, dirX, dirY, wrapOffsetY, closestOther, closestDistance)
592 end
593 end
594 end
595
596 return closestOther, direction
597end

getShortestBoundingBoxVector

Description
Calculate the shortest connecting line segment between two bounding boxes. Overlapping boxes will result in flipped directions, so take care.
Definition
getShortestBoundingBoxVector()
Code
457function FocusManager.getShortestBoundingBoxVector(elementBox, otherBox, otherCenter)
458 local ePointX, ePointY = FocusManager.getClosestPointOnBoundingBox(
459 otherCenter[1], otherCenter[2],
460 elementBox[1], elementBox[2], elementBox[3], elementBox[4])
461
462 local oPointX, oPointY = FocusManager.getClosestPointOnBoundingBox(
463 ePointX, ePointY, -- use the previously calculated bounding box point here to get the closest boundary distance
464 otherBox[1], otherBox[2], otherBox[3], otherBox[4])
465
466 -- get vector between bounding box points
467 local elementDirX = oPointX - ePointX
468 local elementDirY = oPointY - ePointY
469
470 return elementDirX, elementDirY
471end

hasFocus

Description
Determine if a GuiElement is currently focused.
Definition
hasFocus()
Code
871function FocusManager:hasFocus(element)
872 return ((self.currentFocusData.focusElement == element) and (element.focusActive))
873end

inputEvent

Description
Handles input and changes focus if required and possible.
Definition
inputEvent(action Name, value Input, eventUsed Usage)
Arguments
actionNameof navigation action which triggered the event, see InputAction
valueInputvalue [-1, 1]
eventUsedUsageflag, no action is taken if this is true
Return Values
Trueifthe input event has been consumed, false otherwise
Code
301function FocusManager:inputEvent(action, value, eventUsed)
302 local element = self.currentFocusData.focusElement
303
304 local pressedUp = false
305 local pressedDown = false
306 local pressedLeft = false
307 local pressedRight = false
308 local pressedAccept = false
309
310 pressedUp = action == InputAction.MENU_AXIS_UP_DOWN and value > g_analogStickVTolerance
311 pressedDown = action == InputAction.MENU_AXIS_UP_DOWN and value < -g_analogStickVTolerance
312 pressedLeft = action == InputAction.MENU_AXIS_LEFT_RIGHT and value < -g_analogStickHTolerance
313 pressedRight = action == InputAction.MENU_AXIS_LEFT_RIGHT and value > g_analogStickHTolerance
314
315 if action == InputAction.MENU_AXIS_UP_DOWN then
316 self:updateFocus(element, pressedUp, FocusManager.TOP, eventUsed)
317 self:updateFocus(element, pressedDown, FocusManager.BOTTOM, eventUsed)
318 elseif action == InputAction.MENU_AXIS_LEFT_RIGHT then
319 self:updateFocus(element, pressedLeft, FocusManager.LEFT, eventUsed)
320 self:updateFocus(element, pressedRight, FocusManager.RIGHT, eventUsed)
321 end
322
323 if not eventUsed and element ~= nil and not element.needExternalClick then
324 pressedAccept = action == InputAction.MENU_ACCEPT
325 if pressedAccept and not self:isFocusInputLocked(action) then
326 -- elements can get unfocused, accept is only allowed for currently focused and visible elements
327 if element.focusActive and element:getIsVisible() then
328 self.focusSystemMadeChanges = true
329 element:onFocusActivate()
330 self.focusSystemMadeChanges = false
331 end
332 end
333 end
334
335 return eventUsed or pressedUp or pressedDown or pressedLeft or pressedRight or pressedAccept
336end

isDirectionLocked

Description
Determine if focus navigation in a given direction is currently locked.
Definition
isDirectionLocked(direction Navigation)
Arguments
directionNavigationdirection as defined in constants
Return Values
Trueifnavigation in given direction is locked
Code
865function FocusManager:isDirectionLocked(direction)
866 return self.lastInput[direction] ~= nil
867end

isFocusInputLocked

Description
Checks if the focus manager has an input lock on input.
Definition
isFocusInputLocked(inputAxis InputAction, value Axis, True if)
Arguments
inputAxisInputActionaxis or action code
valueAxisvalue [-1, 1] or nil if not a directional axis
Trueiflocked, false otherwise
Code
368function FocusManager:isFocusInputLocked(inputAxis, value)
369 local key = FocusManager.getDirectionForAxisValue(inputAxis, value)
370 if key == nil and inputAxis ~= InputAction.MENU_AXIS_UP_DOWN and inputAxis ~= InputAction.MENU_AXIS_LEFT_RIGHT then
371 key = inputAxis
372 end
373
374 if self.lastInput[key] and self.lockUntil[key] > g_time then
375 return true
376 else
377 return false
378 end
379end

isLocked

Description
Check if focus input is locked.
Definition
isLocked()
Code
857function FocusManager:isLocked()
858 return FocusManager.isFocusLocked
859end

linkElements

Description
Links an element's focus navigation to another element for a given direction. The link is unidirectional from source to target. If bi-directional links are desired, call this method again with swapped arguments.
Definition
linkElements(sourceElement Source, direction Navigation, targetElement Target)
Arguments
sourceElementSourceelement which receives the focus link.
directionNavigationdirection for the link, is not required to be the actual visual direction.
targetElementTargetelement
Code
291function FocusManager:linkElements(sourceElement, direction, targetElement)
292 sourceElement.focusChangeData[direction] = targetElement.focusId
293end

loadElementFromCustomValues

Description
Add an element to the focus system with custom values. The caller should ensure that explicitly set focus IDs are unique. If a duplicate ID is encountered, only the first element with that focus ID is considered for focusing. The method returns a boolean value to indicate any problems with data assignment. Callers can evaluate the value to check if the given parameters were valid. If in doubt or when no elaborate focus navigation is needed, rely on automatic focus ID generation by omitting the ID parameter (or set it to nil).
Definition
loadElementFromCustomValues(element Element, focusId Focus, focusChangeData Custom, focusActive If, isAlwaysFocusedOnOpen If)
Arguments
elementElementto add to focus system
focusIdFocusID for element
focusChangeDataCustomfocus navigation data for the element (map of direction to focus ID)
focusActiveIftrue, the element should be focused right now
isAlwaysFocusedOnOpenIftrue, the element is supposed to be focused when its parent view is opened.
Return Values
Trueifthe element and all of its children could be set up with the given values, false otherwise.
Code
217function FocusManager:loadElementFromCustomValues(element, focusId, focusChangeData, focusActive, isAlwaysFocusedOnOpen)
218 if focusId and self.currentFocusData.idToElementMapping[focusId] then
219 return false -- ignore element, caller is responsible for sensible ID assignment when specified
220 end
221
222 if not element.focusId then
223 if not focusId then
224 focusId = FocusManager.serveAutoFocusId()
225 end
226
227 element.focusId = focusId
228 end
229
230 element.focusChangeData = element.focusChangeData or focusChangeData or {}
231 element.focusActive = focusActive
232 element.isAlwaysFocusedOnOpen = isAlwaysFocusedOnOpen
233
234 FocusManager.allElements[element] = self.currentGui
235 self.currentFocusData.idToElementMapping[element.focusId] = element
236
237 if isAlwaysFocusedOnOpen then
238 self.currentFocusData.initialFocusElement = element
239 end
240
241 if focusActive then
242 self:setFocus(element)
243 end
244
245 local success = true
246 for _, child in pairs(element.elements) do
247 success = success and self:loadElementFromCustomValues(child, child.focusId, child.focusChangeData, child.focusActive, child.isAlwaysFocusedOnOpen)
248 end
249
250 return success
251end

loadElementFromXML

Description
Load GuiElement focus data from its XML definition. This is called at the end of GuiElement:loadFromXML().
Definition
loadElementFromXML()
Code
130function FocusManager:loadElementFromXML(xmlFile, xmlBaseNode, element)
131 local focusId = getXMLString(xmlFile, xmlBaseNode.."#focusId")
132 if not focusId then
133 focusId = FocusManager.serveAutoFocusId()
134 end
135
136 element.focusId = focusId
137 element.focusChangeData = {}
138 -- assign focus change data from configuration if it has not been set by code:
139 if not element.focusChangeData[FocusManager.TOP] then
140 element.focusChangeData[FocusManager.TOP] = getXMLString(xmlFile, xmlBaseNode.."#focusChangeTop")
141 end
142
143 if not element.focusChangeData[FocusManager.BOTTOM] then
144 element.focusChangeData[FocusManager.BOTTOM] = getXMLString(xmlFile, xmlBaseNode.."#focusChangeBottom")
145 end
146
147 if not element.focusChangeData[FocusManager.LEFT] then
148 element.focusChangeData[FocusManager.LEFT] = getXMLString(xmlFile, xmlBaseNode.."#focusChangeLeft")
149 end
150
151 if not element.focusChangeData[FocusManager.RIGHT] then
152 element.focusChangeData[FocusManager.RIGHT] = getXMLString(xmlFile, xmlBaseNode.."#focusChangeRight")
153 end
154
155 if GS_IS_CONSOLE_VERSION then
156 element.focusChangeData[FocusManager.TOP] = Utils.getNoNil(getXMLString(xmlFile, xmlBaseNode.."#consoleFocusChangeTop"), element.focusChangeData[FocusManager.TOP])
157 element.focusChangeData[FocusManager.BOTTOM] = Utils.getNoNil(getXMLString(xmlFile, xmlBaseNode.."#consoleFocusChangeBottom"), element.focusChangeData[FocusManager.BOTTOM])
158 element.focusChangeData[FocusManager.LEFT] = Utils.getNoNil(getXMLString(xmlFile, xmlBaseNode.."#consoleFocusChangeLeft"), element.focusChangeData[FocusManager.LEFT])
159 element.focusChangeData[FocusManager.RIGHT] = Utils.getNoNil(getXMLString(xmlFile, xmlBaseNode.."#consoleFocusChangeRight"), element.focusChangeData[FocusManager.RIGHT])
160
161 if element.focusChangeData[FocusManager.TOP] == "nil" then
162 element.focusChangeData[FocusManager.TOP] = nil
163 end
164
165 if element.focusChangeData[FocusManager.BOTTOM] == "nil" then
166 element.focusChangeData[FocusManager.BOTTOM] = nil
167 end
168
169 if element.focusChangeData[FocusManager.LEFT] == "nil" then
170 element.focusChangeData[FocusManager.LEFT] = nil
171 end
172
173 if element.focusChangeData[FocusManager.RIGHT] == "nil" then
174 element.focusChangeData[FocusManager.RIGHT] = nil
175 end
176 end
177
178 element.focusActive = (getXMLString(xmlFile, xmlBaseNode.."#focusInit") ~= nil)
179 local isAlwaysFocusedOnOpen = (getXMLString(xmlFile, xmlBaseNode.."#focusInit") == "onOpen")
180 element.isAlwaysFocusedOnOpen = isAlwaysFocusedOnOpen
181
182 local focusChangeOverride = getXMLString(xmlFile, xmlBaseNode.."#focusChangeOverride")
183 if focusChangeOverride then
184 if element.target and element.target.focusChangeOverride then
185 element.focusChangeOverride = element.target[focusChangeOverride]
186 else
187 self.focusChangeOverride = ClassUtil.getFunction(focusChangeOverride)
188 end
189 end
190
191 FocusManager.allElements[element] = self.currentGui
192 self.currentFocusData.idToElementMapping[focusId] = element
193
194 if isAlwaysFocusedOnOpen then
195 self.currentFocusData.initialFocusElement = element
196 self:setFocus(element)
197 else
198 if not self.currentFocusData.focusElement then
199 self.currentFocusData.focusElement = element
200 end
201 end
202end

lockFocusInput

Description
Locks a given input axis action's input for a time. Until the delay has passed, the focus manager will not react to that input.
Definition
lockFocusInput(inputAxis InputAction, delay Delay, value Axis)
Arguments
inputAxisInputActionaxis or action code
delayDelayin ms
valueAxisvalue [-1, 1], only relevant to identify directional axes
Code
387function FocusManager:lockFocusInput(axisAction, delay, value)
388 local key = FocusManager.getDirectionForAxisValue(axisAction, value)
389 if not key and axisAction ~= InputAction.MENU_AXIS_UP_DOWN and axisAction ~= InputAction.MENU_AXIS_LEFT_RIGHT then
390 key = axisAction
391 end
392
393 self.lastInput[key] = g_time
394 self.lockUntil[key] = g_time + delay
395end

releaseLock

Description
Release the global focus input lock.
Definition
releaseLock()
Code
851function FocusManager:releaseLock()
852 FocusManager.isFocusLocked = false
853end

releaseMovementFocusInput

Description
Release a focus movement input lock on an action. Called by the UI input handling code. Avoid calling this for anything else.
Definition
releaseMovementFocusInput(action Focus)
Arguments
actionFocusmovement input action name
Code
401function FocusManager:releaseMovementFocusInput(action)
402 -- on input release we do not have a direction input value, need to clear lock for both directions on axes:
403 if action == InputAction.MENU_AXIS_LEFT_RIGHT then
404 self.lastInput[FocusManager.LEFT] = nil
405 self.lockUntil[FocusManager.LEFT] = nil
406 self.lastInput[FocusManager.RIGHT] = nil
407 self.lockUntil[FocusManager.RIGHT] = nil
408 elseif action == InputAction.MENU_AXIS_UP_DOWN then
409 self.lastInput[FocusManager.TOP] = nil
410 self.lockUntil[FocusManager.TOP] = nil
411 self.lastInput[FocusManager.BOTTOM] = nil
412 self.lockUntil[FocusManager.BOTTOM] = nil
413 end
414end

removeElement

Description
Remove a GuiElement from the current focus context.
Definition
removeElement()
Code
255function FocusManager:removeElement(element)
256 if not element.focusId then
257 return
258 end
259
260 for _, child in pairs(element.elements) do
261 self:removeElement(child)
262 end
263
264 if element.focusActive then
265 element:onFocusLeave()
266 FocusManager:unsetFocus(element)
267 end
268
269 if FocusManager.allElements[element] ~= nil then
270 local guiItWasAddedTo = FocusManager.allElements[element]
271 if self.currentGui ~= guiItWasAddedTo then
272 -- log("FOCUS LEAK: Found element", element, "in '", guiItWasAddedTo, "' instead of '", self.currentGui,"'")
273 self.guiFocusData[guiItWasAddedTo].idToElementMapping[element.focusId] = nil
274 end
275
276 FocusManager.allElements[element] = nil -- remove
277 end
278
279 self.currentFocusData.idToElementMapping[element.focusId] = nil
280 element.focusId = nil
281 element.focusChangeData = {}
282end

requireLock

Description
Globally lock focus input.
Definition
requireLock()
Code
845function FocusManager:requireLock()
846 FocusManager.isFocusLocked = true
847end

resetFocusInputLocks

Description
Reset all locks of focus input.
Definition
resetFocusInputLocks()
Code
418function FocusManager:resetFocusInputLocks()
419 for k, _ in pairs(self.lastInput) do
420 self.lastInput[k] = nil
421 end
422 for k, _ in pairs(self.lockUntil) do
423 self.lockUntil[k] = 0
424 end
425end

serveAutoFocusId

Description
Get a new automatic focus ID. It's based on a simple integer increment and will be unique unless billions of elements require an ID.
Definition
serveAutoFocusId()
Code
121function FocusManager.serveAutoFocusId()
122 local focusId = string.format("focusAuto_%d", FocusManager.autoIDcount)
123 FocusManager.autoIDcount = FocusManager.autoIDcount + 1
124 return focusId
125end

setElementFocusOverlayState

Description
Set an elements focus overlay state for displaying.
Definition
setElementFocusOverlayState(element Target, isFocus If, handlePreviousState [optional])
Arguments
elementTargetelement
isFocusIftrue, the element's state will be set to focused. Otherwise, it's state will be either restored or set to normal.
handlePreviousState[optional]If true or undefined, makes the element store its previous overlay state before modification or restore it when isFocused is false.
Code
822function FocusManager:setElementFocusOverlayState(element, isFocused, handlePreviousState)
823 if handlePreviousState == nil then
824 handlePreviousState = true
825 end
826
827 if isFocused then
828 if handlePreviousState and element:getOverlayState() ~= GuiOverlay.STATE_NORMAL then
829 element:storeOverlayState()
830 end
831 element:setOverlayState(GuiOverlay.STATE_FOCUSED)
832 else
833 if handlePreviousState then
834 element:restoreOverlayState()
835 end
836
837 if element:getOverlayState() == GuiOverlay.STATE_FOCUSED then -- could still be focused state after restore, just set to normal
838 element:setOverlayState(GuiOverlay.STATE_NORMAL)
839 end
840 end
841end

setFocus

Description
Set focus on a GuiElement or its focus target. Applies overlay state and triggers onFocusEnter() on the target.
Definition
setFocus(element Element, direction Focus, ... Variable)
Arguments
elementElementwhose focus target (usually itself) receives focus.
directionFocusnavigation direction
...Variablearguments to pass on to the onFocusEnter callback of the target element
Return Values
Trueiffocus has changed, false otherwise
Code
753function FocusManager:setFocus(element, direction, ...)
754 if FocusManager.isFocusLocked or element == nil or not element:canReceiveFocus() then
755 return false
756 end
757
758 -- get the element's focus target (or a descendant's) to return
759 local targetElement = FocusManager.getNestedFocusTarget(element, direction)
760 if targetElement.targetName ~= self.currentGui then
761 return false
762 end
763
764 if self.currentFocusData.focusElement and
765 self.currentFocusData.focusElement == targetElement and
766 self.currentFocusData.focusElement.focusActive then
767 -- the passed element already has focus
768 return false
769 end
770
771 -- clear focus and highlight on previous elements
772 if self.currentFocusData.focusElement ~= nil then
773 self:unsetFocus(self.currentFocusData.focusElement)
774 self:unsetHighlight(self.currentFocusData.highlightElement)
775 end
776
777 -- set focus of newly focused element
778 targetElement.focusActive = true
779 self.currentFocusData.focusElement = targetElement
780 targetElement:onFocusEnter(...)
781
782 if FocusManager.DEBUG then
783 log("focus changed to element", targetElement, "; ID:", targetElement.id, "; profile:", targetElement.profile, "; type:", targetElement.typeName)
784 end
785
786 self:setElementFocusOverlayState(targetElement, true)
787 if not element:getSoundSuppressed() and element:getIsVisible() and element.playHoverSoundOnFocus ~= false and not element.soundDisabled then
788 self.soundPlayer:playSample(GuiSoundPlayer.SOUND_SAMPLES.HOVER)
789 end
790
791 return true
792end

setGui

Description
Set the active GUI for focus input.
Definition
setGui(gui Screen)
Arguments
guiScreenroot GuiElement
Code
73function FocusManager:setGui(gui)
74 -- reset old gui focus
75 if self.currentFocusData then
76 local focusElement = self.currentFocusData.focusElement
77 if focusElement then
78 self:unsetFocus(focusElement)
79 end
80 end
81
82 -- set(up) new gui focus
83 self.currentGui = gui
84 self.currentFocusData = self.guiFocusData[gui]
85 if not self.currentFocusData then
86 self.guiFocusData[gui] = {}
87 self.guiFocusData[gui].idToElementMapping = {} -- all elements
88 self.currentFocusData = self.guiFocusData[gui]
89 else
90 local focusElement = self.currentFocusData.initialFocusElement or self.currentFocusData.focusElement
91 if focusElement ~= nil then
92 self:setFocus(focusElement)
93 end
94 end
95
96 -- reset delay locks
97 self:resetFocusInputLocks()
98end

setHighlight

Description
Activate a highlight on an element. Highlighted elements are only visually marked and do not receive focus activation. Only one element will be highlighted at any time, usually corresponding to the current mouse over target.
Definition
setHighlight(element Element)
Arguments
elementElementto be highlighted.
Code
708function FocusManager:setHighlight(element)
709 -- check if element has highlight already
710 if self.currentFocusData.highlightElement and self.currentFocusData.highlightElement == element then
711 return
712 end
713
714 -- unset highlight of currently highlighted element
715 self:unsetHighlight(self.currentFocusData.highlightElement)
716
717 if not (self.currentFocusData.focusElement and self.currentFocusData.focusElement == element) then
718 -- set highlight of new element
719 self.currentFocusData.highlightElement = element
720 element:storeOverlayState()
721 element:setOverlayState(GuiOverlay.STATE_HIGHLIGHTED)
722 element:onHighlight()
723
724 if not element:getSoundSuppressed() and element:getIsVisible() and element.playHoverSoundOnFocus ~= false and not element.soundDisabled then
725 self.soundPlayer:playSample(GuiSoundPlayer.SOUND_SAMPLES.HOVER)
726 end
727 end
728end

setSoundPlayer

Description
Definition
setSoundPlayer()
Code
102function FocusManager:setSoundPlayer(guiSoundPlayer)
103 self.soundPlayer = guiSoundPlayer
104end

unsetFocus

Description
Removes focus from an element. Applies overlay state and triggers onFocusLeave() on the target.
Definition
unsetFocus(element Element, ... Variable)
Arguments
elementElementwhich should lose focus
...Variablearguments to pass on to the onFocusLeave callback of the target element
Code
799function FocusManager:unsetFocus(element, ...)
800 local prevFocusElement = self.currentFocusData.focusElement
801 if prevFocusElement ~= element then
802 -- the element is not focused
803 return
804 end
805
806 if not element.focusActive then
807 -- the element has already lost focus
808 return
809 end
810
811 prevFocusElement.focusActive = false
812 self:setElementFocusOverlayState(prevFocusElement, false)
813
814 prevFocusElement:onFocusLeave(...) -- call focus leave last, can override overlay state if desired
815end

unsetHighlight

Description
Remove highlight status from an element.
Definition
unsetHighlight(element Highlighted)
Arguments
elementHighlightedelement to revert
Code
733function FocusManager:unsetHighlight(element)
734 if self.currentFocusData.highlightElement and self.currentFocusData.highlightElement == element then
735 local prevState = self.currentFocusData.highlightElement:getOverlayState()
736 if prevState == GuiOverlay.STATE_HIGHLIGHTED then
737 self.currentFocusData.highlightElement:setOverlayState(GuiOverlay.STATE_NORMAL)
738 self.currentFocusData.highlightElement:restoreOverlayState() -- also try restoring
739 end
740
741 self.currentFocusData.highlightElement:onHighlightRemove()
742 self.currentFocusData.highlightElement = nil
743 end
744end

updateFocus

Description
Update the current focus target.
Definition
updateFocus(element GuiElement, isFocusMoving Only, direction Focus, updateOnly If)
Arguments
elementGuiElementwhich should be the new focus target
isFocusMovingOnlymove focus if this is true
directionFocusnavigation movement direction, one of FocusManager.[TOP | BOTTOM | LEFT | RIGHT]
updateOnlyIftrue, only updates the lock state of focus movement for the given parameters
Code
622function FocusManager:updateFocus(element, isFocusMoving, direction, updateOnly)
623 if element == nil then
624 return
625 end
626
627 if isFocusMoving then
628 local notFirst = false
629 if self.lastInput[direction] then
630 if self.lockUntil[direction] <= g_time then
631 self.lastInput[direction] = nil -- release lock
632 self.lockJustReleased = true
633 end
634 -- movement not allowed yet
635 return
636 end
637
638 if updateOnly then
639 return
640 end
641
642 -- delay has passed, focus change is allowed, delay is set up
643 self.lastInput[direction] = g_time
644 if self.lockJustReleased then
645 self.lockUntil[direction] = g_time + self.DELAY_TIME
646 else
647 self.lockUntil[direction] = g_time + self.FIRST_LOCK
648 end
649
650 self.lockJustReleased = false
651
652 -- used if more than one button was pressed, only the first one is handled -- TODO: is needed?, also: button priority
653 if self.currentFocusData.focusElement ~= element then
654 return
655 end
656
657 -- give the element the chance to override the focus change
658 if element:shouldFocusChange(direction) then
659 -- change focus
660 local nextElement, nextElementIsSet
661 if element.focusChangeOverride then
662 if element.target then
663 nextElementIsSet, nextElement = element.focusChangeOverride(element.target, direction)
664 else
665 nextElementIsSet, nextElement = element:focusChangeOverride(direction)
666 end
667 end
668
669 local actualDirection = direction
670 if not nextElementIsSet then
671 nextElement, actualDirection = self:getNextFocusElement(element, direction)
672 end
673
674 if nextElement and nextElement:canReceiveFocus() then
675 self:setFocus(nextElement, actualDirection)
676
677 return nextElement
678 else
679 local focusElement = element
680 nextElement = element
681 if not element.focusChangeOverride or not element:focusChangeOverride(direction) then
682 local maxSteps = 30
683 while maxSteps > 0 do
684 if nextElement == nil then
685 break
686 end
687
688 nextElement, actualDirection = self:getNextFocusElement(nextElement, direction)
689 if nextElement ~= nil and nextElement:canReceiveFocus() then
690 focusElement = nextElement
691 break
692 end
693
694 maxSteps = maxSteps - 1
695 end
696 end
697
698 self:setFocus(focusElement, actualDirection)
699 end
700 end
701 end
702end