decouple icon from text, see #19

This commit is contained in:
airstruck
2016-02-23 01:29:28 -05:00
parent 774d2856c1
commit 5f67a77114
11 changed files with 295 additions and 82 deletions

View File

@@ -1,10 +1,13 @@
return { style = 'dialog',
{ style = 'dialogHead', text = 'About LUIGI' },
{ style = 'dialogBody', padding = 24, icon = 'logo.png', text = [[
{ style = 'dialogBody', padding = 24, icon = 'logo.png', align = 'middle right',
textOffset = { -250, 0 },
text = [[
Lovely User Interfaces for Game Inventors
Copyright (c) 2015 airstruck
]] },
]]
},
{ style = 'dialogFoot',
{}, -- spacer
{ style = 'dialogButton', id = 'closeButton', text = 'Close' }

View File

@@ -45,7 +45,7 @@ return { id = 'mainWindow',
{ text = 'Use monospace font', id = 'mono' }
},
{ style = 'listThing', align = 'middle center',
text = 'Try the scroll wheel on this area.', },
text = 'Try the scroll wheel on this area.' },
{ style = 'listThing', align = 'middle center',
text = 'This text is centered, and in the middle vertically.' },
{ style = 'listThing', align = 'middle left',
@@ -73,10 +73,10 @@ return { id = 'mainWindow',
{ text = 'Use sans-serif font', id = 'sans2' },
{ text = 'Use monospace font', id = 'mono2' }
},
{ value = 1, text = 'Thing One' },
{ value = 2, text = 'Thing Two' },
{ value = 3, text = 'Thing Three' },
{ value = 4, text = 'Thing Four' },
{ value = 1, text = 'First stepper item' },
{ value = 2, text = 'Item two' },
{ value = 3, text = 'Third stepper item' },
{ value = 4, text = 'Item four' },
},
},
{

View File

@@ -14,6 +14,7 @@ to recalculate their size and position.
local ROOT = (...):gsub('[^.]*$', '')
local Shortcut = require(ROOT .. 'shortcut')
local Cleaner = require(ROOT .. 'cleaner')
local Attribute = {}
@@ -161,8 +162,10 @@ Attribute.style = {}
function Attribute.style.set (widget, value)
widget.attributes.style = value
widget.fontData = nil
widget.textData = nil
-- Cleaner.mark(widget, 'fontData')
-- Cleaner.mark(widget, 'textData')
Cleaner.mark(widget, 'fontData')
Cleaner.mark(widget, 'textData')
widget.reshape(widget.parent or widget)
end
@@ -291,7 +294,7 @@ Attribute.flow = {}
function Attribute.flow.set (widget, value)
widget.attributes.flow = value
widget.textData = nil
Cleaner.mark(widget, 'textData')
widget.reshape(widget.parent or widget)
end
@@ -310,12 +313,13 @@ with this widget.
Attribute.width = {}
function Attribute.width.set (widget, value)
if value ~= 'auto' then
-- value ~= 'auto' then
if type(value) == 'number' then
value = value and math.max(value, widget.minwidth or 0)
end
widget.attributes.width = value
if widget.wrap then
widget.textData = nil
Cleaner.mark(widget, 'textData')
end
widget.reshape(widget.parent or widget)
end
@@ -398,8 +402,10 @@ this widget's `text`.
Attribute.font = {}
local function resetFont (widget)
rawset(widget, 'fontData', nil)
rawset(widget, 'textData', nil)
-- rawset(widget, 'fontData', nil)
-- rawset(widget, 'textData', nil)
Cleaner.mark(widget, 'fontData')
Cleaner.mark(widget, 'textData')
for _, child in ipairs(widget) do
resetFont(child)
end
@@ -432,8 +438,8 @@ Attribute.size = {}
function Attribute.size.set (widget, value)
widget.attributes.size = value
widget.fontData = nil
widget.textData = nil
Cleaner.mark(widget, 'fontData')
Cleaner.mark(widget, 'textData')
end
Attribute.size.get = cascade
@@ -456,7 +462,7 @@ Attribute.text = {}
function Attribute.text.set (widget, value)
widget.attributes.text = value
widget.textData = nil
Cleaner.mark(widget, 'textData')
end
--[[--
@@ -472,7 +478,7 @@ Attribute.color = {}
function Attribute.color.set (widget, value)
widget.attributes.color = value
widget.textData = nil
Cleaner.mark(widget, 'textData')
end
Attribute.color.get = cascade
@@ -494,7 +500,7 @@ Attribute.align = {}
function Attribute.align.set (widget, value)
widget.attributes.align = value
widget.textData = nil
Cleaner.mark(widget, 'textData')
end
Attribute.align.get = cascade
@@ -514,11 +520,34 @@ Attribute.wrap = {}
function Attribute.wrap.set (widget, value)
widget.attributes.wrap = value
widget.textData = nil
Cleaner.mark(widget, 'textData')
end
Attribute.wrap.get = cascade
--[[--
Text offset.
@attrib textOffset
--]]--
Attribute.textOffset = {}
function Attribute.textOffset.set (widget, value)
widget.attributes.textOffset = value
end
--[[--
Icon offset.
@attrib iconOffset
--]]--
Attribute.iconOffset = {}
function Attribute.iconOffset.set (widget, value)
widget.attributes.iconOffset = value
end
--[[--
Visual Attributes.
@@ -563,7 +592,7 @@ Attribute.margin = {}
function Attribute.margin.set (widget, value)
widget.attributes.margin = value
widget.textData = nil
Cleaner.mark(widget, 'textData')
widget:reshape()
end
@@ -580,7 +609,7 @@ Attribute.padding = {}
function Attribute.padding.set (widget, value)
widget.attributes.padding = value
widget.textData = nil
Cleaner.mark(widget, 'textData')
widget:reshape()
end
@@ -595,7 +624,8 @@ Attribute.icon = {}
function Attribute.icon.set (widget, value)
widget.attributes.icon = value
widget.textData = nil
Cleaner.mark(widget, 'textData')
widget.iconData = nil
end

32
luigi/cleaner.lua Normal file
View File

@@ -0,0 +1,32 @@
local Cleaner = {}
local marked = {}
local watched = setmetatable({}, { __mode = 'k' })
function Cleaner.mark (t, key)
local length = #marked
local keys = watched[t]
if not keys then
keys = {}
watched[t] = keys
elseif keys[key] then
return
end
keys[key] = true
local i = length + 1
marked[i] = t
marked[i + 1] = key
end
function Cleaner.clean ()
for i = #marked - 1, 1, -2 do
local t = marked[i]
local key = marked[i + 1]
t[key] = nil
marked[i] = nil
marked[i + 1] = nil
watched[t][key] = nil
end
end
return Cleaner

View File

@@ -7,6 +7,7 @@ return function (config)
local lineColor = config.lineColor or { 220, 220, 220 }
local textColor = config.textColor or { 0, 0, 0 }
local highlight = config.highlight or { 0x19, 0xAE, 0xFF }
local spacing = config.spacing or 2
local button_pressed = resources .. 'button_pressed.png'
local button_focused = resources .. 'button_focused.png'
@@ -54,6 +55,14 @@ return function (config)
return self.value and check_checked or check_unchecked
end
local function getCheckTextOffset (self)
local align = self.align or ''
local icon = self:getIcon()
if not icon then return { 0, 0 } end
local width = icon:getWidth() + spacing * 2
return { align:find 'right' and -width or width, 0 }
end
local function getControlHeight (self)
return self.flow == 'x' and self._defaultDimension
end
@@ -120,15 +129,14 @@ return function (config)
width = getControlWidth,
color = textColor,
align = 'center middle',
margin = 2,
margin = spacing,
color = textColor,
solid = true,
_defaultDimension = 36,
},
Line = {
margin = 0,
padding = 4,
margin = spacing + 2,
align = 'left middle',
_defaultDimension = 24,
},
@@ -145,6 +153,7 @@ return function (config)
type = { 'Line', 'Control' },
focusable = true,
icon = getCheckIcon,
textOffset = getCheckTextOffset,
},
label = {
type = { 'Line', 'Control' },
@@ -159,13 +168,13 @@ return function (config)
icon = resources .. 'triangle_right.png',
},
['menu.item'] = {
padding = 4,
padding = spacing * 2,
height = 24,
align = 'left middle',
background = getMenuItemBackground,
},
panel = {
padding = 2,
padding = spacing,
background = backColor,
color = textColor,
solid = true,
@@ -183,6 +192,7 @@ return function (config)
type = { 'Line', 'Control' },
focusable = true,
icon = getRadioIcon,
textOffset = getCheckTextOffset,
},
sash = {
background = getSashBackground,

View File

@@ -4,6 +4,7 @@ local Backend = require(ROOT .. 'backend')
local Base = require(ROOT .. 'base')
local Event = require(ROOT .. 'event')
local Shortcut = require(ROOT .. 'shortcut')
local Cleaner = require(ROOT .. 'cleaner')
local Input = Base:extend()
@@ -18,6 +19,7 @@ function Input:handleDisplay (layout)
local root = layout.root
if root then root:paint() end
Event.Display:emit(layout)
Cleaner.clean()
end
function Input:handleKeyPress (layout, key, x, y)
@@ -147,7 +149,10 @@ function Input:handlePressStart (layout, button, x, y, widget, shortcut)
-- if hit then
self.pressedWidgets[button] = widget
self.passedWidgets[button] = widget
widget.pressed[button] = true
-- widget.pressed[button] = true
for ancestor in widget:eachAncestor(true) do
ancestor.pressed[button] = true
end
if button == 'left' then
widget:focus()
end
@@ -167,7 +172,10 @@ function Input:handlePressEnd (layout, button, x, y, widget, shortcut)
local hit, widget = checkHit(widget or layout:getWidgetAt(x, y), layout)
local wasPressed = originWidget.pressed[button]
if hit then
originWidget.pressed[button] = nil
-- originWidget.pressed[button] = nil
for ancestor in originWidget:eachAncestor(true) do
ancestor.pressed[button] = nil
end
end
widget:bubbleEvent('PressEnd', {
hit = hit,

View File

@@ -22,6 +22,7 @@ local Event = require(ROOT .. 'event')
local Widget = require(ROOT .. 'widget')
local Input = require(ROOT .. 'input')
local Style = require(ROOT .. 'style')
local Cleaner = require(ROOT .. 'cleaner')
local Layout = Base:extend()
@@ -79,7 +80,7 @@ function Layout:createWidget (data)
end
local function clearWidget (widget)
widget.textData = nil
Cleaner.mark(widget, 'textData')
widget.fontData = nil
widget.position = {}
widget.dimensions = {}

View File

@@ -8,7 +8,7 @@ local Text = Backend.Text
local Painter = Base:extend()
local imageCache = {}
local imageCache = setmetatable({}, { __mode = 'v' })
-- local sliceCache = {}
function Painter:constructor (widget)
@@ -199,8 +199,70 @@ function Painter:paintIconAndText ()
Backend.pop()
end
function Painter:paintText ()
local widget = self.widget
if not widget.text then return end
local align = widget.align
local text = widget:getText()
local x, y, w, h = widget:getRectangle(true, true)
local offset = widget.textOffset
local ox, oy = 0, 0
if offset then
ox, oy = offset[1] or 0, offset[2] or 0
end
-- horizontal alignment for non-wrapped text
if align:find 'right' then
x = x + (w - text:getWidth())
elseif align:find 'center' then
x = x + (w - text:getWidth()) * 0.5
end
if align:find 'middle' then
local textHeight = text:getHeight()
y = y + (h - textHeight) * 0.5
elseif align:find 'bottom' then
local textHeight = text:getHeight()
y = y + h - textHeight
end
local sx, sy = widget.scrollX or 0, widget.scrollY or 0
Backend.draw(text, x + ox - sx, y + oy - sy)
-- widget.innerHeight = text:getHeight()
-- widget.innerWidth = text:getWidth()
end
function Painter:paintIcon ()
local widget = self.widget
if not widget.icon then return end
local align = widget.align
local icon = widget:getIcon()
local x, y, w, h = widget:getRectangle(true, true)
if align:find 'right' then
x = x + (w - icon:getWidth())
elseif align:find 'center' then
x = x + (w - icon:getWidth()) * 0.5
end
if align:find 'middle' then
local iconHeight = icon:getHeight()
y = y + (h - iconHeight) * 0.5
elseif align:find 'bottom' then
local iconHeight = icon:getHeight()
y = y + h - iconHeight
end
Backend.draw(icon, x, y)
end
function Painter:paintChildren ()
for i, child in ipairs(self.widget) do
local widget = self.widget
for i, child in ipairs(widget) do
child:paint()
end
end
@@ -225,7 +287,9 @@ function Painter:paint ()
self:paintBackground()
self:paintOutline()
self:paintSlices()
self:paintIconAndText()
-- self:paintIconAndText()
self:paintIcon()
self:paintText()
self:paintChildren()
Backend.pop()

View File

@@ -11,7 +11,9 @@ local Backend = require(ROOT .. 'backend')
local Event = require(ROOT .. 'event')
local Attribute = require(ROOT .. 'attribute')
local Painter = require(ROOT .. 'painter')
local Cleaner = require(ROOT .. 'cleaner')
local Font = Backend.Font
local Text = Backend.Text
local Widget = {}
@@ -230,7 +232,7 @@ local function metaCall (Widget, layout, self)
for k, v in ipairs(self) do
self[k] = v.isWidget and v or metaCall(Widget, self.layout, v)
self[k].parent = self
v.parent = self
end
return self
@@ -285,6 +287,10 @@ function Widget:bubbleEvent (eventName, data)
data = data or {}
data.target = self
for ancestor in self:eachAncestor(true) do
if ancestor ~= self and ancestor.unified then
data.innerTarget = self
return ancestor:bubbleEvent(eventName, data)
end
local result = event:emit(ancestor, data)
if result ~= nil then return result end
end
@@ -400,6 +406,16 @@ function Widget:getPreviousNeighbor ()
return layout.root
end
function Widget:registerChild (data)
local layout = self.layout
local child = data and data.isWidget and data or Widget(layout, data or {})
child.parent = self
child.layout = self.layout
return child
end
--[[--
Add a child to this widget.
@@ -410,12 +426,8 @@ A widget or definition table representing a widget.
The newly added child widget.
--]]--
function Widget:addChild (data)
local layout = self.layout
local child = data and data.isWidget and data or Widget(layout, data or {})
local child = self:registerChild(data)
self[#self + 1] = child
child.parent = self
child.layout = self.layout
return child
end
@@ -491,7 +503,6 @@ function Widget:calculateDimension (name)
end
end
local size = (parentDimension - claimed) / unsized
size = math.max(size, min)
self.dimensions[name] = size
return size
@@ -568,6 +579,14 @@ function Widget:calculateDimensionMinimum (name)
end
end
if not self.wrap then
if name == 'width' then
value = math.max(value, self:getText():getWidth())
else
value = math.max(value, self:getText():getHeight())
end
end
if value > 0 then
local space = (self.margin or 0) * 2 + (self.padding or 0) * 2
value = value + space
@@ -636,7 +655,8 @@ function Widget:getContentWidth ()
width = math.max(width, child:getWidth())
end
end
return width
-- return width
return math.max(width, self:getText():getWidth())
end
--[[--
@@ -659,16 +679,65 @@ function Widget:getContentHeight ()
height = math.max(height, child:getHeight())
end
end
return height
-- return height
return math.max(height, self:getText():getHeight())
end
local imageCache = setmetatable({}, { __mode = 'v' })
local function loadImage (path)
local cached = imageCache[path]
if not cached then
cached = Backend.Image(path)
imageCache[path] = cached
end
return cached
end
function Widget:getIcon ()
if self.lastIcon ~= self.icon or not self.iconData then
self.lastIcon = self.icon
self.iconData = loadImage(self.icon)
end
return self.iconData
end
function Widget:getFont ()
if not self.fontData then
self.fontData = Font(self.font, self.size)
end
if self.fontData then return self.fontData end
self.fontData = Font(self.font, self.size)
return self.fontData
end
function Widget:getText ()
if self.textData then return self.textData end
local font = self:getFont()
if self.wrap then
-- horizontal alignment
local align = self.align
if align:find 'right' then
align = 'right'
elseif align:find 'center' then
align = 'center'
elseif align:find 'justify' then
align = 'justify'
else
align = 'left'
end
-- wrap limit
local x, y, w, h = self:getRectangle(true, true)
local offset = self.textOffset and self.textOffset[1] or 0
local limit = w - math.abs(offset)
self.textData = Text(font, self.text or '', self.color, align, limit)
else
self.textData = Text(font, self.text or '', self.color)
end
return self.textData
end
--[[--
Get x/y/width/height values describing a rectangle within the widget.
@@ -772,7 +841,7 @@ function Widget:reshape ()
self.position = {}
self.dimensions = {}
self.textData = nil
Cleaner.mark(self, 'textData')
Event.Reshape:emit(self, { target = self })
for _, child in ipairs(self) do

View File

@@ -13,6 +13,7 @@ standard themes, the widget's value should be indicated in some other way.
--]]--
return function (self)
self:onPress(function (event)
if event.button ~= 'left' then return end
self.value = not self.value

View File

@@ -37,22 +37,10 @@ local function addLayoutChildren (self)
for index, child in ipairs(self.items) do
child.type = child.type or 'menu.item'
root:addChild(child)
local childHeight = child:getHeight()
height = height + childHeight
if child.type == 'menu.item' then
local font = child:getFont()
local pad = child.padding or 0
local tw = font:getAdvance(child[2].text)
+ pad * 2 + childHeight
local kw = font:getAdvance(child[3].text)
+ pad * 2 + childHeight
textWidth = math.max(textWidth, tw)
keyWidth = math.max(keyWidth, kw)
end
end
root.height = height
root.width = textWidth + keyWidth + (root.padding or 0)
root.height = 'auto'
root.width = 'auto'
local isSubmenu = self.parentMenu and self.parentMenu.parentMenu
local w = isSubmenu and self:getWidth() or 0
@@ -141,22 +129,20 @@ local function registerLayoutEvents (self)
menuLayout:onPress(function (event)
-- if event.button ~= 'left' then return end
if not checkMouseButton(self, event) then return end
for widget in event.target:eachAncestor(true) do
if widget.type == 'menu.item' and #widget.items == 0 then
menuLayout:hide()
deactivateSiblings(self.rootMenu[1])
end
local widget = event.target
if widget.type == 'menu.item' and #widget.items == 0 then
menuLayout:hide()
deactivateSiblings(self.rootMenu[1])
end
end)
menuLayout:onPressEnd(function (event)
-- if event.button ~= 'left' then return end
if not checkMouseButton(self, event) then return end
for widget in event.target:eachAncestor(true) do
if widget.type == 'menu.item' and #widget.items == 0
and event.target ~= event.origin then
widget:bubbleEvent('Press', event)
end
local widget = event.target
if widget.type == 'menu.item' and #widget.items == 0
and event.target ~= event.origin then
widget:bubbleEvent('Press', event)
end
end)
@@ -164,12 +150,12 @@ local function registerLayoutEvents (self)
menuLayout:onPressEnter(activate)
end
local function nothing () end
local function initialize (self)
local font = self:getFont()
local pad = self.padding or 0
local isSubmenu = self.parentMenu and self.parentMenu.parentMenu
local text, shortcut, icon = self.text or '', self.shortcut or '', self.icon
local textWidth = font:getAdvance(text) + pad * 2
-- local textWidth = font:getAdvance(text) + pad * 2
if isSubmenu then
local edgeType
@@ -177,27 +163,36 @@ local function initialize (self)
shortcut = ' '
edgeType = 'menu.expander'
else
shortcut = Shortcut.stringify(shortcut)
shortcut = ' ' .. Shortcut.stringify(shortcut)
end
self.unified = true
self.height = 'auto' -- font:getLineHeight() + pad * 2
self.flow = 'x'
self:addChild { icon = icon, width = self.height }
self:addChild { text = text, width = textWidth }
self:addChild {
width = function () return self:getHeight() end,
icon = function () return self.icon end,
align = 'middle left',
}
self:addChild {
width = 'auto',
text = function () return self.text end
}
self:addChild {
type = edgeType,
text = shortcut,
align = 'middle right',
minwidth = self.height,
minwidth = function () return self:getHeight() end,
color = function ()
local c = self.color or { 0, 0, 0 }
return { c[1], c[2], c[3], (c[4] or 256) / 2 }
end
}
self.icon = nil
self.text = nil
self.painter.paintText = nothing
self.painter.paintIcon = nothing
else
-- top level menu
self.width = textWidth + pad * 2
-- self.width = textWidth
self.width = 'auto'
self.align = 'middle center'
end
end