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. |
192 | function ButtonElement:copyAttributes(src) |
193 | ButtonElement:superClass().copyAttributes(self, src) |
194 | |
195 | GuiOverlay.copyOverlay(self.overlay, src.overlay) |
196 | GuiOverlay.copyOverlay(self.icon, src.icon) |
197 | if src.isTouchButton and GS_IS_MOBILE_VERSION then |
198 | GuiOverlay.copyOverlay(self.touchIcon, src.touchIcon) |
199 | self.touchIconSize = table.copy(src.touchIconSize) |
200 | end |
201 | |
202 | self.iconSize = table.copy(src.iconSize) |
203 | self.iconTextOffset = table.copy(src.iconTextOffset) |
204 | self.hotspot = table.copy(src.hotspot) |
205 | self.forceFocus = src.forceFocus |
206 | self.forceHighlight = src.forceHighlight |
207 | self.needExternalClick = src.needExternalClick |
208 | self.inputActionName = src.inputActionName |
209 | self.clickSoundName = src.clickSoundName |
210 | self.hideKeyboardGlyph = src.hideKeyboardGlyph |
211 | self.fitExtraWidth = src.fitExtraWidth |
212 | self.fitToContent = src.fitToContent |
213 | self.isTouchButton = src.isTouchButton |
214 | self.addTouchArea = src.addTouchArea |
215 | self.iconColors = src.iconColors |
216 | |
217 | self.onClickCallback = src.onClickCallback |
218 | self.onLeaveCallback = src.onLeaveCallback |
219 | self.onFocusCallback = src.onFocusCallback |
220 | self.onHighlightCallback = src.onHighlightCallback |
221 | self.onHighlightRemoveCallback = src.onHighlightRemoveCallback |
222 | |
223 | GuiMixin.cloneMixin(PlaySampleMixin, src, self) |
224 | end |
519 | function ButtonElement:draw(clipX1, clipY1, clipX2, clipY2) |
520 | self:setInputMode(self.keyDisplayText ~= nil and g_inputBinding:getInputHelpMode() == GS_INPUT_HELP_MODE_KEYBOARD, self.isTouchButton and g_inputBinding:getInputHelpMode() == GS_INPUT_HELP_MODE_TOUCH, g_inputBinding:getInputHelpMode() == GS_INPUT_HELP_MODE_GAMEPAD) |
521 | GuiOverlay.renderOverlay(self.overlay, self.absPosition[1], self.absPosition[2], self.size[1], self.size[2], self:getOverlayState(), clipX1, clipY1, clipX2, clipY2) |
522 | |
523 | local xPos, yPos = self:getTextPosition(self.text) |
524 | local textOffsetX, textOffsetY = self:getTextOffset() |
525 | local xOffset, yOffset = self:getIconOffset(self:getTextWidth(), getTextHeight(self.textSize, self.text)) |
526 | |
527 | local iconXPos = xPos + textOffsetX + xOffset |
528 | local iconYPos = yPos + textOffsetY + yOffset |
529 | local iconSizeX, iconSizeY = self:getIconSize() -- includes modifications for key glyph (if necessary) |
530 | local overlayState = self:getOverlayState() |
531 | |
532 | if self.keyDisplayText ~= nil and self.isKeyboardMode then |
533 | if not self.hideKeyboardGlyph then |
534 | local color = GuiOverlay.getOverlayColor(self.iconColors, overlayState) |
535 | self.keyOverlay:setColor(unpack(color)) |
536 | self.keyOverlay:renderButton(self.keyDisplayText, iconXPos, iconYPos, iconSizeY, self.textAlignment, true)--overlayState == GuiOverlay.STATE_DISABLED) |
537 | end |
538 | elseif self.isTouchMode then |
539 | if self.addTouchArea then |
540 | drawTouchButton(self.absPosition[1], self.absPosition[2] + self.absSize[2] / 2, self.absSize[1], overlayState == GuiOverlay.STATE_PRESSED) |
541 | end |
542 | |
543 | if self.touchIcon ~= nil then |
544 | -- Always position in center of button |
545 | local touchIconYPos = self.absPosition[2] + self.absSize[2] / 2 - self.touchIconSize[2] / 2 |
546 | GuiOverlay.renderOverlay(self.touchIcon, iconXPos, touchIconYPos, self.touchIconSize[1], self.touchIconSize[2], overlayState, clipX1, clipY1, clipX2, clipY2) |
547 | end |
548 | else |
549 | GuiOverlay.renderOverlay(self.icon, iconXPos, iconYPos, iconSizeX, iconSizeY, overlayState, clipX1, clipY1, clipX2, clipY2) |
550 | end |
551 | |
552 | if self.debugEnabled or g_uiDebugEnabled then |
553 | local xPixel = 1 / g_screenWidth |
554 | local yPixel = 1 / g_screenHeight |
555 | |
556 | local posX1 = self.absPosition[1]+self.hotspot[1] |
557 | local posX2 = self.absPosition[1]+self.size[1]+self.hotspot[3]-xPixel |
558 | |
559 | local posY1 = self.absPosition[2]+self.hotspot[2] |
560 | local posY2 = self.absPosition[2]+self.size[2]+self.hotspot[4]-yPixel |
561 | |
562 | drawFilledRect(posX1, posY1, posX2-posX1, yPixel, 0, 1, 0, 0.7) |
563 | drawFilledRect(posX1, posY2, posX2-posX1, yPixel, 0, 1, 0, 0.7) |
564 | drawFilledRect(posX1, posY1, xPixel, posY2-posY1, 0, 1, 0, 0.7) |
565 | drawFilledRect(posX1+posX2-posX1, posY1, xPixel, posY2-posY1, 0, 1, 0, 0.7) |
566 | end |
567 | |
568 | ButtonElement:superClass().draw(self, clipX1, clipY1, clipX2, clipY2) |
569 | end |
496 | function ButtonElement:getIconOffset(textWidth, textHeight) |
497 | local iconSizeX, iconSizeY = self:getIconSize() |
498 | local xOffset, yOffset = self.iconTextOffset[1], self.iconTextOffset[2] |
499 | |
500 | if self.textAlignment == RenderText.ALIGN_LEFT then |
501 | xOffset = xOffset - iconSizeX |
502 | elseif self.textAlignment == RenderText.ALIGN_CENTER then |
503 | xOffset = xOffset - textWidth * 0.5 - iconSizeX |
504 | elseif self.textAlignment == RenderText.ALIGN_RIGHT then |
505 | xOffset = xOffset + textWidth - iconSizeX |
506 | end |
507 | |
508 | if self.textVerticalAlignment == TextElement.VERTICAL_ALIGNMENT.TOP then |
509 | yOffset = yOffset - textHeight |
510 | elseif self.textVerticalAlignment == TextElement.VERTICAL_ALIGNMENT.MIDDLE then |
511 | yOffset = yOffset + (textHeight - iconSizeY) * 0.5 |
512 | end |
513 | |
514 | return xOffset, yOffset |
515 | end |
84 | function ButtonElement:loadFromXML(xmlFile, key) |
85 | ButtonElement:superClass().loadFromXML(self, xmlFile, key) |
86 | |
87 | self:addCallback(xmlFile, key.."#onClick", "onClickCallback") |
88 | self:addCallback(xmlFile, key.."#onFocus", "onFocusCallback") |
89 | self:addCallback(xmlFile, key.."#onLeave", "onLeaveCallback") |
90 | self:addCallback(xmlFile, key.."#onHighlight", "onHighlightCallback") |
91 | self:addCallback(xmlFile, key.."#onHighlightRemove", "onHighlightRemoveCallback") |
92 | |
93 | GuiOverlay.loadOverlay(self, self.overlay, "image", self.imageSize, nil, xmlFile, key) |
94 | |
95 | self.iconSize = GuiUtils.getNormalizedValues(getXMLString(xmlFile, key.."#iconSize"), self.outputSize, self.iconSize) |
96 | self.touchIconSize = GuiUtils.getNormalizedValues(getXMLString(xmlFile, key.."#touchIconSize"), self.outputSize, self.touchIconSize) |
97 | self.iconTextOffset = GuiUtils.getNormalizedValues(getXMLString(xmlFile, key.."#iconTextOffset"), self.outputSize, self.iconTextOffset) |
98 | self.hotspot = GuiUtils.getNormalizedValues(getXMLString(xmlFile, key.."#hotspot"), self.outputSize, self.hotspot) |
99 | self.forceFocus = Utils.getNoNil(getXMLBool(xmlFile, key.."#forceFocus"), self.forceFocus) |
100 | self.forceHighlight = Utils.getNoNil(getXMLBool(xmlFile, key.."#forceHighlight"), self.forceHighlight) |
101 | self.needExternalClick = Utils.getNoNil(getXMLBool(xmlFile, key.."#needExternalClick"), self.needExternalClick) |
102 | self.fitToContent = Utils.getNoNil(getXMLBool(xmlFile, key.."#fitToContent"), self.fitToContent) |
103 | self.fitExtraWidth = GuiUtils.getNormalizedValues(getXMLString(xmlFile, key.."#fitExtraWidth"), self.outputSize, self.fitExtraWidth) |
104 | self.hideKeyboardGlyph = Utils.getNoNil(getXMLBool(xmlFile, key .. "#hideKeyboardGlyph"), self.hideKeyboardGlyph) |
105 | self.isTouchButton = Utils.getNoNil(getXMLBool(xmlFile, key .. "#isTouchButton"), self.isTouchButton) |
106 | self.addTouchArea = Utils.getNoNil(getXMLBool(xmlFile, key .. "#addTouchArea"), self.addTouchArea) |
107 | |
108 | local inputActionName = getXMLString(xmlFile, key .. "#inputAction") |
109 | if inputActionName ~= nil and InputAction[inputActionName] ~= nil then |
110 | self.inputActionName = inputActionName |
111 | self:loadInputGlyphColors(nil, xmlFile, key) |
112 | else |
113 | self.iconImageSize = GuiUtils.get2DArray(getXMLString(xmlFile, key.."#iconImageSize"), self.iconImageSize) |
114 | GuiOverlay.loadOverlay(self, self.icon, "icon", self.iconImageSize, nil, xmlFile, key) |
115 | GuiOverlay.createOverlay(self.icon) |
116 | end |
117 | |
118 | if self.isTouchButton and GS_IS_MOBILE_VERSION then |
119 | GuiOverlay.loadOverlay(self, self.touchIcon, "touchIcon", self.imageSize, nil, xmlFile, key) |
120 | GuiOverlay.createOverlay(self.touchIcon) |
121 | end |
122 | |
123 | local sampleName = getXMLString(xmlFile, key .. "#clickSound") or self.clickSoundName |
124 | local resolvedSampleName = GuiSoundPlayer.SOUND_SAMPLES[sampleName] |
125 | if resolvedSampleName ~= nil then |
126 | self.clickSoundName = resolvedSampleName |
127 | end |
128 | |
129 | GuiOverlay.createOverlay(self.overlay) |
130 | |
131 | self:updateSize() |
132 | end |
136 | function ButtonElement:loadProfile(profile, applyProfile) |
137 | ButtonElement:superClass().loadProfile(self, profile, applyProfile) |
138 | |
139 | GuiOverlay.loadOverlay(self, self.overlay, "image", self.imageSize, profile, nil, nil) |
140 | |
141 | self.iconSize = GuiUtils.getNormalizedValues(profile:getValue("iconSize"), self.outputSize, self.iconSize) |
142 | self.touchIconSize = GuiUtils.getNormalizedValues(profile:getValue("touchIconSize"), self.outputSize, self.touchIconSize) |
143 | self.iconTextOffset = GuiUtils.getNormalizedValues(profile:getValue("iconTextOffset"), self.outputSize, self.iconTextOffset) |
144 | self.hotspot = GuiUtils.getNormalizedValues(profile:getValue("hotspot"), self.outputSize, self.hotspot) |
145 | |
146 | |
147 | |
148 | self.forceFocus = profile:getBool("forceFocus", self.forceFocus) |
149 | self.forceHighlight = profile:getBool("forceHighlight", self.forceHighlight) |
150 | self.needExternalClick = profile:getBool("needExternalClick", self.needExternalClick) |
151 | self.fitToContent = profile:getBool("fitToContent", self.fitToContent) |
152 | self.fitExtraWidth = GuiUtils.getNormalizedValues(profile:getValue("fitExtraWidth"), self.outputSize, self.fitExtraWidth) |
153 | self.hideKeyboardGlyph = profile:getBool("hideKeyboardGlyph", self.hideKeyboardGlyph) |
154 | self.isTouchButton = profile:getBool("isTouchButton", self.isTouchButton) |
155 | self.addTouchArea = profile:getBool("addTouchArea", self.addTouchArea) |
156 | |
157 | local inputActionName = profile:getValue("inputAction", self.inputActionName) |
158 | if inputActionName ~= nil and InputAction[inputActionName] ~= nil then |
159 | self.inputActionName = inputActionName |
160 | self:loadInputGlyphColors(profile, nil, nil) |
161 | else |
162 | local imageSize = profile:getValue("iconImageSize") |
163 | if imageSize ~= nil then |
164 | local x, y = imageSize:getVector() |
165 | if x ~= nil and y ~= nil then |
166 | self.iconImageSize = {x, y} |
167 | end |
168 | end |
169 | GuiOverlay.loadOverlay(self, self.icon, "icon", self.iconImageSize, profile, nil, nil) |
170 | GuiOverlay.createOverlay(self.icon) |
171 | end |
172 | |
173 | if self.isTouchButton and GS_IS_MOBILE_VERSION then |
174 | GuiOverlay.loadOverlay(self, self.touchIcon, "touchIcon", self.imageSize, profile, nil, nil) |
175 | GuiOverlay.createOverlay(self.touchIcon) |
176 | end |
177 | |
178 | local sampleName = profile:getValue("clickSound", self.clickSoundName) |
179 | local resolvedSampleName = GuiSoundPlayer.SOUND_SAMPLES[sampleName] |
180 | if resolvedSampleName ~= nil then |
181 | self.clickSoundName = resolvedSampleName |
182 | end |
183 | |
184 | if applyProfile then |
185 | self:applyButtonAspectScale() |
186 | self:updateSize() |
187 | end |
188 | end |
428 | function ButtonElement:mouseEvent(posX, posY, isDown, isUp, button, eventUsed) |
429 | if self:getIsActive() then |
430 | eventUsed = eventUsed or ButtonElement:superClass().mouseEvent(self, posX, posY, isDown, isUp, button, eventUsed) |
431 | |
432 | -- handle highlight regardless of event used state |
433 | local cursorInElement = GuiUtils.checkOverlayOverlap(posX, posY, self.absPosition[1], self.absPosition[2], self.size[1], self.size[2], self.hotspot) |
434 | if cursorInElement then |
435 | if not self.mouseEntered then--and not self.focusActive then |
436 | -- set highlight on mouse over without focus |
437 | if not self.forceHighlight then |
438 | FocusManager:setHighlight(self) |
439 | end |
440 | |
441 | self.mouseEntered = true |
442 | end |
443 | else -- mouse event outside button |
444 | self:restoreOverlayState() |
445 | self.mouseDown = false |
446 | self.mouseEntered = false |
447 | if not self.forceHighlight then |
448 | -- reset highlight |
449 | FocusManager:unsetHighlight(self) |
450 | end |
451 | end |
452 | |
453 | -- handle click/activate only if event has not been consumed, yet |
454 | if not eventUsed then |
455 | if cursorInElement and not FocusManager:isLocked() then |
456 | if isDown and button == Input.MOUSE_BUTTON_LEFT then |
457 | if self.handleFocus and not self.forceFocus then |
458 | FocusManager:setFocus(self) -- focus on mouse down |
459 | eventUsed = true |
460 | end |
461 | |
462 | self.mouseDown = true |
463 | end |
464 | |
465 | -- if needed, set state to PRESSED and store current overlay state for restoration |
466 | if self.mouseDown and self:getOverlayState() ~= GuiOverlay.STATE_PRESSED then |
467 | self:storeOverlayState() |
468 | self:setOverlayState(GuiOverlay.STATE_PRESSED) |
469 | end |
470 | |
471 | if isUp and button == Input.MOUSE_BUTTON_LEFT and self.mouseDown then |
472 | self:restoreOverlayState() |
473 | self.mouseDown = false |
474 | self:sendAction() |
475 | |
476 | eventUsed = true |
477 | end |
478 | end |
479 | end |
480 | end |
481 | |
482 | return eventUsed |
483 | 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.touchIcon = {} |
46 | self.iconSize = {0,0} |
47 | self.touchIconSize = {0,0} |
48 | self.iconTextOffset = {0,0} |
49 | self.focusedTextOffset = {0,0} |
50 | self.hotspot = {0, 0, 0, 0} -- to define clickable area offset |
51 | self.needExternalClick = false -- used to override focus behaviour in special cases |
52 | self.clickSoundName = GuiSoundPlayer.SOUND_SAMPLES.CLICK |
53 | self.fitToContent = false |
54 | self.fitExtraWidth = {0} |
55 | self.hideKeyboardGlyph = false |
56 | self.isTouchButton = false |
57 | self.addTouchArea = true |
58 | |
59 | self.inputActionName = nil -- name of input action whose primary input binding will be displayed as a glyph, if set |
60 | self.hasLoadedInputGlyph = false |
61 | self.isKeyboardMode = false |
62 | self.keyDisplayText = nil -- resolved key display text for the input action |
63 | self.keyOverlay = nil -- holds a shared keyboard key glyph display overlay, do not delete! |
64 | self.keyGlyphOffsetX = 0 -- additional text offset when displaying keyboard key glyph |
65 | self.keyGlyphSize = {0, 0} |
66 | self.iconColors = {color={1, 1, 1, 1}} -- holds overlay color information for keyboard key glyph display |
67 | self.iconImageSize = {1024, 1024} |
68 | |
69 | return self |
70 | end |
700 | function ButtonElement:updateSize(forceTextSize) |
701 | if (not self.fitToContent or not self.textAutoWidth or self.textMaxNumLines ~= 1) and not forceTextSize then |
702 | return |
703 | end |
704 | |
705 | local xOffset, _ = self:getTextOffset() |
706 | |
707 | -- Get width using the source text, as the element is supposed to fit all text (as |
708 | -- textAutoWidth is enabled and max lines is 1) |
709 | setTextBold(self.textBold) |
710 | local textWidth = getTextWidth(self.textSize, self.sourceText) + 0.001 |
711 | setTextBold(false) |
712 | |
713 | local width = xOffset + textWidth + self.fitExtraWidth[1] |
714 | local height |
715 | |
716 | if self.isTouchButton and self.isTouchMode and self.addTouchArea then |
717 | width = width + (58/1920) |
718 | height = 120/1280 |
719 | |
720 | if self.originalHeight ~= nil then |
721 | self.originalHeight = self.size[2] |
722 | end |
723 | else |
724 | height = self.originalHeight |
725 | self.originalHeight = nil |
726 | end |
727 | |
728 | self:setSize(width, height) |
729 | |
730 | if self.parent ~= nil and self.parent.invalidateLayout ~= nil then |
731 | self.parent:invalidateLayout() |
732 | end |
733 | end |