Files
love-luigi/luigi/attribute.lua
2016-01-28 12:02:56 -05:00

603 lines
13 KiB
Lua

--[[--
Widget attributes.
This module defines "attributes" (special fields) that are
recognized by all widgets. Their interpretation may vary
depending on the `type` of widget. Some widget types may also
recognize additional attributes.
Setting attributes can have side effects. For example, setting
`height` or `width` causes the parent widget and its descendants
to recalculate their size and position.
--]]--
local ROOT = (...):gsub('[^.]*$', '')
local Shortcut = require(ROOT .. 'shortcut')
local Attribute = {}
local function cascade (widget, attribute)
local value = rawget(widget, 'attributes')[attribute]
if value ~= nil then return value end
local parent = rawget(widget, 'parent')
return parent and parent[attribute]
end
--[[--
Type of widget.
Should contain a string identifying the widget's type.
After the layout is built, may be replaced by an array
of strings identifying multiple types. This is used
internally by some widgets to provide information about
the widget's state to the theme (themes describe the
appearance of widgets according to their type).
If a type is registered with the widget's layout, the registered
type initializer function will run once when the widget is constructed.
@see Widget.register
@attrib type
--]]--
Attribute.type = {}
function Attribute.type.set (widget, value)
local oldType = widget.attributes.type
widget.attributes.type = value
if value and not widget.hasType then
widget.hasType = true
local Widget = require(ROOT .. 'widget')
local decorate = Widget.typeDecorators[value]
if decorate then
decorate(widget)
end
end
end
--[[--
Widget identifier.
Should contain a unique string identifying the widget, if present.
A reference to the widget will be stored in the associated layout
in a property having the same name as the widget's id.
Setting this attribute re-registers the widget with its layout.
@attrib id
--]]--
Attribute.id = {}
function Attribute.id.set (widget, value)
local layout = widget.layout.master or widget.layout
local oldValue = widget.attributes.id
if oldValue then
layout[oldValue] = nil
end
if value then
layout[value] = widget
end
widget.attributes.id = value
end
--[[--
Widget value.
Some widget types expect the value to be of a specific type and
within a specific range. For example, `slider` and `progress`
widgets expect a normalized number, `text` widgets expect
a string, and `check` and `radio` widgets expect a boolean.
Setting this attribute bubbles the `Change` event.
@attrib value
--]]--
Attribute.value = {}
function Attribute.value.set (widget, value)
local oldValue = widget.value
widget.attributes.value = value
widget:bubbleEvent('Change', { value = value, oldValue = oldValue })
end
--[[--
Solidity.
Should true or false.
@attrib icon
--]]--
Attribute.solid = {}
function Attribute.solid.set (widget, value)
widget.attributes.solid = value
end
Attribute.solid.get = cascade
--[[--
Context menu.
- This attribute cascades.
@attrib context
--]]--
Attribute.context = {}
function Attribute.context.set (widget, value)
widget.attributes.context = value
if not value then return end
value.isContextMenu = true
widget.layout:createWidget { type = 'menu', value }
end
Attribute.context.get = cascade
--[[--
Widget style.
Should contain a string or array of strings identifying
style rules to be applied to the widget. When resolving
any attribute with a `nil` value, these style rules are
searched for a corresponding attribute.
Setting this attribute resets the `Font` and `Text` object
associated with this widget.
Setting this attribute recalculates the size and position
of the parent widget and its descendants.
@attrib style
--]]--
Attribute.style = {}
function Attribute.style.set (widget, value)
widget.attributes.style = value
widget.fontData = nil
widget.textData = nil
widget.reshape(widget.parent or widget)
end
--[[--
Status message.
Should contain a string with a short message describing the
purpose or state of the widget.
This message will appear in the last created `status` widget
in the same layout, or in the master layout if one exists.
- This attribute cascades.
@attrib status
--]]--
Attribute.status = {}
Attribute.status.get = cascade
--[[--
Scroll ability.
Should contain `true` or `false` (or `nil`).
If set to `true`, moving the scroll wheel over the widget will adjust
its scroll position when the widget's contents overflow its boundary.
@attrib scroll
--]]--
Attribute.scroll = {}
--[[--
Keyboard Attributes.
@section keyboard
--]]--
--[[--
Focusable.
Should contain `true` if the widget can be focused by pressing the tab key.
@attrib focusable
--]]--
Attribute.focusable = {}
--[[--
Keyboard shortcut.
Should contain a string representing a key and optional modifiers,
separated by dashes; for example `'ctrl-c'` or `'alt-shift-escape'`.
Pressing this key combination bubbles a `Press` event on the widget,
as if it had been pressed with a mouse or touch interface.
Setting this attribute re-registers the widget with its layout.
@attrib shortcut
--]]--
Attribute.shortcut = {}
local function setShortcut (layout, shortcut, value)
local mainKey, modifierFlags = Shortcut.parseKeyCombo(shortcut)
if mainKey then
layout.shortcuts[modifierFlags][mainKey] = value
end
end
function Attribute.shortcut.set (widget, value)
local layout = widget.layout.master or widget.layout
local oldValue = widget.attributes.shortcut
if oldValue then
if type(oldValue) == 'table' then
for _, v in ipairs(oldValue) do
setShortcut(layout, v, nil)
end
else
setShortcut(layout, oldValue, nil)
end
end
if value then
if type(value) == 'table' then
for _, v in ipairs(value) do
setShortcut(layout, v, widget)
end
else
setShortcut(layout, value, widget)
end
end
widget.attributes.shortcut = value
end
--[[--
Size Attributes.
Setting these attributes recalculates the size and position
of the parent widget and its descendants.
@section size
--]]--
--[[--
Flow axis.
Should equal either `'x'` or `'y'`. Defaults to `'y'`.
This attribute determines the placement and default dimensions
of any child widgets.
When flow is `'x'`, the `height` of child widgets defaults
to this widget's height, and each child is placed to the
right of the previous child. When flow is `'y'`, the `width`
of child widgets defaults to this widget's width, and each
child is placed below the previous child.
Setting this attribute resets the `Text` object associated
with this widget.
@attrib flow
--]]--
Attribute.flow = {}
function Attribute.flow.set (widget, value)
widget.attributes.flow = value
widget.textData = nil
widget.reshape(widget.parent or widget)
end
--[[--
Width.
This attribute may not always hold a numeric value.
To get the calculated width, use `Widget:getWidth`.
Setting this attribute when the `wrap` attribute is
also present resets the `Text` object associated
with this widget.
@attrib width
--]]--
Attribute.width = {}
function Attribute.width.set (widget, value)
if value ~= 'auto' then
value = value and math.max(value, widget.minwidth or 0)
end
widget.attributes.width = value
if widget.wrap then
widget.textData = nil
end
widget.reshape(widget.parent or widget)
end
--[[--
Height.
This attribute may not always hold a numeric value.
To get the calculated height, use `Widget:getHeight`.
@attrib height
--]]--
Attribute.height = {}
function Attribute.height.set (widget, value)
if value ~= 'auto' then
value = value and math.max(value, widget.minheight or 0)
end
widget.attributes.height = value
widget.reshape(widget.parent or widget)
end
--[[--
Minimum width.
@attrib minwidth
--]]--
Attribute.minwidth = {}
function Attribute.minwidth.set (widget, value)
local attributes = widget.attributes
attributes.minwidth = value
if type(value) == 'number' then
local current = attributes.width
if type(current) == 'number' then
attributes.width = math.max(current, value)
end
end
widget.reshape(widget.parent or widget)
end
--[[--
Minimum height.
@attrib minheight
--]]--
Attribute.minheight = {}
function Attribute.minheight.set (widget, value)
local attributes = widget.attributes
attributes.minheight = value
if type(value) == 'number' then
local current = attributes.height
if type(current) == 'number' then
attributes.height = math.max(current, value)
end
end
widget.reshape(widget.parent or widget)
end
--[[--
Font Attributes.
Setting these attributes resets the Font and Text
objects associated with the widget.
@section font
--]]--
--[[--
Font path.
Should contain a path to a TrueType font to use for displaying
this widget's `text`.
- This attribute cascades.
@attrib font
--]]--
Attribute.font = {}
local function resetFont (widget)
rawset(widget, 'fontData', nil)
rawset(widget, 'textData', nil)
for _, child in ipairs(widget) do
resetFont(child)
end
local items = widget.items
if items then
for _, child in ipairs(items) do
resetFont(child)
end
end
end
function Attribute.font.set (widget, value)
widget.attributes.font = value
resetFont(widget)
end
Attribute.font.get = cascade
--[[--
Font size.
Should contain a number representing the size of the font, in points.
Defaults to 12.
- This attribute cascades.
@attrib size
--]]--
Attribute.size = {}
function Attribute.size.set (widget, value)
widget.attributes.size = value
widget.fontData = nil
widget.textData = nil
end
Attribute.size.get = cascade
--[[--
Text Attributes.
Setting these attributes resets the Text object
associated with the widget.
@section text
--]]--
--[[--
Text to display.
@attrib text
--]]--
Attribute.text = {}
function Attribute.text.set (widget, value)
widget.attributes.text = value
widget.textData = nil
end
--[[--
Text color.
Should contain an array with 3 or 4 values (RGB or RGBA) from 0 to 255.
- This attribute cascades.
@attrib color
--]]--
Attribute.color = {}
function Attribute.color.set (widget, value)
widget.attributes.color = value
widget.textData = nil
end
Attribute.color.get = cascade
--[[--
Text and icon alignment.
Should contain a string defining vertical and horizontal alignment.
Vertical alignment is defined by either 'top', 'middle', or 'bottom',
and horizontal alignment is defined by either 'left', 'center', or 'right'.
For example, `align = 'top left'`
- This attribute cascades.
@attrib align
--]]--
Attribute.align = {}
function Attribute.align.set (widget, value)
widget.attributes.align = value
widget.textData = nil
end
Attribute.align.get = cascade
--[[--
Wrap text onto multiple lines.
Should contain `true` for multiline text, or `false` or `nil`
for a single line. Even text containing line breaks will display
as a single line when this attribute is not set to `true`.
- This attribute cascades.
@attrib wrap
--]]--
Attribute.wrap = {}
function Attribute.wrap.set (widget, value)
widget.attributes.wrap = value
widget.textData = nil
end
Attribute.wrap.get = cascade
--[[--
Visual Attributes.
@section visual
--]]--
--[[--
Background color.
Should contain an array with 3 or 4 values (RGB or RGBA) from 0 to 255.
@attrib background
--]]--
Attribute.background = {}
--[[--
Outline color.
Should contain an array with 3 or 4 values (RGB or RGBA) from 0 to 255.
@attrib outline
--]]--
Attribute.outline = {}
--[[--
Slice image.
Should contain a path to an image with "slices" to display for this widget.
@attrib slices
--]]--
Attribute.slices = {}
--[[--
Margin size.
The margin area occupies space outside of the `outline` and `slices`.
@attrib margin
--]]--
Attribute.margin = {}
function Attribute.margin.set (widget, value)
widget.attributes.margin = value
widget.textData = nil
widget:reshape()
end
--[[--
Padding size.
The padding area occupies space inside the `outline` and `slices`,
and outside the space where the `icon` and `text` and any
child widgets appear.
@attrib padding
--]]--
Attribute.padding = {}
function Attribute.padding.set (widget, value)
widget.attributes.padding = value
widget.textData = nil
widget:reshape()
end
--[[--
Icon path.
Should contain a path to an image file.
@attrib icon
--]]--
Attribute.icon = {}
function Attribute.icon.set (widget, value)
widget.attributes.icon = value
widget.textData = nil
end
return Attribute