GuiElement#iconSize | string [optional] Pixel size of button glyph in reference resolution. Format: "[width]px [height]px" |
GuiElement#iconTextOffset | string [optional] Pixel offset of of icon relative to text position in reference resolution. Format: "[x]px [y]px" |
GuiElement#hotspot | string [optional] Pixel offset of the clickable button area borders in reference resolution, defaults to [0, 0, 0, 0]. Format: "[leftOffset]px [topOffset]px [rightOffset]px [bottomOffset]px" |
GuiElement#forceFocus | bool [optional] If true, focus on this button is not automatically set when it is clicked but must instead be set by code. |
GuiElement#forceHighlight | bool [optional] If true, highlight on this button is not automatically set on mouse over but must instead be set by code. |
GuiElement#needExternalClick | bool [optional] If true prevents this button from being "clicked" by being activated when focused. Instead, a direct input activation (e.g. controller button) is required. |
GuiElement#inputAction | string [optional] Input action name which triggers this button's click event, defaults to nil. |
GuiElement#clickSound | string [optional] Sound sample name which is played on button activation, defaults to "CLICK". Valid values are defined in GuiSoundPlayer.SOUND_SAMPLES. |
GuiElement#fitToContent | bool [optional] Fit width of button to fix content |
GuiElement#hideKeyboardGlyph | [optional] If true, does not draw the keyboard glyphs |
GuiElement#fitMargins | string [optional] Extra margins when fitting |
GuiElement#onClick | callback [optional] Called when this button is clicked / activated. |
GuiElement#onFocus | callback [optional] Called when this button is focused. |
GuiElement#onLeave | callback [optional] Called when this button loses focus. |
GuiElement#onHighlight | callback [optional] Called when this button is highlighted. |
GuiElement#onHighlightRemove | callback [optional] Called when this button loses highlight. |
233 | function ButtonElement:applyButtonAspectScale() |
234 | local xScale, yScale = self:getAspectScale() |
235 | |
236 | self.iconSize[1] = self.iconSize[1] * xScale |
237 | self.iconTextOffset[1] = self.iconTextOffset[1] * xScale |
238 | self.hotspot[1] = self.hotspot[1] * xScale |
239 | self.hotspot[3] = self.hotspot[3] * xScale |
240 | self.fitExtraWidth[1] = self.fitExtraWidth[1] * xScale |
241 | |
242 | self.iconSize[2] = self.iconSize[2] * yScale |
243 | self.iconTextOffset[2] = self.iconTextOffset[2] * yScale |
244 | self.hotspot[2] = self.hotspot[2] * yScale |
245 | self.hotspot[4] = self.hotspot[4] * yScale |
246 | end |
160 | function ButtonElement:copyAttributes(src) |
161 | ButtonElement:superClass().copyAttributes(self, src) |
162 | |
163 | GuiOverlay.copyOverlay(self.overlay, src.overlay) |
164 | GuiOverlay.copyOverlay(self.icon, src.icon) |
165 | |
166 | self.iconSize = ListUtil.copyTable(src.iconSize) |
167 | self.iconTextOffset = ListUtil.copyTable(src.iconTextOffset) |
168 | self.hotspot = ListUtil.copyTable(src.hotspot) |
169 | self.forceFocus = src.forceFocus |
170 | self.forceHighlight = src.forceHighlight |
171 | self.needExternalClick = src.needExternalClick |
172 | self.inputActionName = src.inputActionName |
173 | self.clickSoundName = src.clickSoundName |
174 | self.hideKeyboardGlyph = src.hideKeyboardGlyph |
175 | self.fitExtraWidth = src.fitExtraWidth |
176 | self.fitToContent = src.fitToContent |
177 | |
178 | self.onClickCallback = src.onClickCallback |
179 | self.onLeaveCallback = src.onLeaveCallback |
180 | self.onFocusCallback = src.onFocusCallback |
181 | self.onHighlightCallback = src.onHighlightCallback |
182 | self.onHighlightRemoveCallback = src.onHighlightRemoveCallback |
183 | |
184 | GuiMixin.cloneMixin(PlaySampleMixin, src, self) |
185 | end |
471 | function ButtonElement:draw() |
472 | self:setKeyboardMode(self.keyDisplayText ~= nil and g_inputBinding:getInputHelpMode() == GS_INPUT_HELP_MODE_KEYBOARD) |
473 | GuiOverlay.renderOverlay(self.overlay, self.absPosition[1], self.absPosition[2], self.size[1], self.size[2], self:getOverlayState()) |
474 | |
475 | local xPos, yPos = self:getTextPosition(self.text) |
476 | local textOffsetX, textOffsetY = self:getTextOffset() |
477 | local xOffset, yOffset = self:getIconOffset(self:getTextWidth(), getTextHeight(self.textSize, self.text)) |
478 | |
479 | local iconXPos = xPos + textOffsetX + xOffset |
480 | local iconYPos = yPos + textOffsetY + yOffset |
481 | local iconSizeX, iconSizeY = self:getIconSize() -- includes modifications for key glyph (if necessary) |
482 | local overlayState = self:getOverlayState() |
483 | |
484 | if self.keyDisplayText ~= nil and self.isKeyboardMode then |
485 | if not self.hideKeyboardGlyph then |
486 | local color = GuiOverlay.getOverlayColor(self.iconColors, overlayState) |
487 | self.keyOverlay:setColor(unpack(color)) |
488 | self.keyOverlay:renderButton(self.keyDisplayText, iconXPos, iconYPos, iconSizeY, self.textAlignment, overlayState == GuiOverlay.STATE_DISABLED) |
489 | end |
490 | else |
491 | GuiOverlay.renderOverlay(self.icon, iconXPos, iconYPos, iconSizeX, iconSizeY, overlayState) |
492 | end |
493 | |
494 | if self.debugEnabled or g_uiDebugEnabled then |
495 | local xPixel = 1 / g_screenWidth |
496 | local yPixel = 1 / g_screenHeight |
497 | setOverlayColor(GuiElement.debugOverlay, 0, 1, 0, 0.7) |
498 | |
499 | local posX1 = self.absPosition[1]+self.hotspot[1] |
500 | local posX2 = self.absPosition[1]+self.size[1]+self.hotspot[3]-xPixel |
501 | |
502 | local posY1 = self.absPosition[2]+self.hotspot[2] |
503 | local posY2 = self.absPosition[2]+self.size[2]+self.hotspot[4]-yPixel |
504 | |
505 | renderOverlay(GuiElement.debugOverlay, posX1, posY1, posX2-posX1, yPixel) |
506 | renderOverlay(GuiElement.debugOverlay, posX1, posY2, posX2-posX1, yPixel) |
507 | renderOverlay(GuiElement.debugOverlay, posX1, posY1, xPixel, posY2-posY1) |
508 | renderOverlay(GuiElement.debugOverlay, posX1+posX2-posX1, posY1, xPixel, posY2-posY1) |
509 | end |
510 | |
511 | ButtonElement:superClass().draw(self) |
512 | end |
448 | function ButtonElement:getIconOffset(textWidth, textHeight) |
449 | local iconSizeX, iconSizeY = self:getIconSize() |
450 | local xOffset, yOffset = self.iconTextOffset[1], self.iconTextOffset[2] |
451 | |
452 | if self.textAlignment == RenderText.ALIGN_LEFT then |
453 | xOffset = xOffset - iconSizeX |
454 | elseif self.textAlignment == RenderText.ALIGN_CENTER then |
455 | xOffset = xOffset - textWidth * 0.5 - iconSizeX |
456 | elseif self.textAlignment == RenderText.ALIGN_RIGHT then |
457 | xOffset = xOffset + textWidth - iconSizeX |
458 | end |
459 | |
460 | if self.textVerticalAlignment == TextElement.VERTICAL_ALIGNMENT.TOP then |
461 | yOffset = yOffset - textHeight |
462 | elseif self.textVerticalAlignment == TextElement.VERTICAL_ALIGNMENT.MIDDLE then |
463 | yOffset = yOffset + (textHeight - iconSizeY) * 0.5 |
464 | end |
465 | |
466 | return xOffset, yOffset |
467 | end |
78 | function ButtonElement:loadFromXML(xmlFile, key) |
79 | ButtonElement:superClass().loadFromXML(self, xmlFile, key) |
80 | |
81 | self:addCallback(xmlFile, key.."#onClick", "onClickCallback") |
82 | self:addCallback(xmlFile, key.."#onFocus", "onFocusCallback") |
83 | self:addCallback(xmlFile, key.."#onLeave", "onLeaveCallback") |
84 | self:addCallback(xmlFile, key.."#onHighlight", "onHighlightCallback") |
85 | self:addCallback(xmlFile, key.."#onHighlightRemove", "onHighlightRemoveCallback") |
86 | |
87 | GuiOverlay.loadOverlay(self, self.overlay, "image", self.imageSize, nil, xmlFile, key) |
88 | |
89 | self.iconSize = GuiUtils.getNormalizedValues(getXMLString(xmlFile, key.."#iconSize"), self.outputSize, self.iconSize) |
90 | self.iconTextOffset = GuiUtils.getNormalizedValues(getXMLString(xmlFile, key.."#iconTextOffset"), self.outputSize, self.iconTextOffset) |
91 | self.hotspot = GuiUtils.getNormalizedValues(getXMLString(xmlFile, key.."#hotspot"), self.outputSize, self.hotspot) |
92 | self.forceFocus = Utils.getNoNil(getXMLBool(xmlFile, key.."#forceFocus"), self.forceFocus) |
93 | self.forceHighlight = Utils.getNoNil(getXMLBool(xmlFile, key.."#forceHighlight"), self.forceHighlight) |
94 | self.needExternalClick = Utils.getNoNil(getXMLBool(xmlFile, key.."#needExternalClick"), self.needExternalClick) |
95 | self.fitToContent = Utils.getNoNil(getXMLBool(xmlFile, key.."#fitToContent"), self.fitToContent) |
96 | self.fitExtraWidth = GuiUtils.getNormalizedValues(getXMLString(xmlFile, key.."#fitExtraWidth"), self.outputSize, self.fitExtraWidth) |
97 | self.hideKeyboardGlyph = Utils.getNoNil(getXMLBool(xmlFile, key .. "#hideKeyboardGlyph"), self.hideKeyboardGlyph) |
98 | |
99 | local inputActionName = getXMLString(xmlFile, key .. "#inputAction") |
100 | if inputActionName ~= nil and InputAction[inputActionName] ~= nil then |
101 | self.inputActionName = inputActionName |
102 | self:loadInputGlyphColors(nil, xmlFile, key) |
103 | else |
104 | GuiOverlay.loadOverlay(self, self.icon, "icon", self.imageSize, nil, xmlFile, key) |
105 | GuiOverlay.createOverlay(self.icon) |
106 | end |
107 | |
108 | local sampleName = getXMLString(xmlFile, key .. "#clickSound") or self.clickSoundName |
109 | local resolvedSampleName = GuiSoundPlayer.SOUND_SAMPLES[sampleName] |
110 | if resolvedSampleName ~= nil then |
111 | self.clickSoundName = resolvedSampleName |
112 | end |
113 | |
114 | GuiOverlay.createOverlay(self.overlay) |
115 | |
116 | self:updateSize() |
117 | end |
121 | function ButtonElement:loadProfile(profile, applyProfile) |
122 | ButtonElement:superClass().loadProfile(self, profile, applyProfile) |
123 | |
124 | GuiOverlay.loadOverlay(self, self.overlay, "image", self.imageSize, profile, nil, nil) |
125 | |
126 | self.iconSize = GuiUtils.getNormalizedValues(profile:getValue("iconSize"), self.outputSize, self.iconSize) |
127 | self.iconTextOffset = GuiUtils.getNormalizedValues(profile:getValue("iconTextOffset"), self.outputSize, self.iconTextOffset) |
128 | self.hotspot = GuiUtils.getNormalizedValues(profile:getValue("hotspot"), self.outputSize, self.hotspot) |
129 | |
130 | self.forceFocus = profile:getBool("forceFocus", self.forceFocus) |
131 | self.forceHighlight = profile:getBool("forceHighlight", self.forceHighlight) |
132 | self.needExternalClick = profile:getBool("needExternalClick", self.needExternalClick) |
133 | self.fitToContent = profile:getBool("fitToContent", self.fitToContent) |
134 | self.fitExtraWidth = GuiUtils.getNormalizedValues(profile:getValue("fitExtraWidth"), self.outputSize, self.fitExtraWidth) |
135 | self.hideKeyboardGlyph = profile:getBool("hideKeyboardGlyph", self.hideKeyboardGlyph) |
136 | |
137 | local inputActionName = profile:getValue("inputAction", self.inputActionName) |
138 | if inputActionName ~= nil and InputAction[inputActionName] ~= nil then |
139 | self.inputActionName = inputActionName |
140 | self:loadInputGlyphColors(profile, nil, nil) |
141 | else |
142 | GuiOverlay.loadOverlay(self, self.icon, "icon", self.imageSize, profile, nil, nil) |
143 | GuiOverlay.createOverlay(self.icon) |
144 | end |
145 | |
146 | local sampleName = profile:getValue("clickSound", self.clickSoundName) |
147 | local resolvedSampleName = GuiSoundPlayer.SOUND_SAMPLES[sampleName] |
148 | if resolvedSampleName ~= nil then |
149 | self.clickSoundName = resolvedSampleName |
150 | end |
151 | |
152 | if applyProfile then |
153 | self:applyButtonAspectScale() |
154 | self:updateSize() |
155 | end |
156 | end |
376 | function ButtonElement:mouseEvent(posX, posY, isDown, isUp, button, eventUsed) |
377 | if self:getIsActive() then |
378 | eventUsed = eventUsed or ButtonElement:superClass().mouseEvent(self, posX, posY, isDown, isUp, button, eventUsed) |
379 | |
380 | -- handle highlight regardless of event used state |
381 | local cursorInElement = GuiUtils.checkOverlayOverlap(posX, posY, self.absPosition[1], self.absPosition[2], self.size[1], self.size[2], self.hotspot) |
382 | if cursorInElement then |
383 | if not self.mouseEntered and not self.focusActive then |
384 | -- set highlight on mouse over without focus |
385 | if not self.forceHighlight then |
386 | FocusManager:setHighlight(self) |
387 | end |
388 | |
389 | self.mouseEntered = true |
390 | end |
391 | else -- mouse event outside button |
392 | self:restoreOverlayState() |
393 | self.mouseDown = false |
394 | self.mouseEntered = false |
395 | if not self.forceHighlight then |
396 | -- reset highlight |
397 | FocusManager:unsetHighlight(self) |
398 | end |
399 | end |
400 | |
401 | -- handle click/activate only if event has not been consumed, yet |
402 | if not eventUsed then |
403 | if cursorInElement and not FocusManager:isLocked() then |
404 | if isDown and button == Input.MOUSE_BUTTON_LEFT then |
405 | if self.handleFocus and not self.forceFocus then |
406 | FocusManager:setFocus(self) -- focus on mouse down |
407 | eventUsed = true |
408 | end |
409 | |
410 | self.mouseDown = true |
411 | end |
412 | |
413 | -- if needed, set state to PRESSED and store current overlay state for restoration |
414 | if self.mouseDown and self:getOverlayState() ~= GuiOverlay.STATE_PRESSED then |
415 | self:storeOverlayState() |
416 | self:setOverlayState(GuiOverlay.STATE_PRESSED) |
417 | end |
418 | |
419 | if isUp and button == Input.MOUSE_BUTTON_LEFT and self.mouseDown then |
420 | self:playSample(self.clickSoundName) |
421 | |
422 | self:restoreOverlayState() |
423 | self.mouseDown = false |
424 | self:raiseCallback("onClickCallback", self) |
425 | |
426 | eventUsed = true |
427 | end |
428 | end |
429 | end |
430 | end |
431 | |
432 | return eventUsed |
433 | end |
36 | function ButtonElement:new(target, custom_mt) |
37 | local self = TextElement:new(target, custom_mt or ButtonElement_mt) |
38 | self:include(PlaySampleMixin) -- add sound playing |
39 | |
40 | self.mouseDown = false |
41 | self.forceFocus = false |
42 | self.forceHighlight = false -- if true, highlight state is managed by external caller |
43 | self.overlay = {} |
44 | self.icon = {} |
45 | self.iconSize = {0,0} |
46 | self.iconTextOffset = {0,0} |
47 | self.focusedTextOffset = {0,0} |
48 | self.hotspot = {0, 0, 0, 0} -- to define clickable area offset |
49 | self.needExternalClick = false -- used to override focus behaviour in special cases |
50 | self.clickSoundName = GuiSoundPlayer.SOUND_SAMPLES.CLICK |
51 | self.fitToContent = false |
52 | self.fitExtraWidth = {0} |
53 | self.hideKeyboardGlyph = false |
54 | |
55 | self.inputActionName = nil -- name of input action whose primary input binding will be displayed as a glyph, if set |
56 | self.hasLoadedInputGlyph = false |
57 | self.isKeyboardMode = false |
58 | self.keyDisplayText = nil -- resolved key display text for the input action |
59 | self.keyOverlay = nil -- holds a shared keyboard key glyph display overlay, do not delete! |
60 | self.keyGlyphOffsetX = 0 -- additional text offset when displaying keyboard key glyph |
61 | self.keyGlyphSize = {0, 0} |
62 | self.iconColors = {color={1, 1, 1, 1}} -- holds overlay color information for keyboard key glyph display |
63 | |
64 | return self |
65 | end |
304 | function ButtonElement:onOpen() |
305 | ButtonElement:superClass().onOpen(self) |
306 | if self.disabled then |
307 | self:setOverlayState(GuiOverlay.STATE_DISABLED) |
308 | end |
309 | |
310 | -- deferred loading of input glyph, so that not having a controller plugged in does not break the UI on loading: |
311 | if self.inputActionName ~= nil and not self.hasLoadedInputGlyph then |
312 | self:loadInputGlyph(true) |
313 | end |
314 | |
315 | if GS_PLATFORM_TYPE == GS_PLATFORM_TYPE_GGP then |
316 | g_messageCenter:subscribe(MessageType.INPUT_MODE_CHANGED, self.onControllerChanged, self) |
317 | g_messageCenter:subscribe(MessageType.INPUT_HELP_MODE_CHANGED, self.onControllerChanged, self) |
318 | end |
319 | end |