cave-story-randomizer/caver/lib/luigi/painter.lua
2021-11-30 20:51:31 -06:00

238 lines
6.4 KiB
Lua

local ROOT = (...):gsub('[^.]*$', '')
local Backend = require(ROOT .. 'backend')
local Base = require(ROOT .. 'base')
local Event = require(ROOT .. 'event')
local Mosaic = require(ROOT .. 'mosaic')
local Text = Backend.Text
local Painter = Base:extend()
local imageCache = {}
-- local sliceCache = {}
function Painter:constructor (widget)
self.widget = widget
end
function Painter:loadImage (path)
if not imageCache[path] then
imageCache[path] = Backend.Image(path)
end
return imageCache[path]
end
function Painter:paintSlices ()
local widget = self.widget
local mosaic = Mosaic.fromWidget(widget)
if not mosaic then return end
local x, y, w, h = widget:getRectangle(true)
mosaic:setRectangle(x, y, w, h)
mosaic:draw()
end
function Painter:paintBackground ()
local widget = self.widget
if not widget.background then return end
local x, y, w, h = widget:getRectangle(true)
Backend.push()
Backend.setColor(widget.background)
Backend.drawRectangle('fill', x, y, w, h)
Backend.pop()
end
function Painter:paintOutline ()
local widget = self.widget
if not widget.outline then return end
local x, y, w, h = widget:getRectangle(true)
Backend.push()
Backend.setColor(widget.outline)
Backend.drawRectangle('line', x + 0.5, y + 0.5, w, h)
Backend.pop()
end
-- returns icon coordinates and rectangle with remaining space
function Painter:positionIcon (x1, y1, x2, y2)
local widget = self.widget
if not widget.icon then
return nil, nil, x1, y1, x2, y2
end
local icon = self:loadImage(widget.icon)
local iconWidth, iconHeight = icon:getWidth(), icon:getHeight()
local align = widget.align or ''
local padding = widget.padding or 0
local x, y
-- horizontal alignment
if align:find('right') then
x = x2 - iconWidth
x2 = x2 - iconWidth - padding
elseif align:find('center') then
x = x1 + (x2 - x1) / 2 - iconWidth / 2
else -- if align:find('left') then
x = x1
x1 = x1 + iconWidth + padding
end
-- vertical alignment
if align:find('bottom') then
y = y2 - iconHeight
elseif align:find('middle') then
y = y1 + (y2 - y1) / 2 - iconHeight / 2
else -- if align:find('top') then
y = y1
end
return x, y, x1, y1, x2, y2
end
-- returns text coordinates
function Painter:positionText (x1, y1, x2, y2)
local widget = self.widget
if not widget.text or x1 >= x2 then
return nil, nil, x1, y1, x2, y2
end
local font = widget:getFont()
local align = widget.align or ''
local horizontal = 'left'
-- horizontal alignment
if align:find 'right' then
horizontal = 'right'
elseif align:find 'center' then
horizontal = 'center'
elseif align:find 'justify' then
horizontal = 'justify'
end
if not widget.textData then
local limit = widget.wrap and x2 - x1 or nil
widget.textData = Text(
font, widget.text, widget.color, horizontal, limit)
end
local textHeight = widget.textData:getHeight()
local y
-- vertical alignment
if align:find('bottom') then
y = y2 - textHeight
elseif align:find('middle') then
y = y2 - (y2 - y1) / 2 - textHeight / 2
else -- if align:find('top') then
y = y1
end
return font, x1, y
end
function Painter:paintIconAndText ()
local widget = self.widget
if not (widget.icon or widget.text) then return end
local x, y, w, h = widget:getRectangle(true, true)
if w < 1 or h < 1 then return end
-- calculate position for icon and text based on alignment and padding
local iconX, iconY, x1, y1, x2, y2 = self:positionIcon(x, y, x + w, y + h)
local font, textX, textY = self:positionText(x1, y1, x2, y2)
local icon = widget.icon and self:loadImage(widget.icon)
local text = widget.text
local align = widget.align or ''
local padding = widget.padding or 0
-- if aligned center, icon displays above the text
-- reposition icon and text for proper vertical alignment
if icon and text and align:find('center') then
local iconHeight = icon:getHeight()
if align:find 'middle' then
local textHeight = widget.textData:getHeight()
local contentHeight = textHeight + padding + iconHeight
local offset = (h - contentHeight) / 2
iconY = y + offset
textY = y + offset + padding + iconHeight
elseif align:find 'top' then
iconY = y
textY = y + padding + iconHeight
else -- if align:find 'bottom'
local textHeight = widget.textData:getHeight()
textY = y + h - textHeight
iconY = textY - padding - iconHeight
end
end
-- horizontal alignment for non-wrapped text
-- TODO: handle this in Backend.Text
if text and not widget.wrap then
if align:find 'right' then
textX = textX + (w - widget.textData:getWidth())
elseif align:find 'center' then
textX = textX + (w - widget.textData:getWidth()) / 2
end
end
Backend.push()
Backend.intersectScissor(x, y, w, h)
-- draw the icon
if icon then
iconX, iconY = math.floor(iconX), math.floor(iconY)
Backend.draw(icon, iconX, iconY)
end
-- draw the text
if text and textX and textY and w > 1 then
widget.innerHeight = textY - y + widget.textData:getHeight()
widget.innerWidth = textX - x + widget.textData:getWidth()
textX = math.floor(textX - (widget.scrollX or 0))
textY = math.floor(textY - (widget.scrollY or 0))
Backend.draw(widget.textData, textX, textY)
end
Backend.pop()
end
function Painter:paintChildren ()
for i, child in ipairs(self.widget) do
child:paint()
end
end
function Painter:paint ()
local widget = self.widget
local x, y, w, h = widget:getRectangle()
-- if the drawable area has no width or height, don't paint
if w < 1 or h < 1 then return end
Event.PreDisplay:emit(widget, { target = widget }, function()
Backend.push()
if widget.parent then
Backend.intersectScissor(x, y, w, h)
else
Backend.setScissor()
end
self:paintBackground()
self:paintOutline()
self:paintSlices()
self:paintIconAndText()
self:paintChildren()
Backend.pop()
end)
Event.Display:emit(widget, { target = widget })
end
return Painter