-- This file contains definitions, which are used by Ui library. #include "ui/ui_helpers.lua" Ui = {} --all UI related things shouldn't be serialized Ui.nonserializable = true Ui.Style = {} Ui.Style.Color = { Black = 0x000000, White = 0xFFFFFF, Yellow = 0xFFFF7F, Red = 0xFF0000, Mark = 0xFFCD66FF, Dim = 0xB4B4B4FF, } Ui.TextAlignment = { Left = "left", Right = "right", Center = "center" } Ui.Color = function(color) UiColor(hexrgb(color)) end Ui.ColorFilter = function(color) UiColorFilter(hexrgb(color)) end Ui.fontScale = 1.23 function Ui:Font(name, size) UiFont(name, size * self.fontScale) end function Ui:RegularFont(size) Ui:Font("regular.ttf", size) end function Ui:BoldFont(size) Ui:Font("bold.ttf", size) end function Ui:MediumFont(size) Ui:Font("medium.ttf", size) end function deepcopy(orig, copies) copies = copies or {} local orig_type = type(orig) local copy if orig_type == 'table' then if copies[orig] then copy = copies[orig] else copy = {} copies[orig] = copy for orig_key, orig_value in next, orig, nil do copy[deepcopy(orig_key, copies)] = deepcopy(orig_value, copies) end setmetatable(copy, deepcopy(getmetatable(orig), copies)) end else -- number, string, boolean, etc copy = orig end return copy end function GetTextSizeWithFont(txt, fontName, fontSize) UiPush() UiFont(fontName, fontSize) local w, h = UiGetTextSize(txt) UiPop() return w, h end function UiTextWithFont(txt, fontName, fontSize) UiPush() UiFont(fontName, fontSize) local w,h,_,_,linkid = UiText(txt) UiPop() return w,h,linkid end function Ui._CreateDragBehaviour() local beh = { isWaiting = false, isDragging = false, isActive = false, dragX = 0, dragY = 0, update = function(self, w, h) self.w = w self.h = h if not self._state then self.dragX = 0 self.dragY = 0 self._state = self._inactiveState end self._state:update(self) end, _inactiveState = { update = function(self, parent) if InputPressed("lmb") and UiIsMouseInRect(parent.w, parent.h) then parent._draggingState.startPos = { UiGetMousePos() } parent._draggingState.startTime = GetTime() parent._state = parent._draggingState end end }, _draggingState = { update = function(self, parent) local timeDelta = GetTime() - self.startTime if InputDown("lmb") then local x,y = UiGetMousePos() parent.dragX = x - self.startPos[1] parent.dragY = y - self.startPos[2] self.startPos = { x, y } elseif not InputDown("lmb") then if timeDelta > 0.2 then InputClear() end parent.dragX = 0 parent.dragY = 0 parent._state = parent._inactiveState end end } } return beh end function UiCreateListInputBehaviour(isHorizontal) local beh = { nextRequested = false, prevRequested = false, nextAction = isHorizontal and "menu_right" or "menu_down", prevAction = isHorizontal and "menu_left" or "menu_up", disabled = false, update = function(self) if self.disabled then return end local waitTime = 0.5 local repeatPeriod = 0.05 self:clearRequests() if InputPressed(self.nextAction) then self.nextRequested = true self.nextIsPressed = true self.prevIsPressed = false self.waitStartTime = GetTime() elseif InputPressed(self.prevAction) then self.prevRequested = true self.nextIsPressed = false self.prevIsPressed = true self.waitStartTime = GetTime() end if self.nextIsPressed and InputDown(self.nextAction) then if GetTime() - self.waitStartTime > waitTime and not self.repeatEvent then self.repeatEvent = true self.waitStartTime = GetTime() self.nextRequested = true end if self.repeatEvent then if GetTime() - self.waitStartTime > repeatPeriod then self.waitStartTime = GetTime() self.nextRequested = true end end elseif self.nextIsPressed and not InputDown(self.nextAction) then self.nextIsPressed = false self.repeatEvent = false end if self.prevIsPressed and InputDown(self.prevAction) then if GetTime() - self.waitStartTime > waitTime and not self.repeatEvent then self.repeatEvent = true self.waitStartTime = GetTime() self.prevRequested = true end if self.repeatEvent then if GetTime() - self.waitStartTime > repeatPeriod then self.waitStartTime = GetTime() self.prevRequested = true end end elseif self.prevIsPressed and not InputDown(self.prevAction) then self.prevIsPressed = false self.repeatEvent = false end end, clearRequests = function(self) self.nextRequested = false self.prevRequested = false end } return beh end local _scCount = 0 function UiCreateScrollBar(thumbObj, backgroundObj, isHorizontal) _scCount = _scCount or 0 _scCount = _scCount + 1 thumbObj.offset = thumbObj.offset or { 2, 2 } local scrollBar = { isHorizontal = isHorizontal or false, w = backgroundObj.w, h = backgroundObj.h, thumb = thumbObj, background = backgroundObj, thumbDrag = false, thumbDragPos = 0, thumbDelta = 0, thumbDeltaLerp = 0, lerpStartTime = 0, lerping = false, allowLerp = true, preserveState = false, lerpSpeed = 5, selectedIdx = 0, navIdx = 0, forceIdx = 0, itemsPadding = 0, scrollbarOffset = 5, scrollSpeed = 60, allowListDrag = false, listDrag = false, listDragPos = 0, listDragDelta = 0, ignoreWheelScroll = false, holdFocusOnListEnd = true, inputBehaviour = UiCreateListInputBehaviour(isHorizontal), dragBehaviour = Ui._CreateDragBehaviour(), _id = tostring(GetScriptId()) .. "_" .. _scCount, contentSpan = 1, rect = { w = 1, h = 1 }, onLerpStop = function() end, drawScrollbar = function(self) self:_logicUpdate() if not self.isShown or self.forceHideScrollbar then return end UiPush() UiColorFilter(1,1,1,0.5) self.background:draw() if self.isHorizontal then UiTranslate(self.thumbDeltaLerp, 0) else UiTranslate(0, self.thumbDeltaLerp) end -- NOTE: Different hover and drag states if not UiIsMouseInRect(self.thumb.w, self.thumb.h) and not self.thumbDrag then UiColorFilter(0.7, 0.7, 0.7, 1) elseif not self.thumbDrag then UiColorFilter(0.85, 0.85, 0.85,1) end self.thumb:draw() UiPop() end, getOffset = function(self) local listSpan = self.isHorizontal and self.rect.w or self.rect.h local thumbSpan = self.isHorizontal and self.thumb.w or self.thumb.h local bgSpan = self.isHorizontal and self.background.w or self.background.h return -(self.contentSpan - listSpan) * self.thumbDeltaLerp / (bgSpan - thumbSpan) end, addOffset = function(self, offset, noLerp) if not self.isShown or offset == 0 then return end local listSpan = self.isHorizontal and self.rect.w or self.rect.h local thumbSpan = self.isHorizontal and self.thumb.w or self.thumb.h local bgSpan = self.isHorizontal and self.background.w or self.background.h self.thumbDelta = self.thumbDelta - offset * (bgSpan - thumbSpan) / (self.contentSpan - listSpan) self.thumbDelta = clamp(self.thumbDelta, 0, (bgSpan - thumbSpan)) if noLerp then self.thumbDeltaLerp = self.thumbDelta elseif self.thumbDelta ~= self.thumbDeltaLerp then self.lerpStarDelta = self.thumbDeltaLerp self.lerpStartTime = GetTime() self.lerping = true end end, getTranslateOffset = function(self) if self.isHorizontal then return self:getOffset(), 0 else return 0, self:getOffset() end end, getTranslateToScrollbar = function(self) if self.isHorizontal then return 0, self.rect.h + self.scrollbarOffset else return self.rect.w + self.scrollbarOffset, 0 end end, reset = function(self) self.thumbDelta = 0 self.thumbDeltaLerp = 0 self.selectedIdx = 0 self.navIdx = 0 self.forceIdx = 0 end, scrollToItem = function(self, idx, allowLerp) self.navIdx = idx self.forceIdx = idx self.allowLerp = allowLerp or false end, _logicUpdate = function(self) local listSpan = self.isHorizontal and self.rect.w or self.rect.h local thumbSpan = self.isHorizontal and self.thumb.w or self.thumb.h local bgSpan = self.isHorizontal and self.background.w or self.background.h local bgWidth = self.isHorizontal and self.background.h or self.background.w self.isShown = self.contentSpan > listSpan if not self.isShown then return end UiPush() thumbSpan = bgSpan * listSpan / self.contentSpan if self.isHorizontal then self.thumb.w = thumbSpan else self.thumb.h = thumbSpan end self.thumbDeltaLerp = lerp(self.thumbDeltaLerp, self.thumbDelta, (GetTime() - self.lerpStartTime) * self.lerpSpeed) if self.thumbDeltaLerp == self.thumbDelta and self.lerping then self.lerping = false self:onLerpStop() end if self.isHorizontal then UiTranslate(self.thumbDeltaLerp, 0) else UiTranslate(0, self.thumbDeltaLerp) end if InputPressed("lmb") and UiIsMouseInRect(self.thumb.w, self.thumb.h) then self.thumbDrag = true local mx, my = UiGetMousePos() self.thumbDragPos = self.isHorizontal and mx or my elseif self.thumbDrag and InputDown("lmb") then local mx, my = UiGetMousePos() local pos = self.isHorizontal and mx or my self.thumbDelta = self.thumbDeltaLerp + pos - self.thumbDragPos self.thumbDelta = clamp(self.thumbDelta, 0, (bgSpan - thumbSpan)) self.navIdx = 0 self.forceIdx = 0 elseif InputReleased("lmb") then self.thumbDrag = false end UiPush() local inRect = false if self.isHorizontal then UiTranslate(-self.thumbDelta, 0) inRect = UiIsMouseInRect(self.thumbDelta, bgWidth) else UiTranslate(0, -self.thumbDelta) inRect = UiIsMouseInRect(bgWidth, self.thumbDelta) end if InputPressed("lmb") and inRect then local mx, my = UiGetMousePos() self.thumbDelta = self.isHorizontal and mx or my self.thumbDelta = clamp(self.thumbDelta, 0, (bgSpan - thumbSpan)) end UiPop() UiPush() inRect = false if self.isHorizontal then UiTranslate(thumbSpan, 0) inRect = UiIsMouseInRect(bgSpan - thumbSpan - self.thumbDelta, bgWidth) else UiTranslate(0, thumbSpan) inRect = UiIsMouseInRect(bgWidth, bgSpan - thumbSpan - self.thumbDelta) end if InputPressed("lmb") and inRect then local mx, my = UiGetMousePos() self.thumbDelta = self.thumbDelta + (self.isHorizontal and mx or my) self.thumbDelta = clamp(self.thumbDelta, 0, (bgSpan - thumbSpan)) end UiPop() self.thumbDeltaLerp = clamp(self.thumbDeltaLerp, 0, (bgSpan - thumbSpan)) UiPop() end, clickSound = "common/click.ogg" } return scrollBar end function UiCreateListState(isHorizontal) if isHorizontal then return UiCreateScrollBar( { w = 120, h = 8, color = {1, 1, 1, 0.6}, imageBox = { "common/rcorners_box_3.png", 3, 3 }, offset = { 2, 2 }, draw = function(self) UiPush() UiTranslate(unpack(self.offset)) UiColor(unpack(self.color)) UiImageBox(self.imageBox[1], self.w, self.h, self.imageBox[2], self.imageBox[3]) UiPop() end, }, { w = 240, h = 12, color = {0, 0, 0, 0.6}, imageBox = { "common/rcorners_box_4.png", 4, 4 }, draw = function(self) UiPush() UiColor(unpack(self.color)) UiImageBox(self.imageBox[1], self.w, self.h, self.imageBox[2], self.imageBox[3]) UiPop() end, }, true) else return UiCreateScrollBar( { w = 8, h = 120, color = {1, 1, 1, 0.6}, imageBox = { "common/rcorners_box_3.png", 3, 3 }, offset = { 2, 2 }, draw = function(self) UiPush() UiTranslate(unpack(self.offset)) UiColor(unpack(self.color)) UiImageBox(self.imageBox[1], self.w, self.h, self.imageBox[2], self.imageBox[3]) UiPop() end, }, { w = 12, h = 240, color = {0, 0, 0, 0.6}, imageBox = { "common/rcorners_box_4.png", 4, 4 }, draw = function(self) UiPush() UiColor(unpack(self.color)) UiImageBox(self.imageBox[1], self.w, self.h, self.imageBox[2], self.imageBox[3]) UiPop() end, }, false) end end function UiListItemProxy(state) local proxy = {} if state.isHorizontal then proxy = { isHorizontal = true, size = function(item) return item.w end, offset = function(item) return item.w + state.itemsPadding end, getTranslateToNext = function(item) return item.w + state.itemsPadding, 0 end } else proxy = { isHorizontal = false, size = function(item) return item.h end, offset = function(item) return item.h + state.itemsPadding end, getTranslateToNext = function(item) return 0, item.h + state.itemsPadding end } end return proxy end function UiList(w, h, items, state) if #items == 0 then return end local itemInfo = UiListItemProxy(state) state.contentSpan = tableAccumulate( items, function(sum, it) return sum + itemInfo.offset(it) end, 0 ) - state.itemsPadding state.rect.x, state.rect.y = UiGetCursorPos() state.rect.w = w state.rect.h = h if state.allowListDrag then state.dragBehaviour:update(w, h) local dragOffset = state.isHorizontal and state.dragBehaviour.dragX or state.dragBehaviour.dragY state:addOffset(dragOffset, true) end state.inputBehaviour:update() -- if the list wasn't drawn on previous draw then return list to initial state if state.lastFrame ~= UiGetFrameNo() - 1 and not state.preserveState then state:reset() end state.lastFrame = UiGetFrameNo() UiPush() UiTranslate(state:getTranslateToScrollbar()) state:drawScrollbar() UiPop() UiPush() UiAlign("top left") UiClipRect(w, h, true) UiNavGroupBegin() UiNavGroupSize(w, h) UiPush() if UiIsMouseInRect(w, h) and not state.ignoreWheelScroll then local mouseScroll = InputValue("mousewheel") * state.scrollSpeed if mouseScroll ~= 0 then state:addOffset(mouseScroll) state.navIdx = 0 state.forceIdx = 0 end end UiTranslate(state:getTranslateOffset()) local focusedComponentIdx = 0 local itemChanged = false for i=1, #items do local sx, sy = UiGetScale() local isVisible = not UiIsFullyClipped(items[i].w * sx, items[i].h * sy) local itemId = "ITEM_" .. tostring(state._id) .. "_" .. tostring(i) UiNavGroupBegin(itemId) UiNavGroupSize(items[i].w, items[i].h) if isVisible then UiPush() items[i]:draw() UiPop() end UiNavGroupEnd() if UiIsComponentInFocus(itemId) then -- capture focus if state.forceIdx == 0 then state.forceIdx = i end focusedComponentIdx = i if state.inputBehaviour.nextRequested and not itemChanged and not state.lerping then if i ~= #items then UiNavSkipUpdate() local idx = clamp(i + 1, 1, #items) state.forceIdx = idx itemChanged = true elseif i == #items and state.holdFocusOnListEnd then UiNavSkipUpdate() end elseif state.inputBehaviour.prevRequested and not itemChanged and not state.lerping then if i ~= 1 then UiNavSkipUpdate() local idx = clamp(i - 1, 1, #items) state.forceIdx = idx itemChanged = true elseif i == 1 and state.holdFocusOnListEnd then UiNavSkipUpdate() end end end if UiIsMouseInRect(items[i].w, items[i].h) then focusedComponentIdx = i if InputPressed("lmb") then state.lastSelectedItem = items[i] end end UiTranslate(itemInfo.getTranslateToNext(items[i])) end -- check if list has lost focus if state.isHorizontal and wasAnyActionPressed({ "menu_up", "menu_down" }) then state.forceIdx = 0 elseif not state.isHorizontal and wasAnyActionPressed({ "menu_left", "menu_right" }) then state.forceIdx = 0 end if state.forceIdx ~= 0 then local navId = "ITEM_" .. tostring(state._id) .. "_" .. tostring(state.forceIdx) UiForceFocus(navId) state.navIdx = state.forceIdx UiNavSkipUpdate() state.forceIdx = 0 end if state.navIdx ~= 0 and items[state.navIdx] ~= nil then local listDim = state.isHorizontal and w or h local itemPos = state:getOffset() for i=1,state.navIdx - 1 do itemPos = itemPos + itemInfo.offset(items[i]) end local topBorder = (state.navIdx == 1) and 0 or (0.1 * listDim * UiGetScale()) local bottomBorder = (state.navIdx == #items) and (listDim) or (listDim * 0.9 * UiGetScale()) local itemInVisibleRegion = (itemPos >= topBorder) and ((itemPos + itemInfo.size(items[state.navIdx]) * UiGetScale()) <= bottomBorder) if not itemInVisibleRegion and not state.lerping then if itemPos < topBorder then local offset = topBorder - itemPos state:addOffset(offset / UiGetScale(), not state.allowLerp) state.allowLerp = true elseif itemPos + itemInfo.size(items[state.navIdx]) > bottomBorder then local offset = bottomBorder - (itemPos + itemInfo.size(items[state.navIdx])) state:addOffset(offset / UiGetScale(), not state.allowLerp) state.allowLerp = true end end end UiPop() UiNavGroupEnd() UiPop() state.selectedIdx = focusedComponentIdx end function UiListHorizontalWithButtons(w, h, items, state) local scrollWidth = state.background.w local needArrows = state.contentSpan > w and LastInputDevice() ~= UI_DEVICE_GAMEPAD if needArrows then w = w - 98 state.background.w = state.background.w - 98 UiTranslate(49, 0) local pressed = false UiPush() UiAlign("middle right") UiTranslate(-20, h / 2) local scrollSize = items[1].w + state.itemsPadding local isLeftSide = state.thumbDelta == 0 local isRightSide = state.thumbDelta == (state.background.w - state.thumb.w) UiPush() UiColorFilter(1,1,1, isLeftSide and 0 or 1) if UiImageButton("common/scroll_arrow_left.png") then UiSound("common/click.ogg") state:addOffset(scrollSize) state.navIdx = 0 state.forceIdx = 0 pressed = true end UiPop() UiTranslate(w + 40, 0) UiAlign("middle left") UiPush() UiColorFilter(1,1,1, isRightSide and 0 or 1) if UiImageButton("common/scroll_arrow_right.png") then UiSound("common/click.ogg") state:addOffset(-scrollSize) state.navIdx = 0 state.forceIdx = 0 pressed = true end UiPop() UiPop() if pressed then UiDisableInput() UiIgnoreNavigation() end end UiList(w, h, items, state) state.background.w = scrollWidth end function UiCreateList(isHorizontal) isHorizontal = isHorizontal or false local list = { w = 100, h = 100, items = {}, isHorizontal = isHorizontal, state = UiCreateListState(isHorizontal), lastSelectedItem = nil, lastSelectedIdx = 0, onSelectionChanged = function(self) end, draw = function(self) UiList(self.w, self.h, self.items, self.state) if self.state.selectedIdx ~= self.lastSelectedIdx then self.lastSelectedIdx = self.state.selectedIdx self.lastSelectedItem = self.items[self.state.selectedIdx] self:onSelectionChanged() end end } return list end function UiCreateDropDownState() local state = { isOpened = false, values = {}, selectedIdx = 1, initialized = false, wasOpened = false, isClosing = false, wasClosed = true, fade = 0, onSelectionChanged = function(self, idx) end, onClosed = function(self) end } return state end function UiDropDownList(mainView, listView, itemTemplate, state) listView.list.listItems = {} for i=1,#state.values do local item = { itemT = itemTemplate, w = itemTemplate.w, h = itemTemplate.h, dropDownState = state, idx = i, draw = function(self) self.itemT:draw(self.idx == self.dropDownState.selectedIdx, self.dropDownState.values[self.idx]) end } listView.list.listItems[i] = item end UiPush() if not state.isOpened then local gId = UiNavGroupBegin() UiNavGroupSize(mainView.w, mainView.h) mainView:drawClosed(state.values[state.selectedIdx]) UiNavGroupEnd() UiPush() UiIgnoreNavigation() UiTranslate(listView.offsetX, listView.offsetY) if UiIsComponentInFocus(gId) and state.fade and state.fade ~= 0 then UiColorFilter(1, 1, 1, state.fade) listView:draw() end UiPop() if state.wasOpened then state.wasOpened = false state.isClosing = true end if state.isClosing then if not UiIsComponentInFocus(gId) then UiForceFocus(gId) else UiNavSkipUpdate() end end if state.isClosing and state.fade == 0 then state.isClosing = false listView.list.scrollBar:reset() state:onClosed() end if InputPressed("lmb") and UiIsMouseInRect(mainView.w, mainView.h) then UiSound("common/click.ogg") state.isOpened = true end if UiIsComponentInFocus(gId) and InputPressed("menu_accept") then UiSound("common/click.ogg") state.isOpened = true end if state.isOpened == true then SetValueInTable(state, "fade", 1, "linear", 0.1) state.wasClosed = true end else local x, y = UiGetCursorPos() local obj = { main = mainView, list = listView, st = state, scale = UiGetScale(), draw = function(self) for i=1,#self.list.list.listItems do self.list.list.listItems[i] = nil end for i=1,#self.st.values do local item = { itemT = itemTemplate, w = itemTemplate.w, h = itemTemplate.h, dropDownState = state, idx = i, draw = function(self) self.itemT:draw(self.idx == self.dropDownState.selectedIdx, self.dropDownState.values[self.idx]) end } self.list.list.listItems[i] = item end UiPush() UiModalBegin(true) UiEnableInput() UiTranslate(x, y) UiPush() UiScale(self.scale) UiAlign("top left") self.main:drawOpened(self.st.values[self.st.selectedIdx]) UiTranslate(self.list.offsetX, self.list.offsetY) local listId = UiNavGroupBegin() UiNavGroupSize(self.list.w, self.list.h) UiColorFilter(1, 1, 1, self.st.fade) if wasAnyActionPressed({ "menu_left", "menu_right" }) then InputClear() end self.list:draw() UiNavGroupEnd() if (UiIsComponentInFocus(listId) or UiIsMouseInRect(self.list.w, self.list.h)) then if InputPressed("menu_accept") or InputPressed('lmb') then if self.st.selectedIdx ~= self.list.list.scrollBar.selectedIdx and self.list.list.scrollBar.selectedIdx ~= 0 then UiSound("common/click.ogg") self.st.selectedIdx = self.list.list.scrollBar.selectedIdx self.st:onSelectionChanged(self.st.selectedIdx) end end end UiPop() local outOfMainView = not UiIsMouseInRect(self.main.w, self.main.h) if InputPressed("lmb") and not outOfMainView then self.st.isOpened = false end UiTranslate(self.main.w - self.list.w, self.main.h) local outOfListView = not UiIsMouseInRect(self.list.w, self.list.h) if outOfMainView and outOfListView and InputPressed('lmb') then self.st.isOpened = false end if InputPressed("menu_cancel") then self.st.isOpened = false end if self.st.isOpened == false then self.st.wasOpened = true SetValueInTable(self.st, "fade", 0, "linear", 0.1) end if self.st.fade > 0 and self.st.fade < 1 then if not UiIsComponentInFocus(listId) then self.list.list.scrollBar:scrollToItem(self.st.selectedIdx) end end UiPop() end } UiDrawLater(obj) end UiPop() end function UiCreateDialogState() local state = { w = 100, h = 100, requestShow = false, requestHide = false, isClosed = true, fade = 0, onNavigateBack = function(self) end, onLogicUpdate = function(self) if InputPressed("menu_cancel") or InputPressed("esc") then self:hide() self:onNavigateBack() InputClear() return end UiTranslateToScreenCenter(-self.w / 2, 0) UiTranslateToScreenMiddle(0, -self.h / 2) if not UiIsMouseInRect(self.w, self.h) and InputPressed("lmb") and GetBool("game.cursor.enabled") then self:hide() self:onNavigateBack() InputClear() end end, onDraw = function(self) end, onClose = function(self) end, onShow = function(self) end, onBeginClosing = function(self) end, onBeginShowing = function(self) end, show = function(self) self.requestShow = true end, hide = function(self) self.requestHide = true end, update = function(self) if self.requestShow then SetValueInTable(self, "fade", 1, "linear", 0.1) self.requestShow = false self:onBeginShowing() end if self.requestHide then SetValueInTable(self, "fade", 0, "linear", 0.1) self.requestHide = false self:onBeginClosing() end if self.fade == 0 and self.isClosed then return end if self.isClosed == false and self.fade == 0 then self.isClosed = true self:onClose() end if self.fade == 1 then if self.isClosed == true then self.isClosed = false self:onShow() end end local drawObj = { state = self, draw = function(self) UiPush() UiBlur(self.state.fade) UiColorFilter(1, 1, 1, self.state.fade) UiModalBegin(true) UiBeginFrame() UiResetFrame() self.state:onDraw() self.state.w, self.state.h = UiEndFrame() self.state:onLogicUpdate() UiModalEnd() UiPop() end } UiDrawLater(drawObj) end } return state end local safeAreaOffset = { x = 108, y = 170 } function UiDebugDrawSafeArea() UiPush() UiColor(1,0,0, 0.2) UiRect(safeAreaOffset.x, UiHeight()) UiTranslate(safeAreaOffset.x,0) UiRect(UiWidth(), safeAreaOffset.y) UiPop() end function UiTranslateCursorToSafeArea(offsetX, offsetY) UiTranslateToScreenLeftEdge(safeAreaOffset.x + offsetX, 0) UiTranslateToScreenTopEdge(0, safeAreaOffset.y + offsetY) end function UiDrawGamepadHintsLeftAlign(hintsList) local padding = 0 local indent = 30 UiPush() UiAlign("middle left") for i=1,#hintsList do local ico = hintsList[i].ico local icoWidth = GetTextSizeWithFont(ico, "regular.ttf", 27) local txtWidth = 0 if hintsList[i].txt then txtWidth = GetTextSizeWithFont(GetTranslatedStringByKey(hintsList[i].txt), "regular.ttf", 27) else txtWidth = GetTextSizeWithFont(hintsList[i].rawTxt, "regular.ttf", 27) end UiTextWithFont(ico, "regular.ttf", 27) UiTranslate(icoWidth + padding, 0) UiPush() if hintsList[i].txtColor then Ui.Color(hintsList[i].txtColor) else UiColorFilter(1, 1, 1, 0.7) end local txt = hintsList[i].txt or hintsList[i].rawTxt UiTextWithFont(txt, "regular.ttf", 27) UiPop() UiTranslate(txtWidth + indent, 0) end UiPop() end function UiDrawGamepadHintsRightAlign(hintsList) local padding = 10 local indent = 30 UiPush() UiAlign("middle right") for i=1,#hintsList do local ico = hintsList[#hintsList - i + 1].ico local txt = GetTranslatedStringByKey(hintsList[#hintsList - i + 1].txt) local icoWidth = GetTextSizeWithFont(ico, "regular.ttf", 27) local txtWidth = GetTextSizeWithFont(txt, "regular.ttf", 27) UiTextWithFont(hintsList[#hintsList - i + 1].txt, "regular.ttf", 27) UiTranslate(-(txtWidth + padding), 0) UiTextWithFont(ico, "regular.ttf", 27) UiTranslate(-(icoWidth + indent), 0) end UiPop() end function UiDrawHintsCentered(hintsList, indentt) local padding = IsRunningOnPlaystation() and 6 or 2 local indent = indentt or 30 --calculate total width local totalWidth = 0 for i=1,#hintsList do local icoWidth = GetTextSizeWithFont(hintsList[i].ico, "regular.ttf", 27) local txtWidth = 0 if hintsList[i].txt then txtWidth = GetTextSizeWithFont(GetTranslatedStringByKey(hintsList[i].txt), "regular.ttf", 27) else txtWidth = GetTextSizeWithFont(hintsList[i].rawTxt, "regular.ttf", 27) end totalWidth = totalWidth + icoWidth + txtWidth + padding + indent end totalWidth = totalWidth - indent UiPush() UiAlign("middle left") UiTranslate(-(totalWidth) / 2, 0) for i=1,#hintsList do local ico = hintsList[i].ico local icoWidth, _ = GetTextSizeWithFont(ico, "regular.ttf", 27) local txtWidth = 0 local txtHeight = 0 if hintsList[i].txt then txtWidth, txtHeight = GetTextSizeWithFont(GetTranslatedStringByKey(hintsList[i].txt), "regular.ttf", 27) else txtWidth, txtHeight = GetTextSizeWithFont(hintsList[i].rawTxt, "regular.ttf", 27) end UiTextWithFont(ico, "regular.ttf", 27) UiTranslate(icoWidth + padding, 0) UiPush() UiFont("regular.ttf", 27) if hintsList[i].txtColor then Ui.Color(hintsList[i].txtColor) else UiColorFilter(1, 1, 1, 0.7) end local txt = hintsList[i].txt or hintsList[i].rawTxt --attempt to align text taking into account the font ascent and descent UiAlign("bottom left") UiTranslate(0, txtHeight - UiFontHeight() / 2) UiTextWithFont(txt, "regular.ttf", 27) UiPop() UiTranslate(txtWidth + indent, 0) end UiPop() end function GetScreenSize() local displayIndex = GetInt("options.display.index") local displayMode = GetInt("options.display.mode") local displayResolution = GetInt("options.display.resolution") local resW, resH = GetDisplayResolution(displayIndex, displayMode, displayResolution) return { w = resW, h = resH } end function UiSafeCanvasSize() return { w = 1920, h = 1080 } end