Created
January 10, 2015 03:10
-
-
Save ggcrunchy/f53a38d889ca423294b6 to your computer and use it in GitHub Desktop.
A couple of mostly self-contained layout utility modules for Corona SDK
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- Utilities for layout handling. | |
-- | |
-- These are designed to allow layout decisions and queries, without worrying about the | |
-- anchor points of the objects in question. In many cases, positions may be substituted | |
-- for objects, as well. | |
-- | |
-- With respect to this module, a **Number** may be any of the following: | |
-- | |
-- * A number, or a string that @{tonumber} is able to convert. These values are used as is. | |
-- * A string of the form **"AMOUNT%"**, e.g. `"20%"` or `"-4.2%"`, which resolves to the | |
-- indicated percent of the content width or height. | |
-- * **nil**, which resolves to 0. | |
-- TODO: Where two objects are concerned, layout should be able to handle mixed parents | |
-- TODO: Positions should respect parent's positions, and not assume screen coordiantes | |
-- | |
-- Permission is hereby granted, free of charge, to any person obtaining | |
-- a copy of this software and associated documentation files (the | |
-- "Software"), to deal in the Software without restriction, including | |
-- without limitation the rights to use, copy, modify, merge, publish, | |
-- distribute, sublicense, and/or sell copies of the Software, and to | |
-- permit persons to whom the Software is furnished to do so, subject to | |
-- the following conditions: | |
-- | |
-- The above copyright notice and this permission notice shall be | |
-- included in all copies or substantial portions of the Software. | |
-- | |
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
-- | |
-- [ MIT license: http://www.opensource.org/licenses/mit-license.php ] | |
-- | |
-- Standard library imports -- | |
local floor = math.floor | |
local ipairs = ipairs | |
local sub = string.sub | |
local tonumber = tonumber | |
local type = type | |
-- Corona globals -- | |
local display = display | |
-- Cached module references -- | |
local _Above_ | |
local _Below_ | |
local _CenterAlignWith_ | |
local _CenterAt_ | |
local _CenterOf_ | |
local _LeftOf_ | |
local _PutAbove_ | |
local _PutBelow_ | |
local _PutLeftOf_ | |
local _PutRightOf_ | |
local _RightOf_ | |
-- Exports -- | |
local M = {} | |
-- Resolves a Number (most often being a delta) to a value | |
local function Delta (n, dim) | |
if type(n) ~= "string" then | |
return n or 0 | |
elseif sub(n, -1) == "%" then | |
return tonumber(sub(n, 1, -2)) * display[dim] / 100 | |
else | |
return tonumber(n) | |
end | |
end | |
-- Helper for horizontal deltas... | |
local function DX (n) | |
return Delta(n, "contentWidth") | |
end | |
-- ...and vertical ones | |
local function DY (n) | |
return Delta(n, "contentHeight") | |
end | |
-- Is the object not a group? | |
local function NonGroup (object) | |
return object._type ~= "GroupObject" | |
end | |
-- Is the object a number, or can it be coerced / defaulted to one? | |
local function Number (object) | |
local otype = type(object) | |
return object == nil or otype == "string" or otype == "number" | |
end | |
-- Helper to get an x-coordinate relative to a position, in terms of width... | |
local function RelativeX (object, t) | |
return object.x + t * object.contentWidth | |
end | |
-- ...and y-coordinate, in terms of height | |
local function RelativeY (object, t) | |
return object.y + t * object.contentHeight | |
end | |
-- Finds the y-coordinate at the bottom of an object; Numbers resolve like deltas, directly to themselves | |
local function BottomY (object) | |
if Number(object) then | |
return DY(object) | |
elseif NonGroup(object) then | |
return RelativeY(object, 1 - object.anchorY) | |
else | |
return object.contentBounds.yMax | |
end | |
end | |
-- Finds the x-coordinate at the left side of an object; Numbers behave as per BottomY | |
local function LeftX (object) | |
if Number(object) then | |
return DX(object) | |
elseif NonGroup(object) then | |
return RelativeX(object, -object.anchorX) | |
else | |
return object.contentBounds.xMin | |
end | |
end | |
-- Finds the x-coordinate at the right side of an object; Numbers behave as per BottomY | |
local function RightX (object) | |
if Number(object) then | |
return DX(object) | |
elseif NonGroup(object) then | |
return RelativeX(object, 1 - object.anchorX) | |
else | |
return object.contentBounds.xMax | |
end | |
end | |
-- Finds the y-coordinate at the top of an object; Numbers behave as per BottomY | |
local function TopY (object) | |
if Number(object) then | |
return DY(object) | |
elseif NonGroup(object) then | |
return RelativeY(object, -object.anchorY) | |
else | |
return object.contentBounds.yMin | |
end | |
end | |
--- Finds the y-coordinate above an object or position. | |
-- @tparam ?|DisplayObject|Number ref Reference object or y-coordinate. | |
-- @tparam[opt] Number dy Displacement from the "above" position. | |
-- @treturn number Final result, i.e. y-coordinate plus any displacement. | |
function M.Above (ref, dy) | |
return floor(TopY(ref) + DY(dy)) | |
end | |
--- Finds the y-coordinate below an object or position. | |
-- @tparam ?|DisplayObject|Number ref Reference object or y-coordinate. | |
-- @tparam[opt] Number dy Displacement from the "below" position. | |
-- @treturn number Final result, i.e. y-coordinate plus any displacement. | |
function M.Below (ref, dy) | |
return floor(BottomY(ref) + DY(dy)) | |
end | |
--- Assigns an object's y-coordinate so that its bottom aligns with either the bottom of a | |
-- reference object or a y-coordinate. | |
-- @pobject object Object to align. | |
-- @tparam ?|DisplayObject|Number ref Reference object or y-coordinate. | |
-- @tparam[opt] Number dy Displacement from the aligned position. | |
function M.BottomAlignWith (object, ref, dy) | |
_PutAbove_(object, _Below_(ref), dy) | |
end | |
-- Finds the x-coordinate at the center of an object... | |
local function CenterX (object) | |
if NonGroup(object) then | |
return RelativeX(object, .5 - object.anchorX) | |
else | |
local bounds = object.contentBounds | |
return .5 * (bounds.xMin + bounds.xMax) | |
end | |
end | |
-- ...and the y-coordinate | |
local function CenterY (object) | |
if NonGroup(object) then | |
return RelativeY(object, .5 - object.anchorY) | |
else | |
local bounds = object.contentBounds | |
return .5 * (bounds.yMin + bounds.yMax) | |
end | |
end | |
-- Aligns an object's center x-coordinate to an x-coordinate (plus optional delta)... | |
local function PutCenterAtX (object, x, dx) | |
object.x = floor(object.x + DX(x) - CenterX(object) + DX(dx)) | |
end | |
-- ...and aligns the y-coordinate, likewise | |
local function PutCenterAtY (object, y, dy) | |
object.y = floor(object.y + DY(y) - CenterY(object) + DY(dy)) | |
end | |
-- Centers an object horizontally at the content center x-coordinate... | |
local function PutAtContentCenterX (object, dx) | |
PutCenterAtX(object, display.contentCenterX, dx) | |
end | |
-- ...and vertically at the y-coordinate | |
local function PutAtContentCenterY (object, dy) | |
PutCenterAtY(object, display.contentCenterY, dy) | |
end | |
--- Assigns an object's x- and y-coordinates so that its center aligns with the center of a | |
-- reference object. | |
-- @pobject object Object to align. | |
-- @pobject ref_object Reference object. | |
-- @tparam[opt] Number dx Displacement from the aligned position's x-coordinate... | |
-- @tparam[opt] Number dy ...and from its y-coordinate. | |
function M.CenterAlignWith (object, ref_object, dx, dy) | |
_CenterAt_(object, _CenterOf_(ref_object, dx, dy)) | |
end | |
--- Assigns an object's x- and y-coordinates so that its center is at a position. | |
-- @pobject object Object to center. | |
-- @tparam[opt] Number x Position x-coordinate... | |
-- @tparam[opt] Number y ...and y-coordinate. | |
-- @tparam[opt] Number dx Displacement from the center's x-coordinate... | |
-- @tparam[opt] Number dy ...and from its y-coordinate. | |
function M.CenterAt (object, x, y, dx, dy) | |
PutCenterAtX(object, x, dx) | |
PutCenterAtY(object, y, dy) | |
end | |
--- Variant of @{CenterAt} that only assigns the x-coordinate. | |
-- @pobject object Object to center. | |
-- @tparam[opt] Number x Position x-coordinate. | |
-- @tparam[opt] Number dx Displacement from the x-coordinate. | |
function M.CenterAtX (object, x, dx) | |
PutCenterAtY(object, x, dx) | |
end | |
--- Variant of @{CenterAt} that only assigns the y-coordinate. | |
-- @pobject object Object to center. | |
-- @tparam[opt] Number y Position y-coordinate. | |
-- @tparam[opt] Number dy Displacement from the y-coordinate. | |
function M.CenterAtY (object, y, dy) | |
PutCenterAtY(object, y, dy) | |
end | |
--- Finds the x- and y-coordinates of an object's center. | |
-- @pobject object Object to query. | |
-- @tparam[opt] Number dx Displacement from the "center" position, x-coordinate... | |
-- @tparam[opt] Number dy ...and the y-coordinate. | |
-- @treturn number Final result, i.e. x-coordinate plus any displacement... | |
-- @treturn number ...and likewise, for the y-coordinate. | |
function M.CenterOf (object, dx, dy) | |
return CenterX(object, dx), CenterY(object, dy) | |
end | |
--- Variant of @{CenterOf} that only supplies the x-coordinate. | |
-- @pobject object Object to query. | |
-- @tparam[opt] Number dx Displacement from the "center" position. | |
-- @treturn number Final result, i.e. x-coordinate plus any displacement. | |
function M.CenterX (object, dx) | |
return CenterX(object, dx) | |
end | |
--- Variant of @{CenterOf} that only supplies the y-coordinate. | |
-- @pobject object Object to query. | |
-- @tparam[opt] Number dy Displacement from the "center" position. | |
-- @treturn number Final result, i.e. y-coordinate plus any displacement. | |
function M.CenterY (object, dy) | |
return CenterY(object, dy) | |
end | |
--- Assigns an object's x-coordinate so that its left side aligns with either the left side | |
-- of a reference object or an x-coordinate. | |
-- @pobject object Object to align. | |
-- @tparam ?|DisplayObject|Number ref Reference object or x-coordinate. | |
-- @tparam[opt] Number dx Displacement from the aligned position. | |
function M.LeftAlignWith (object, ref, dx) | |
_PutRightOf_(object, _LeftOf_(ref), dx) | |
end | |
--- Finds the x-coordinate to the left of an object or position. | |
-- @tparam ?|DisplayObject|Number ref Reference object or x-coordinate. | |
-- @tparam[opt] Number dx Displacement from the "left of" position. | |
-- @treturn number Final result, i.e. x-coordinate plus any displacement. | |
function M.LeftOf (ref, dx) | |
return floor(LeftX(ref) + DX(dx)) | |
end | |
--- Moves an object along the x-axis relative to its current position. | |
-- @pobject object Object to move. | |
-- @tparam[opt] Number dx Displacement. | |
function M.MoveX (object, dx) | |
object.x = floor(object.x + DX(dx)) | |
end | |
--- Moves an object along the y-axis relative to its current position. | |
-- @pobject object Object to move. | |
-- @tparam[opt] Number dy Displacement. | |
function M.MoveY (object, dy) | |
object.y = floor(object.y + DY(dy)) | |
end | |
--- Assigns an object's y-coordinate such that its bottom is aligned with the top of a | |
-- reference object or a y-coordinate. | |
-- @pobject object Object to position. | |
-- @tparam ?|DisplayObject|Number ref Reference object or y-coordinate. | |
-- @tparam[opt] Number dy Displacement from the "above" position. | |
function M.PutAbove (object, ref, dy) | |
local y = TopY(ref) | |
if NonGroup(object) then | |
y = y - (1 - object.anchorY) * object.contentHeight | |
else | |
y = y - (object.contentBounds.yMax - object.y) | |
end | |
object.y = floor(y + DY(dy)) | |
end | |
--- Centers an object horizontally and bottom-aligns it to the bottom of the content. | |
-- @pobject object Object to position. | |
-- @tparam[opt] Number dx Displacement from the center. | |
-- @tparam[opt] Number dy Displacement from the bottom. | |
function M.PutAtBottomCenter (object, dx, dy) | |
PutAtContentCenterX(object, dx) | |
_PutAbove_(object, display.contentHeight, dy) | |
end | |
--- Left-aligns an object to the left side of the content and bottom-aligns it to the | |
-- bottom of the content. | |
-- @pobject object Object to position. | |
-- @tparam[opt] Number dx Displacement from the left side. | |
-- @tparam[opt] Number dy Displacement from the bottom. | |
function M.PutAtBottomLeft (object, dx, dy) | |
_PutRightOf_(object, 0, dx) | |
_PutAbove_(object, display.contentHeight, dy) | |
end | |
--- Right-aligns an object to the right side of the content and bottom-aligns it to the | |
-- bottom of the content. | |
-- @pobject object Object to position. | |
-- @tparam[opt] Number dx Displacement from the right side. | |
-- @tparam[opt] Number dy Displacement from the bottom. | |
function M.PutAtBottomRight (object, dx, dy) | |
_PutLeftOf_(object, display.contentWidth, dx) | |
_PutAbove_(object, display.contentHeight, dy) | |
end | |
--- Assigns an object's x- and y-coordinates so that its center is at the content center. | |
-- @pobject object Object to center. | |
-- @tparam[opt] Number dx Displacement from the center position's x-coordinate... | |
-- @tparam[opt] Number dy ...and from its y-coordinate. | |
function M.PutAtCenter (object, dx, dy) | |
PutAtContentCenterX(object, dx) | |
PutAtContentCenterY(object, dy) | |
end | |
--- Left-aligns an object to the left side of the content and centers it vertically. | |
-- @pobject object Object to position. | |
-- @tparam[opt] Number dx Displacement from the left side. | |
-- @tparam[opt] Number dy Displacement from the center. | |
function M.PutAtCenterLeft (object, dx, dy) | |
_PutRightOf_(object, 0, dx) | |
PutAtContentCenterY(object, dy) | |
end | |
--- Right-aligns an object to the right side of the content and centers it vertically. | |
-- @pobject object Object to position. | |
-- @tparam[opt] Number dx Displacement from the right side. | |
-- @tparam[opt] Number dy Displacement from the center. | |
function M.PutAtCenterRight (object, dx, dy) | |
_PutLeftOf_(object, display.contentWidth, dx) | |
PutAtContentCenterY(object, dy) | |
end | |
--- Variant of @{PutAtCenter} that only assigns the x-coordinate. | |
-- @pobject object Object to center. | |
-- @tparam[opt] Number dx Displacement from the x-coordinate. | |
function M.PutAtCenterX (object, dx) | |
PutAtContentCenterX(object, dx) | |
end | |
--- Variant of @{PutAtCenter} that only assigns the y-coordinate. | |
-- @pobject object Object to center. | |
-- @tparam[opt] Number dy Displacement from the y-coordinate. | |
function M.PutAtCenterY (object, dy) | |
PutAtContentCenterY(object, dy) | |
end | |
--- DOCME | |
-- TODO: This doesn't seem to be adequate, needs to be split on (x, y) | |
function M.PutAtFirstHit (object, ref_object, choices, center_on_fail) | |
local x, y, dx, dy = object.x, object.y, DX(choices.dx), DY(choices.dy) | |
-- | |
for _, choice in ipairs(choices) do | |
_CenterAlignWith_(object, ref_object) | |
-- | |
if choice == "above" or choice == "below" then | |
if choice == "above" then | |
_PutAbove_(object, ref_object, -dy) | |
if _Above_(object) >= 0 then | |
return | |
end | |
else | |
_PutBelow_(object, ref_object, dy) | |
if _Below_(object) < display.contentHeight then | |
return | |
end | |
end | |
-- | |
elseif choice == "left_of" or choice == "right_of" then | |
if choice == "left_of" then | |
_PutLeftOf_(object, ref_object, -dx) | |
if _LeftOf_(object) >= 0 then | |
return | |
end | |
else | |
_PutRightOf_(object, ref_object, dx) | |
if _RightOf_(object) < display.contentWidth then | |
return | |
end | |
end | |
end | |
end | |
-- | |
if center_on_fail then | |
_CenterAlignWith_(object, ref_object) | |
else | |
object.x, object.y = x, y | |
end | |
end | |
--- Centers an object horizontally and top-aligns it to the top of the content. | |
-- @pobject object Object to position. | |
-- @tparam[opt] Number dx Displacement from the center. | |
-- @tparam[opt] Number dy Displacement from the top. | |
function M.PutAtTopCenter (object, dx, dy) | |
PutAtContentCenterX(object, dx) | |
_PutAbove_(object, 0, dy) | |
end | |
--- Left-aligns an object to the left side of the content and top-aligns it to the top of | |
-- the content. | |
-- @pobject object Object to position. | |
-- @tparam[opt] Number dx Displacement from the left side. | |
-- @tparam[opt] Number dy Displacement from the top. | |
function M.PutAtTopLeft (object, dx, dy) | |
_PutRightOf_(object, 0, dx) | |
_PutBelow_(object, 0, dy) | |
end | |
--- Right-aligns an object to the right side of the content and top-aligns it to the top | |
-- of the content. | |
-- @pobject object Object to position. | |
-- @tparam[opt] Number dx Displacement from the right side. | |
-- @tparam[opt] Number dy Displacement from the top. | |
function M.PutAtTopRight (object, dx, dy) | |
_PutLeftOf_(object, display.contentWidth, dx) | |
_PutBelow_(object, 0, dy) | |
end | |
--- Assigns an object's y-coordinate such that its top is aligned with the bottom of a | |
-- reference object or a y-coordinate. | |
-- @pobject object Object to position. | |
-- @tparam ?|DisplayObject|Number ref Reference object or y-coordinate. | |
-- @tparam[opt] Number dy Displacement from the "below" position. | |
function M.PutBelow (object, ref, dy) | |
local y = BottomY(ref) | |
if NonGroup(object) then | |
y = y + object.anchorY * object.contentHeight | |
else | |
y = y + (object.y - object.contentBounds.yMin) | |
end | |
object.y = floor(y + DY(dy)) | |
end | |
--- Assigns an object's x-coordinate so that its right side is to the left of a reference | |
-- object or an x-coordinate. | |
-- @pobject object Object to position. | |
-- @tparam ?|DisplayObject|Number ref Reference object or x-coordinate. | |
-- @tparam[opt] Number dx Displacement from the "left of" position. | |
function M.PutLeftOf (object, ref, dx) | |
local x = LeftX(ref) | |
if NonGroup(object) then | |
x = x - (1 - object.anchorX) * object.contentWidth | |
else | |
x = x - (object.contentBounds.xMax - object.x) | |
end | |
object.x = floor(x + DX(dx)) | |
end | |
--- Assigns an object's x-coordinate so that its left side is to the right of a reference | |
-- object or an x-coordinate. | |
-- @pobject object Object to position. | |
-- @tparam ?|DisplayObject|Number ref Reference object or x-coordinate. | |
-- @tparam[opt] Number dx Displacement from the "right of" position. | |
function M.PutRightOf (object, ref, dx) | |
local x = RightX(ref) | |
if NonGroup(object) then | |
x = x + object.anchorX * object.contentWidth | |
else | |
x = x + (object.x - object.contentBounds.xMin) | |
end | |
object.x = floor(x + DX(dx)) | |
end | |
--- Assigns an object's x-coordinate so that its right side aligns with either the right side | |
-- of a reference object or an x-coordinate. | |
-- @pobject object Object to align. | |
-- @tparam ?|DisplayObject|Number ref Reference object or x-coordinate. | |
-- @tparam[opt] Number dx Displacement from the aligned position. | |
function M.RightAlignWith (object, ref, dx) | |
_PutLeftOf_(object, _RightOf_(ref), dx) | |
end | |
--- Finds the x-coordinate to the right of an object or position. | |
-- @tparam ?|DisplayObject|Number ref Reference object or x-coordinate. | |
-- @tparam[opt] Number dx Displacement from the "right of" position. | |
-- @treturn number Final result, i.e. x-coordinate plus any displacement. | |
function M.RightOf (ref, dx) | |
return floor(RightX(ref) + DX(dx)) | |
end | |
--- Assigns an object's y-coordinate so that its top aligns with either the top of a | |
-- reference object or a y-coordinate. | |
-- @pobject object Object to align. | |
-- @tparam ?|DisplayObject|Number ref Reference object or y-coordinate. | |
-- @tparam[opt] Number dy Displacement from the aligned position. | |
function M.TopAlignWith (object, ref, dy) | |
_PutBelow_(object, _Above_(ref), dy) | |
end | |
-- Cache module members. | |
_Above_ = M.Above | |
_Below_ = M.Below | |
_CenterAlignWith_ = M.CenterAlignWith | |
_CenterAt_ = M.CenterAt | |
_CenterOf_ = M.CenterOf | |
_LeftOf_ = M.LeftOf | |
_PutAbove_ = M.PutAbove | |
_PutBelow_ = M.PutBelow | |
_PutLeftOf_ = M.PutLeftOf | |
_PutRightOf_ = M.PutRightOf | |
_RightOf_ = M.RightOf | |
-- Export the module. | |
return M |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- Implements a small domain-specific language on top of the layout system, in order to | |
-- make positions and dimensions more expressive. | |
-- | |
-- With respect to this module, a **DSL_Number** may be either of the following: | |
-- | |
-- * A number, or a string that @{tonumber} is able to convert. These values are used as is. | |
-- * A string of the form **"AMOUNT%"**, e.g. `"20%"` or `"-4.2%"`, which resolves to the | |
-- indicated percent of the content width or height. | |
-- | |
-- Permission is hereby granted, free of charge, to any person obtaining | |
-- a copy of this software and associated documentation files (the | |
-- "Software"), to deal in the Software without restriction, including | |
-- without limitation the rights to use, copy, modify, merge, publish, | |
-- distribute, sublicense, and/or sell copies of the Software, and to | |
-- permit persons to whom the Software is furnished to do so, subject to | |
-- the following conditions: | |
-- | |
-- The above copyright notice and this permission notice shall be | |
-- included in all copies or substantial portions of the Software. | |
-- | |
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
-- | |
-- [ MIT license: http://www.opensource.org/licenses/mit-license.php ] | |
-- | |
-- Standard library imports -- | |
local assert = assert | |
local ceil = math.ceil | |
local getmetatable = getmetatable | |
local gsub = string.gsub | |
local sub = string.sub | |
local setmetatable = setmetatable | |
local tonumber = tonumber | |
local type = type | |
-- Modules -- | |
local layout = require("layout") | |
-- Corona globals -- | |
local display = display | |
-- Cached module references -- | |
local _AddProperties_Metatable_ | |
local _EvalDims_ | |
local _EvalPos_ | |
-- Exports -- | |
local M = {} | |
--- Augments a display object's metatable, so that its objects can also query **left**, | |
-- **center_x**, **right**, **bottom**, **center_y**, and **top** properties, with semantics | |
-- as in @{corona_ui.utils.layout}. Additionally, any of these (and also **x** and **y**) | |
-- may be assigned, accepting the same inputs as @{PutObjectAt}. | |
-- @pobject object Object which will have its metatable modified. | |
function M.AddProperties (object) | |
local mt = getmetatable(object) | |
_AddProperties_Metatable_(mt) | |
setmetatable(object, mt) | |
end | |
-- Lookup for __index properties -- | |
local Index = { | |
left = layout.LeftOf, center_x = layout.CenterX, right = layout.RightOf, | |
bottom = layout.Below, center_y = layout.CenterY, top = layout.Above | |
} | |
-- Lookup for __newindex properties -- | |
local NewIndex = { | |
left = layout.LeftAlignWith, center_x = layout.CenterAtX, right = layout.RightAlignWith, | |
bottom = layout.BottomAlignWith, center_y = layout.CenterAtY, top = layout.TopAlignWith | |
} | |
-- Forward declarations -- | |
local EvalNewIndex | |
--- Variant of @{AddProperties} that populates the metatable, e.g. for multiple uses. | |
-- @ptable mt Metatable (assumed to originate from a display object). | |
function M.AddProperties_Metatable (mt) | |
local index = assert(mt.__index, "Missing __index metamethod") | |
local newindex = assert(mt.__newindex, "Missing __newindex method") | |
-- Augment __index... | |
function mt.__index (object, k) | |
local prop = Index[k] | |
if prop then | |
return prop(object) | |
else | |
return index(object, k) | |
end | |
end | |
-- ...and __newindex. | |
function mt.__newindex (object, k, v) | |
local prop = NewIndex[k] | |
if type(v) == "string" then | |
v = EvalNewIndex(object, k, v) | |
if v == nil then | |
return | |
end | |
end | |
if prop then | |
prop(object, v) | |
else | |
newindex(object, k, v) | |
end | |
end | |
end | |
-- Helper to extract a number from a DSL_Number | |
local function ParseNumber (arg, dim, can_fail) | |
local num = tonumber(arg) | |
if num then | |
return num | |
else | |
local ok = sub(arg, -1) == "%" | |
assert(ok or can_fail, "Invalid argument") | |
num = ok and tonumber(sub(arg, 1, -2)) | |
return num and num * display[dim] / 100 | |
end | |
end | |
--- Normalizes width and height values. | |
-- @tparam[opt] DSL_Number w If present, the width (e.g. 20, "10%") to normalize... | |
-- @tparam[opt] DSL_Number h ...and likewise, the height. | |
-- @treturn ?|number|nil If _w_ is absent, **nil**. Otherwise, the evaluated width. | |
-- @treturn ?|number|nil As per _w_, for the height. | |
function M.EvalDims (w, h) | |
w = w and ceil(ParseNumber(w, "contentWidth")) or nil | |
h = h and ceil(ParseNumber(h, "contentHeight")) or nil | |
return w, h | |
end | |
-- IDEA: EvalDims_Object... could look for, say, text fields and calculate widths relative to those | |
-- Command parsed out a choice string; first and second number arguments to command -- | |
local Command, Num1, Num2 | |
-- Dimension corresponding to command -- | |
local Dim | |
-- Number of tokens parsed during replacement -- | |
local N | |
-- Tokenizes a command string, of the form "command[, num1[, num2]]" | |
local function RepToken (token) | |
if not Command then | |
Command = token | |
else | |
local num = ParseNumber(token, Dim) | |
if Num1 then | |
Num2 = num | |
else | |
Num1 = num | |
end | |
N = N + 1 | |
end | |
end | |
-- Helper to evaluate a coordinate command string | |
local function AuxEvalCoord (arg, choices, dim) | |
Dim, N, Command, Num1, Num2 = dim, 0 | |
gsub(arg, "[^%s]+", RepToken, 3) | |
-- Validate that a command was found, that it exists among the choices, and that not | |
-- too many arguments were provided. If all these are okay, pass along the handler. | |
local choice = assert(choices[Command], "Unrecognized or missing command") | |
assert(N <= choice[2], "Too many arguments") | |
return choice[1] | |
end | |
-- Evaluate the more basic commands | |
local function EvalBasic (arg, choices, dim) | |
return ParseNumber(arg, dim, true) or AuxEvalCoord(arg, choices, dim)(Num1, Num2) | |
end | |
-- Helper for center commands | |
local function Center (func, coord) | |
return { | |
function(delta) | |
return func(display[coord], delta) | |
end, 1 | |
} | |
end | |
-- Helper to reverse a basic evaluation command | |
local function ReverseBasic (func) | |
return { | |
function(delta) | |
return func("100%", delta) | |
end, 1 | |
} | |
end | |
-- Choices used by EvalPos to evaluate the x-coordinte... -- | |
local ChoicesX = { | |
at = { layout.LeftOf, 2 }, | |
center = Center(layout.RightOf, "contentCenterX"), | |
from_right = ReverseBasic(layout.LeftOf) | |
} | |
-- ...and the y-coordinate -- | |
local ChoicesY = { | |
at = { layout.Above, 2 }, | |
center = Center(layout.Below, "contentCenterY"), | |
from_bottom = ReverseBasic(layout.Above) | |
} | |
--- Normalizes position values. | |
-- @tparam[opt] ?|DSL_Number|string x If _x_ is a **DSL_Number**, it evaluates as described | |
-- in the summary. Otherwise, if it is a string, the following commands are available: | |
-- | |
-- * **"at xpos dx"**: Evaluates both _xpos_ and _dx_ and returns their sum. | |
-- * **"center dx"**: Evaluates _dx_ and adds it to the content center. | |
-- * **"from_right dx"**: Evaluates _dx_ and adds it to the right side of the content. | |
-- | |
-- In the above, _xpos_ and _dx_ are of type **DSL_Number**, and resolve to 0 when absent. | |
-- | |
-- Example commands: `"at 20 5%"`, `"center 10%"`, `"from_right -20"`, `"from_right -3.5%"`. | |
-- @tparam[opt] ?|DSL_Number|string y As per _x_. The corresponding choices are **"at"**, | |
-- **"center"**, and **"from_bottom"**, with the obvious changes. | |
-- @treturn ?|number|nil If _x_ is absent, **nil**. Otherwise, the evaluated x-coordinate. | |
-- @treturn ?|number|nil As per _x_. | |
function M.EvalPos (x, y) | |
x = x and EvalBasic(x, ChoicesX, "contentWidth") | |
y = y and EvalBasic(y, ChoicesY, "contentHeight") | |
return x or nil, y or nil | |
end | |
-- Helper to process position / dimension fields in widget constructor options | |
local function AuxProcessWidgetParams (params, t) | |
local x, y, w, h = params.x, params.y, _EvalDims_(params.width, params.height) | |
t.left, t.top, t.x, t.y = _EvalPos_(not x and params.left, not y and params.top) | |
t.width, t.height = w or t.width, h or t.height | |
return t, x, y | |
end | |
--- Convenience utility for doing DSL evaluation of position- or dimension-type fields in | |
-- Corona-style widget constructors. | |
-- @ptable[opt] params If absent, this is a no-op. Otherwise, its **left**, **top**, **x**, | |
-- **y**, **width**, and **height** fields will be processed. | |
-- @ptable[opt] t Receives the evaluated params. If absent, a table is supplied (if _params_ | |
-- also exists). | |
-- | |
-- Any **x** or **y** field in _t_ is removed; the same fields in _params_ are returned, | |
-- instead. This separation is motivated by consistency with other DSL-using code, where | |
-- assignment to **x** and **y** fields support commands such as **"from_right -20"**; these | |
-- assume that the correct dimensions are available, yet this is not so before construction. | |
-- | |
-- Any **width** or **height** field in _params_ is evaluated, cf. @{EvalDims}, the result | |
-- being placed into _t_. If a field is absent, it is also left untouched in _t_. | |
-- | |
-- If **x** (or **y**) is present, any **left** (or **top**) field is removed from _t_. | |
-- Otherwise, that field is evaluated, cf. @{EvalPos}, and added to _t_. | |
-- @todo: The width / height are not used, say, to allow from-right/bottom alignment since | |
-- they aren't officially known until widget creation, and likewise this motivates the | |
-- separate x, y handling... however the widgets are probably predictable enough in general | |
-- to relax this, with some relevant notes | |
-- @treturn ?|table|nil _t_. | |
-- @treturn ?|DSL_number|string|nil If _params_ is present, the original value of `params.x`... | |
-- @treturn ?|DSL_number|string|nil ...and `params.y`. | |
function M.ProcessWidgetParams (params, t) | |
local x, y | |
if params then | |
t, x, y = AuxProcessWidgetParams(params, t or {}) | |
end | |
return t, x, y | |
end | |
--- Variant of @{ProcessWidgetParams} where _params_ does double duty as _t_. | |
-- @ptable[opt] params If absent, this is a no-op. Otherwise, as per @{ProcessWidgetParams}. | |
-- @treturn ?|table|nil _params_. | |
-- @treturn ?|DSL_number|string|nil If _params_ is present, the original value of `params.x`... | |
-- @treturn ?|DSL_number|string|nil ...and `params.y`. | |
function M.ProcessWidgetParams_InPlace (params) | |
local t, x, y | |
if params then | |
t, x, y = AuxProcessWidgetParams(params, params) | |
end | |
return t, x, y | |
end | |
-- Helper to reverse an object-based evaluation command | |
local function ReversePut (func) | |
return { | |
function(object, delta) | |
func(object, "100%", delta) | |
end, 1 | |
} | |
end | |
-- Choices used by PutObjectAt to evaluate the x-coordinate... -- | |
local PutChoicesX = { | |
center = { layout.PutAtCenterX, 1 }, | |
from_right = ReversePut(layout.PutLeftOf), | |
from_right_align = ReversePut(layout.RightAlignWith), | |
left_of = { layout.PutLeftOf, 2 }, | |
right_of = { layout.PutRightOf, 2 } | |
} | |
-- ...and the y-coordinate -- | |
local PutChoicesY = { | |
above = { layout.PutAbove, 2 }, | |
below = { layout.PutBelow, 2 }, | |
center = { layout.PutAtCenterY, 1 }, | |
from_bottom = ReversePut(layout.PutAbove), | |
from_bottom_align = ReversePut(layout.BottomAlignWith) | |
} | |
-- Evaluate commands that involve objects | |
local function EvalPut (object, arg, choices, coord, dim) | |
if arg then | |
local dim = choices == PutChoicesX and "contentWidth" or "contentHeight" | |
local num = ParseNumber(arg, dim, true) | |
if num then | |
object[coord] = num | |
else | |
AuxEvalCoord(arg, choices, dim)(object, Num1, Num2) | |
end | |
end | |
end | |
--- Puts an object at a given normalized position. | |
-- @pobject object Object to position. | |
-- @tparam ?|DSL_Number|string|nil x If absent, the x-coordinate is untouched. Otherwise, | |
-- the number is evaluated as in @{EvalPos} and assigned to the x-coordinate. Available | |
-- commands, and the corresponding x-coordinate, are: | |
-- | |
-- * **"center dx"**: The content center's x-coordinate. | |
-- * **"from_right dx"**: The right side of the content. | |
-- * **"from\_right\_align dx"**: The value that aligns the right side of _object_ to the right | |
-- side of the content. | |
-- * **"left_of xpos dx"**: The value that puts the right side of _object_ at _xpos_. | |
-- * **"right_of xpos dx"**: The value that puts the left side of _object_ at _xpos_. | |
-- | |
-- In each case, _dx_ is evaluated and added to the coordinate; the sum is the final result. | |
-- @tparam ?|DSL_Number|string|nil y As per _x_. The corresponding choices are **"center"**, | |
-- **"from_bottom"**, **"from\_bottom\_align"**, **"above"**, and **"below"**, with the obvious changes. | |
function M.PutObjectAt (object, x, y) | |
EvalPut(object, x, PutChoicesX, "x") | |
EvalPut(object, y, PutChoicesY, "y") | |
end | |
-- Set of valid "put choices" for the x-coordinate... -- | |
local X = { x = true, left = true, center_x = true, right = true } | |
-- ...and for the y-coordinate -- | |
local Y = { y = true, bottom = true, center_y = true, top = true } | |
-- Helper to evaluate a __newindex'd property | |
function EvalNewIndex (object, k, v) | |
if k == "width" or k == "height" then | |
local w, h = _EvalDims_(v, v) | |
return k == "width" and w or h -- Let the original __newindex take it from here | |
elseif X[k] then | |
EvalPut(object, v, PutChoicesX, k) -- All done, so return nothing | |
elseif Y[k] then | |
EvalPut(object, v, PutChoicesY, k) -- Ditto | |
else | |
return v -- Unhandled; just let the original __newindex handle it | |
end | |
end | |
-- Cache module members. | |
_AddProperties_Metatable_ = M.AddProperties_Metatable | |
_EvalDims_ = M.EvalDims | |
_EvalPos_ = M.EvalPos | |
-- TODO: Pens, cursors? | |
-- Export the module. | |
return M |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment