--- Implements a multi-choice widget -- -- @classmod yui.Choice -- @copyright 2022, The DoubleFourteen Code Forge -- @author Lorenzo Cogotti, Andrea Pasquini -- --- Multi-choice widget receives the following callbacks: @{yui.Widget.WidgetCallbacks|onEnter}(), @{yui.Widget.WidgetCallbacks|onChange}(), @{yui.Widget.WidgetCallbacks|onLeave}(). local BASE = (...):gsub('choice$', '') local Widget = require(BASE..'widget') local core = require(BASE..'core') local shadowtext = require 'lib.gear.shadowtext' local T = require('lib.moonspeak').translate local Choice = setmetatable({ __call = function(cls, args) return cls.new(args) end }, Widget) Choice.__index = Choice --- Attributes accepted by the @{Choice} widget beyond the standard @{yui.Widget.WidgetAttributes|attributes} -- and @{yui.Widget.WidgetCallbacks|callbacks}. -- -- @field choices (table) available choices list -- @field nowrap (boolean) disable choices wrapping -- @field[opt='center'] valign (string) vertical alignment 'top', 'bottom', 'center' -- @field[opt='center'] align (string) horizontal alignment, 'left', 'center', 'right' -- @field cornerRadius (number) radius for rounded corners -- @field notranslate (boolean) don't translate text -- @table ChoiceAttributes --- Choice constructor -- @param args (@{ChoiceAttributes}) widget attributes function Choice.new(args) local self = setmetatable(args, Choice) self.align = self.align or 'center' self.valign = self.valign or 'center' self.cornerRadius = self.cornerRadius or core.theme.cornerRadius self.color = self.color or core.theme.color self.hovered = false self.choices = self.choices or { "" } self.nowrap = self.nowrap or #self.choices == 0 self.index = 1 -- by default for i,choice in ipairs(self.choices) do -- Expand shorthands if type(choice) ~= 'table' then choice = { text = tostring(choice), notranslate = type(choice) ~= 'string', value = choice } self.choices[i] = choice end -- Mark default choice if needed if choice.value == self.default then self.index = i end -- Translate choice if not (self.notranslate or choice.notranslate) then choice.text = T(choice.text) end end return self end function Choice:checkIndex() if self.nowrap then self.index = math.min(math.max(self.index, 1), #self.choices) else if self.index < 1 then self.index = #self.choices end if self.index > #self.choices then self.index = 1 end end end function Choice:onActionInput(action) local oldindex = self.index local handled = false -- Change choice if action.left then self.index = oldindex - 1 handled = true end if action.right then self.index = oldindex + 1 handled = true end if not handled then return false end -- Apply wrapping self:checkIndex() -- Fire event if necessary if oldindex ~= self.index then self:onChange(self.choices[self.index]) end return true end function Choice:onPointerInput(px,py, clicked) self:grabFocus() if not clicked then return end local mx = px - self.x local oldindex = self.index -- Test whether arrows are hit -- NOTE: don't care about arrows being disabled, checkIndex() will fix that. if mx <= self.h+2 then self.index = self.index - 1 elseif mx >= self.w - self.h-2 then self.index = self.index + 1 end self:checkIndex() if oldindex ~= self.index then self:onChange(self.choices[self.index]) end end function Choice:draw() local x,y,w,h = self.x,self.y,self.w,self.h local font = self.font or love.graphics.getFont() local c = self:colorForState() core.drawBox(x,y,w,h, c, self.cornerRadius) if self.ui.focused == self then -- draw < and > arrows, desaturate color if arrow is disabled local cc = self.color.hovered love.graphics.setLineStyle('smooth') love.graphics.setLineWidth(3) love.graphics.setLineJoin('bevel') local r, g, b = cc.fg[1], cc.fg[2], cc.fg[3] local a = (self.nowrap and self.index == 1) and 0.4 or 1 love.graphics.setColor(r,g,b,a) love.graphics.line(x+h*.8,y+h*.2, x+h*.5,y+h*.5, x+h*.8,y+h*.8) a = (self.nowrap and self.index == #self.choices) and 0.4 or 1 love.graphics.setColor(r,g,b,a) love.graphics.line(x+w-h*.8,y+h*.2, x+w-h*.5,y+h*.5, x+w-h*.8,y+h*.8) end -- draw text local text = self.choices[self.index].text y = y + core.verticalOffsetForAlign(self.valign, font, h) love.graphics.setColor(c.fg) love.graphics.setFont(font) shadowtext.printf(text, x+h+2, y, w-2*(h + 2), self.align) end return Choice