#include "ui_defs.lua" #include "ui_layout.lua" function Ui.HorizontalCarousel(obj) local hLayout = Ui.HorizontalLayout{} local carouselLogicUpdate = function(self) self:getSize() local ib = self._inputBeh ib.disabled = false ib:update() ib.disabled = true if ib.prevRequested and not self._lerping then local newIdx = self.currentItemIdx - 1 if newIdx < 1 then newIdx = #self.children end if newIdx ~= self.currentItemIdx then self._addOffset = -(self.children[self.currentItemIdx].w / 2 + self.padding + self.children[newIdx].w / 2) self._lerpOffset = self._addOffset self:_startLerp(1) self.currentItemIdx = newIdx self:onSelectionChanged(newIdx) end elseif ib.nextRequested and not self._lerping then local newIdx = self.currentItemIdx + 1 if newIdx > #self.children then newIdx = 1 end if newIdx ~= self.currentItemIdx then self._addOffset = self.children[self.currentItemIdx].w / 2 + self.padding + self.children[newIdx].w / 2 self._lerpOffset = self._addOffset self:_startLerp(1) self.currentItemIdx = newIdx self:onSelectionChanged(newIdx) end elseif self._lerping then self._lerpOffset = self._addOffset * (1 - self:_getLerpProgress()) if self:_getLerpProgress() == 1 then self._lerping = false self._addOffset = 0 end end end local carousel = Ui.Object:new { padding = 0, alignment = Ui.ContentLayout.Vertical.Top, currentItemIdx = 1, lerpSpeed = 10, onSelectionChanged = function(self, idx) end, draw = function(self) carouselLogicUpdate(self) UiPush() hLayout.padding = self.padding hLayout.alignment = self.alignment local hlChildren = { self.children[self.currentItemIdx] } local centerOffset = self.w / 2 -- prepend children from left if necessary local prependOffset = 0 local idx = self.currentItemIdx - 1 while prependOffset < centerOffset + self._lerpOffset do local c = nil -- if there is no child from left take it from the end of the array if idx < 1 then local acIdx = idx while acIdx < 1 do acIdx = acIdx + #self.children end c = self.children[acIdx] else c = self.children[idx] end table.insert(hlChildren, 1, c) prependOffset = prependOffset + c.w + self.padding idx = idx - 1 end -- append children to right if necessary local appendOffset = 0 idx = self.currentItemIdx + 1 while appendOffset < centerOffset - self._lerpOffset do local c = nil -- if there is no child from left take it from the end of the array if idx > #self.children then local acIdx = idx while acIdx > #self.children do acIdx = acIdx - #self.children end c = self.children[acIdx] else c = self.children[idx] end table.insert(hlChildren, c) appendOffset = appendOffset + c.w + self.padding idx = idx + 1 end -- calculate and apply offset for hlayout local offset = centerOffset - prependOffset - self.children[self.currentItemIdx].w / 2 -- draw hlayout with its children hLayout.children = hlChildren UiClipRect(self.w, self.h, true) UiTranslate(offset + self._lerpOffset, 0) hLayout:draw() UiPop() end, getSize = function(self) self.h = tableMax( self.children, function(a, b) local _, ah = a:getSize() local _, bh = b:getSize() return ah > bh end ).h local totalWidth = tableAccumulate( self.children, function(acc, it) local itw = it:getSize() return acc + itw + self.padding end, -self.padding) if totalWidth < self.w then self.w = totalWidth end return self.w, self.h end, scrollToItem = function(self, idx, direction) if self._lerping then return end local dir = idx < self.currentItemIdx and -1 or 1 dir = direction or dir local offset = 0 local dist = 0 local i = self.currentItemIdx while i ~= idx do local ni = i + dir if ni > #self.children then ni = 1 elseif ni < 1 then ni = #self.children end offset = offset + self.children[i].w / 2 + self.padding + self.children[ni].w / 2 i = i + dir if i > #self.children then i = 1 elseif i < 1 then i = #self.children end dist = dist + math.abs(dir) end self._addOffset = offset * dir self._lerpOffset = self._addOffset self:_startLerp(dist) self.currentItemIdx = idx self:onSelectionChanged(idx) end, _inputBeh = UiCreateListInputBehaviour(true), -- fields for lerp and switch logic _lerpOffset = 0, _addOffset = 0, _lerping = false, _lerpStartTime = 0, _lerpTotalTime = 0.1, _startLerp = function(self, dist) self._lerping = true self._lerpStartTime = GetTime() self._lerpTotalTime = dist / self.lerpSpeed end, _getLerpProgress = function(self) return clamp((GetTime() - self._lerpStartTime) / self._lerpTotalTime, 0, 1) end } local newCarousel = carousel:new(obj) return newCarousel end