LUADOC - Farming Simulator 22

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
505function FocusManager.checkElementDistance(curElement, other, dirX, dirY, curElementOffsetY, closestOther, closestDistanceSq)
506 local retOther = closestOther
507 local retDistSq = closestDistanceSq
508
509 local elementBox = curElement:getBorders()
510 elementBox[2] = elementBox[2] + curElementOffsetY
511 elementBox[4] = elementBox[4] + curElementOffsetY
512 local elementCenter = curElement:getCenter()
513 elementCenter[2] = elementCenter[2] + curElementOffsetY
514
515 if other ~= curElement and not other.disabled and other:getIsVisible() and other:canReceiveFocus() and not (other:isChildOf(curElement) or curElement:isChildOf(other)) then
516 local otherBox = other:getBorders()
517 local otherCenter = other:getCenter()
518
519 -- get vector between bounding box points
520 local elementDirX, elementDirY = FocusManager.getShortestBoundingBoxVector(elementBox, otherBox, otherCenter)
521
522 -- test direction and distance of bounding box points
523 local boxDistanceSq = MathUtil.vector2LengthSq(elementDirX, elementDirY)
524 local dot = MathUtil.dotProduct(elementDirX, elementDirY, 0, dirX, dirY, 0)
525 if boxDistanceSq < FocusManager.EPSILON then -- boundaries touch, use center points for direction check
526 dot = MathUtil.dotProduct(otherCenter[1] - elementCenter[1], otherCenter[2] - elementCenter[2], 0, dirX, dirY, 0)
527 end
528
529 if dot > 0 then -- other element lies in scanning direction
530 local useOther = false
531
532 -- when two elements are equally close, choose the one further up (-y) and/or further left (-x)
533 if closestOther and math.abs(closestDistanceSq - boxDistanceSq) < FocusManager.EPSILON then
534 -- also compare dot products
535 local closestBox = closestOther:getBorders()
536 local closestCenter = closestOther:getCenter()
537 local toClosestX, toClosestY = FocusManager.getShortestBoundingBoxVector(elementBox, closestBox, closestCenter)
538 local closestDot = MathUtil.dotProduct(toClosestX, toClosestY, 0, dirX, dirY, 0)
539
540 if math.abs(closestDot - dot) < FocusManager.EPSILON then -- same distance and angle as previous best
541 -- when going up, go right first, etc. --> ensure symmetric paths in all directions
542 if dirY > 0 then
543 useOther = other.absPosition[1] > closestOther.absPosition[1]
544 elseif dirY < 0 then
545 useOther = other.absPosition[1] < closestOther.absPosition[1]
546 elseif dirX > 0 then
547 useOther = other.absPosition[2] > closestOther.absPosition[2]
548 elseif dirX < 0 then
549 useOther = other.absPosition[2] < closestOther.absPosition[2]
550 end
551 elseif dot > closestDot then -- when distance is equal and angles differ, prefer the one closer to the movement direction
552 useOther = true
553 end
554 elseif boxDistanceSq < closestDistanceSq then
555 useOther = true
556 end
557
558 if useOther then
559 retOther = other
560 retDistSq = boxDistanceSq
561 end
562 end
563 end
564
565 return retOther, retDistSq
566end

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
459function FocusManager.getClosestPointOnBoundingBox(x, y, boxMinX, boxMinY, boxMaxX, boxMaxY)
460 local px, py = x, y
461 if x < boxMinX then
462 px = boxMinX
463 elseif x > boxMaxX then
464 px = boxMaxX
465 end
466
467 if y < boxMinY then
468 py = boxMinY
469 elseif y > boxMaxY then
470 py = boxMaxY
471 end
472
473 return px, py
474end

getDirectionForAxisValue

Description
Get a direction value for a given menu input action and value
Definition
getDirectionForAxisValue()
Code
362function FocusManager.getDirectionForAxisValue(inputAction, value)
363 if value == nil then
364 return nil
365 end
366
367 local direction = nil
368 if inputAction == InputAction.MENU_AXIS_UP_DOWN then
369 if value < 0 then
370 direction = FocusManager.BOTTOM
371 elseif value > 0 then
372 direction = FocusManager.TOP
373 end
374 elseif inputAction == InputAction.MENU_AXIS_LEFT_RIGHT then
375 if value < 0 then
376 direction = FocusManager.LEFT
377 elseif value > 0 then
378 direction = FocusManager.RIGHT
379 end
380 end
381
382 return direction
383end

getElementById

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

getFocusedElement

Description
Get the currently focused GuiElement
Definition
getFocusedElement()
Code
117function FocusManager:getFocusedElement()
118 return self.currentFocusData.focusElement
119end

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
902function FocusManager:getFocusOverrideFunction(forDirections, substitute, useSubstituteForFocus)
903 if forDirections == nil or #forDirections < 1 then
904 return function(elementSelf, dir) return false, nil end
905 end
906
907 local f = function(elementSelf, dir)
908 for _, overrideDirection in pairs(forDirections) do
909 if dir == overrideDirection then
910 if useSubstituteForFocus then
911 local next = self:getNextFocusElement(substitute, dir)
912 if next then
913 return true, next
914 end
915 else
916 return true, substitute
917 end
918 end
919 end
920
921 return false, nil
922 end
923
924 return f
925end

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
627function FocusManager.getNestedFocusTarget(element, direction)
628 local target = element
629 local prevTarget = nil
630 while target and prevTarget ~= target do
631 prevTarget = target
632 target = target:getFocusTarget(FocusManager.OPPOSING_DIRECTIONS[direction], direction)
633 end
634
635 return target
636end

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
573function FocusManager:getNextFocusElement(element, direction)
574 -- if there is a configured next element, return that
575 local nextFocusId = element.focusChangeData[direction]
576 if nextFocusId then
577 return self.currentFocusData.idToElementMapping[nextFocusId], direction
578 end
579 -- otherwise, find the next one based on proximity:
580 local dirX, dirY = unpack(FocusManager.DIRECTION_VECTORS[direction])
581
582 local closestOther = nil
583 local closestDistance = math.huge
584
585 for _, other in pairs(self.currentFocusData.idToElementMapping) do
586 closestOther, closestDistance = FocusManager.checkElementDistance(element, other, dirX, dirY, 0, closestOther, closestDistance)
587 end
588
589 if closestOther == nil then
590 -- wrap around
591 if direction == FocusManager.LEFT then
592 -- look up instead
593 closestOther, direction = self:getNextFocusElement(element, FocusManager.TOP)
594 elseif direction == FocusManager.RIGHT then
595 -- look down instead
596 closestOther, direction = self:getNextFocusElement(element, FocusManager.BOTTOM)
597 else
598 -- get the right test elements
599 local validWrapElements = self.currentFocusData.idToElementMapping -- screen wrap around
600 if element.parent and element.parent.wrapAround then -- local box/area wrap around if required
601 validWrapElements = element.parent.elements
602 end
603
604 local wrapOffsetY = 0
605 if direction == FocusManager.TOP then
606 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]
607 elseif direction == FocusManager.BOTTOM then
608 wrapOffsetY = 1.2 + element.size[2] -- above screen
609 end
610
611 -- try wrapping around
612 for _, other in pairs(validWrapElements) do
613 closestOther, closestDistance = FocusManager.checkElementDistance(element, other, dirX, dirY, wrapOffsetY, closestOther, closestDistance)
614 end
615 end
616 end
617
618 return closestOther, direction
619end

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
479function FocusManager.getShortestBoundingBoxVector(elementBox, otherBox, otherCenter)
480 local ePointX, ePointY = FocusManager.getClosestPointOnBoundingBox(
481 otherCenter[1], otherCenter[2],
482 elementBox[1], elementBox[2], elementBox[3], elementBox[4])
483
484 local oPointX, oPointY = FocusManager.getClosestPointOnBoundingBox(
485 ePointX, ePointY, -- use the previously calculated bounding box point here to get the closest boundary distance
486 otherBox[1], otherBox[2], otherBox[3], otherBox[4])
487
488 -- get vector between bounding box points
489 local elementDirX = oPointX - ePointX
490 local elementDirY = oPointY - ePointY
491
492 return elementDirX, elementDirY
493end

hasFocus

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

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
328function FocusManager:inputEvent(action, value, eventUsed)
329 local element = self.currentFocusData.focusElement
330
331 local pressedAccept = false
332 local pressedUp = action == InputAction.MENU_AXIS_UP_DOWN and value > g_analogStickVTolerance
333 local pressedDown = action == InputAction.MENU_AXIS_UP_DOWN and value < -g_analogStickVTolerance
334 local pressedLeft = action == InputAction.MENU_AXIS_LEFT_RIGHT and value < -g_analogStickHTolerance
335 local pressedRight = action == InputAction.MENU_AXIS_LEFT_RIGHT and value > g_analogStickHTolerance
336
337 if action == InputAction.MENU_AXIS_UP_DOWN then
338 self:updateFocus(element, pressedUp, FocusManager.TOP, eventUsed)
339 self:updateFocus(element, pressedDown, FocusManager.BOTTOM, eventUsed)
340 elseif action == InputAction.MENU_AXIS_LEFT_RIGHT then
341 self:updateFocus(element, pressedLeft, FocusManager.LEFT, eventUsed)
342 self:updateFocus(element, pressedRight, FocusManager.RIGHT, eventUsed)
343 end
344
345 if not eventUsed and element ~= nil and not element.needExternalClick then
346 pressedAccept = action == InputAction.MENU_ACCEPT
347 if pressedAccept and not self:isFocusInputLocked(action) then
348 -- elements can get unfocused, accept is only allowed for currently focused and visible elements
349 if element.focusActive and element:getIsVisible() then
350 self.focusSystemMadeChanges = true
351 element:onFocusActivate()
352 self.focusSystemMadeChanges = false
353 end
354 end
355 end
356
357 return eventUsed or pressedUp or pressedDown or pressedLeft or pressedRight or pressedAccept
358end

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
886function FocusManager:isDirectionLocked(direction)
887 return self.lastInput[direction] ~= nil
888end

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
390function FocusManager:isFocusInputLocked(inputAxis, value)
391 local key = FocusManager.getDirectionForAxisValue(inputAxis, value)
392 if key == nil and inputAxis ~= InputAction.MENU_AXIS_UP_DOWN and inputAxis ~= InputAction.MENU_AXIS_LEFT_RIGHT then
393 key = inputAxis
394 end
395
396 if self.lastInput[key] and self.lockUntil[key] > g_time then
397 return true
398 else
399 return false
400 end
401end

isLocked

Description
Check if focus input is locked.
Definition
isLocked()
Code
878function FocusManager:isLocked()
879 return FocusManager.isFocusLocked
880end

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
314function FocusManager:linkElements(sourceElement, direction, targetElement)
315 if targetElement == nil then
316 sourceElement.focusChangeData[direction] = "nil"
317 else
318 sourceElement.focusChangeData[direction] = targetElement.focusId
319 end
320end

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
231function FocusManager:loadElementFromCustomValues(element, focusId, focusChangeData, focusActive, isAlwaysFocusedOnOpen)
232 if focusId and self.currentFocusData.idToElementMapping[focusId] then
233 return false -- ignore element, caller is responsible for sensible ID assignment when specified
234 end
235
236 if not element.focusId then
237 if not focusId then
238 focusId = FocusManager.serveAutoFocusId()
239 end
240
241 element.focusId = focusId
242 end
243
244 element.focusChangeData = element.focusChangeData or focusChangeData or {}
245 element.focusActive = focusActive
246 element.isAlwaysFocusedOnOpen = isAlwaysFocusedOnOpen
247
248 if FocusManager.allElements[element] == nil then
249 FocusManager.allElements[element] = {}
250 end
251 table.insert(FocusManager.allElements[element], self.currentGui)
252 self.currentFocusData.idToElementMapping[element.focusId] = element
253
254 if isAlwaysFocusedOnOpen then
255 self.currentFocusData.initialFocusElement = element
256 end
257
258 if focusActive then
259 self:setFocus(element)
260 end
261
262 local success = true
263 for _, child in pairs(element.elements) do
264 success = success and self:loadElementFromCustomValues(child, child.focusId, child.focusChangeData, child.focusActive, child.isAlwaysFocusedOnOpen)
265 end
266
267 return success
268end

loadElementFromXML

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

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
409function FocusManager:lockFocusInput(axisAction, delay, value)
410 local key = FocusManager.getDirectionForAxisValue(axisAction, value)
411 if not key and axisAction ~= InputAction.MENU_AXIS_UP_DOWN and axisAction ~= InputAction.MENU_AXIS_LEFT_RIGHT then
412 key = axisAction
413 end
414
415 self.lastInput[key] = g_time
416 self.lockUntil[key] = g_time + delay
417end

releaseLock

Description
Release the global focus input lock.
Definition
releaseLock()
Code
872function FocusManager:releaseLock()
873 FocusManager.isFocusLocked = false
874end

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
423function FocusManager:releaseMovementFocusInput(action)
424 -- on input release we do not have a direction input value, need to clear lock for both directions on axes:
425 if action == InputAction.MENU_AXIS_LEFT_RIGHT then
426 self.lastInput[FocusManager.LEFT] = nil
427 self.lockUntil[FocusManager.LEFT] = nil
428 self.lastInput[FocusManager.RIGHT] = nil
429 self.lockUntil[FocusManager.RIGHT] = nil
430 elseif action == InputAction.MENU_AXIS_UP_DOWN then
431 self.lastInput[FocusManager.TOP] = nil
432 self.lockUntil[FocusManager.TOP] = nil
433 self.lastInput[FocusManager.BOTTOM] = nil
434 self.lockUntil[FocusManager.BOTTOM] = nil
435 end
436end

removeElement

Description
Remove a GuiElement from the current focus context.
Definition
removeElement()
Code
272function FocusManager:removeElement(element)
273 if not element.focusId then
274 return
275 end
276
277 for _, child in pairs(element.elements) do
278 self:removeElement(child)
279 end
280
281 if element.focusActive then
282 element:onFocusLeave()
283 FocusManager:unsetFocus(element)
284 end
285
286 if FocusManager.allElements[element] ~= nil then
287 for _, guiItWasAddedTo in ipairs(FocusManager.allElements[element]) do
288 local data = self.guiFocusData[guiItWasAddedTo]
289 data.idToElementMapping[element.focusId] = nil
290 if data.focusElement == element then
291 data.focusElement = nil
292 end
293 end
294
295 FocusManager.allElements[element] = nil -- remove
296 end
297
298 self.currentFocusData.idToElementMapping[element.focusId] = nil
299 element.focusId = nil
300 element.focusChangeData = {}
301
302 if self.currentFocusData.focusElement == element then
303 self.currentFocusData.focusElement = nil
304 end
305end

requireLock

Description
Globally lock focus input.
Definition
requireLock()
Code
866function FocusManager:requireLock()
867 FocusManager.isFocusLocked = true
868end

resetFocusInputLocks

Description
Reset all locks of focus input.
Definition
resetFocusInputLocks()
Code
440function FocusManager:resetFocusInputLocks()
441 for k, _ in pairs(self.lastInput) do
442 self.lastInput[k] = nil
443 end
444 for k, _ in pairs(self.lockUntil) do
445 self.lockUntil[k] = 0
446 end
447end

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
124function FocusManager.serveAutoFocusId()
125 local focusId = string.format("focusAuto_%d", FocusManager.autoIDcount)
126 FocusManager.autoIDcount = FocusManager.autoIDcount + 1
127 return focusId
128end

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
843function FocusManager:setElementFocusOverlayState(element, isFocused, handlePreviousState)
844 if handlePreviousState == nil then
845 handlePreviousState = true
846 end
847
848 if isFocused then
849 if handlePreviousState and element:getOverlayState() ~= GuiOverlay.STATE_NORMAL then
850 element:storeOverlayState()
851 end
852 element:setOverlayState(GuiOverlay.STATE_FOCUSED)
853 else
854 if handlePreviousState then
855 element:restoreOverlayState()
856 end
857
858 if element:getOverlayState() == GuiOverlay.STATE_FOCUSED then -- could still be focused state after restore, just set to normal
859 element:setOverlayState(GuiOverlay.STATE_NORMAL)
860 end
861 end
862end

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
774function FocusManager:setFocus(element, direction, ...)
775 if FocusManager.isFocusLocked or element == nil or not element:canReceiveFocus() then
776 return false
777 end
778
779 -- get the element's focus target (or a descendant's) to return
780 local targetElement = FocusManager.getNestedFocusTarget(element, direction)
781 if targetElement.targetName ~= self.currentGui then
782 return false
783 end
784
785 if self.currentFocusData.focusElement and
786 self.currentFocusData.focusElement == targetElement and
787 self.currentFocusData.focusElement.focusActive then
788 -- the passed element already has focus
789 return false
790 end
791
792 -- clear focus and highlight on previous elements
793 if self.currentFocusData.focusElement ~= nil then
794 self:unsetFocus(self.currentFocusData.focusElement)
795 self:unsetHighlight(self.currentFocusData.highlightElement)
796 end
797
798 -- set focus of newly focused element
799 targetElement.focusActive = true
800 self.currentFocusData.focusElement = targetElement
801 targetElement:onFocusEnter(...)
802
803 if FocusManager.DEBUG then
804 log("focus changed to element", targetElement, "; ID:", targetElement.id, "; profile:", targetElement.profile, "; type:", targetElement.typeName)
805 end
806
807 self:setElementFocusOverlayState(targetElement, true)
808 if not element:getSoundSuppressed() and element:getIsVisible() and element.playHoverSoundOnFocus ~= false and not element.soundDisabled then
809 self.soundPlayer:playSample(targetElement.customFocusSample or GuiSoundPlayer.SOUND_SAMPLES.HOVER)
810 end
811
812 return true
813end

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 local oldSound = focusElement.soundDisabled
93 focusElement.soundDisabled = true
94 self:setFocus(focusElement)
95 focusElement.soundDisabled = oldSound
96 end
97 end
98
99 -- reset delay locks
100 self:resetFocusInputLocks()
101end

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
729function FocusManager:setHighlight(element)
730 -- check if element has highlight already
731 if self.currentFocusData.highlightElement and self.currentFocusData.highlightElement == element then
732 return
733 end
734
735 -- unset highlight of currently highlighted element
736 self:unsetHighlight(self.currentFocusData.highlightElement)
737
738 if not element.disallowFocusedHighlight or not (self.currentFocusData.focusElement and self.currentFocusData.focusElement == element) then
739 -- set highlight of new element
740 self.currentFocusData.highlightElement = element
741 element:storeOverlayState()
742 element:setOverlayState(GuiOverlay.STATE_HIGHLIGHTED)
743 element:onHighlight()
744
745 if not element:getSoundSuppressed() and element:getIsVisible() and element.playHoverSoundOnFocus ~= false and not element.soundDisabled then
746 self.soundPlayer:playSample(GuiSoundPlayer.SOUND_SAMPLES.HOVER)
747 end
748 end
749end

setSoundPlayer

Description
Definition
setSoundPlayer()
Code
105function FocusManager:setSoundPlayer(guiSoundPlayer)
106 self.soundPlayer = guiSoundPlayer
107end

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
820function FocusManager:unsetFocus(element, ...)
821 local prevFocusElement = self.currentFocusData.focusElement
822 if prevFocusElement ~= element or prevFocusElement == nil then
823 -- the element is not focused
824 return
825 end
826
827 if not element.focusActive then
828 -- the element has already lost focus
829 return
830 end
831
832 prevFocusElement.focusActive = false
833 self:setElementFocusOverlayState(prevFocusElement, false)
834
835 prevFocusElement:onFocusLeave(...) -- call focus leave last, can override overlay state if desired
836end

unsetHighlight

Description
Remove highlight status from an element.
Definition
unsetHighlight(element Highlighted)
Arguments
elementHighlightedelement to revert
Code
754function FocusManager:unsetHighlight(element)
755 if self.currentFocusData.highlightElement and self.currentFocusData.highlightElement == element then
756 local prevState = self.currentFocusData.highlightElement:getOverlayState()
757 if prevState == GuiOverlay.STATE_HIGHLIGHTED then
758 self.currentFocusData.highlightElement:setOverlayState(GuiOverlay.STATE_NORMAL)
759 self.currentFocusData.highlightElement:restoreOverlayState() -- also try restoring
760 end
761
762 self.currentFocusData.highlightElement:onHighlightRemove()
763 self.currentFocusData.highlightElement = nil
764 end
765end

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
644function FocusManager:updateFocus(element, isFocusMoving, direction, updateOnly)
645 if element == nil then
646 return
647 end
648
649 if isFocusMoving then
650 if self.lastInput[direction] then
651 if self.lockUntil[direction] <= g_time then
652 self.lastInput[direction] = nil -- release lock
653 self.lockJustReleased = true
654 end
655 -- movement not allowed yet
656 return
657 end
658
659 if updateOnly then
660 return
661 end
662
663 -- delay has passed, focus change is allowed, delay is set up
664 self.lastInput[direction] = g_time
665 if self.lockJustReleased then
666 self.lockUntil[direction] = g_time + self.DELAY_TIME
667 else
668 self.lockUntil[direction] = g_time + self.FIRST_LOCK
669 end
670
671 self.lockJustReleased = false
672
673 -- used if more than one button was pressed, only the first one is handled -- TODO: is needed?, also: button priority
674 if self.currentFocusData.focusElement ~= element then
675 return
676 end
677
678 -- give the element the chance to override the focus change
679 if element:shouldFocusChange(direction) then
680 -- change focus
681 local nextElement, nextElementIsSet
682 if element.focusChangeOverride then
683 if element.target then
684 nextElementIsSet, nextElement = element.focusChangeOverride(element.target, direction)
685 else
686 nextElementIsSet, nextElement = element:focusChangeOverride(direction)
687 end
688 end
689
690 local actualDirection = direction
691 if not nextElementIsSet then
692 nextElement, actualDirection = self:getNextFocusElement(element, direction)
693 end
694
695 if nextElement and nextElement:canReceiveFocus() then
696 self:setFocus(nextElement, actualDirection)
697
698 return nextElement
699 else
700 local focusElement = element
701 nextElement = element
702 if not element.focusChangeOverride or not element:focusChangeOverride(direction) then
703 local maxSteps = 30
704 while maxSteps > 0 do
705 if nextElement == nil then
706 break
707 end
708
709 nextElement, actualDirection = self:getNextFocusElement(nextElement, direction)
710 if nextElement ~= nil and nextElement:canReceiveFocus() then
711 focusElement = nextElement
712 break
713 end
714
715 maxSteps = maxSteps - 1
716 end
717 end
718
719 self:setFocus(focusElement, actualDirection)
720 end
721 end
722 end
723end