From 019c412d42e939dea8b3a1ebabf435ea5da26b6f Mon Sep 17 00:00:00 2001 From: Leslie Krause Date: Wed, 22 May 2019 19:38:31 -0400 Subject: [PATCH] BUILD 02 - major code reorganization into multiple classes - added support for pixel-based unit of measurement - further simplified some "black magic" constants - implemented margins to simulate container element - revamped translation interface for stateful use - various improvements to element parameter parsing --- README.txt | 12 +- init.lua | 792 ++++++++++++++++++++++++++++------------------------- 2 files changed, 430 insertions(+), 374 deletions(-) diff --git a/README.txt b/README.txt index d78e80b..d4a91fc 100644 --- a/README.txt +++ b/README.txt @@ -1,4 +1,4 @@ -Scarlet Mod v1.0 +Scarlet Mod v1.1 By Leslie Krause Scarlet is a thin-wrapper library for Minetest that provides a logical, uniform system of @@ -20,6 +20,14 @@ Revision History Version 1.0b (18-May-2019) - initial alpha version +Version 1.1b (22-May-2019) + - major code reorganization into multiple classes + - added support for pixel-based unit of measurement + - further simplified some "black magic" constants + - implemented margins to simulate container element + - revamped translation interface for stateful use + - various improvements to element parameter parsing + Compatability ---------------------- @@ -44,7 +52,7 @@ Source Code License The MIT License (MIT) -Copyright (c) 2018, Leslie Krause (leslie@searstower.org) +Copyright (c) 2019, Leslie Krause (leslie@searstower.org) 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 diff --git a/init.lua b/init.lua index 1e17b67..1d460a4 100644 --- a/init.lua +++ b/init.lua @@ -5,474 +5,528 @@ -- Copyright (c) 2019, Leslie E. Krause -------------------------------------------------------- -ScarletDef = function ( screen_dpi, gui_scaling, has_padding ) +scarlet = { } +------------------------ +-- UnitConversion Class +------------------------ + +function UnitConversion( screen_dpi, gui_scaling, has_padding ) + local self = { } local image_size = 0.55 * ( screen_dpi or 72 ) * ( gui_scaling or 1 ) -- calculate the dot pitch as images per pixel and cells per pixel -- using the "black magic" algorithm from guiFormSpecMenu.cpp - local dot_pitch = { + + self.dot_pitch = { -- imgsize (images per pixel) ix = 1 / image_size, iy = 1 / image_size, -- spacing (cells per pixel) - x = 1 / ( image_size * 5 / 4 ), - y = 1 / ( image_size * 15 / 13 ) + x = 1 / image_size * 4 / 5, + y = 1 / image_size * 13 / 15 } -- padding width and height in cell units -- original formula: image_size * 3.0 / 8 - local padding_width = has_padding and 0 or 3 / 10 - local padding_height = has_padding and 0 or 13 / 40 + self.padding_width = has_padding and 0.0 or 3 / 10 + self.padding_height = has_padding and 0.0 or 13 / 40 -- nominal button height in cell units -- NB: this is 2x the value of m_button_height used internally! -- original formula: image_size * 15.0 / 13 * 0.35 - local button_height = 0.7 + self.button_height = 0.7 -- cell margin width and height in cell units -- original formula: vx * spacing.x - ( spacing.x - imgsize.x ) - local cell_margin_width = 1 / 5 - local cell_margin_height = 2 / 15 + self.cell_margin_width = 1 / 5 + self.cell_margin_height = 2 / 15 - --------------------------- - -- unit conversion objects - --------------------------- - - local units = ( function ( ) + self.units = ( function ( ) -- cell size measurements - local factors = { - i = { x = 4 / 5, y = 13 / 15 }, -- imgsize + local factors = { + d = { x = 1 / 39.6 * 4 / 5, y = 1 / 39.6 * 13 / 15 }, + i = { x = 4 / 5, y = 13 / 15 }, -- imgsize c = { x = 1, y = 1 }, -- spacing (unity) - b = { y = button_height }, -- 2 x m_button_height + b = { y = self.button_height }, -- 2 x m_button_height } - local function get_x( v, u ) - return not factors[ u ] and math.floor( v ) * dot_pitch.x or v * factors[ u ].x + local function get_x( v, u, dot_pitch ) + return not factors[ u ] and math.floor( v ) * self.dot_pitch.x or v * factors[ u ].x end - local function get_y( v, u ) - return not factors[ u ] and math.floor( v ) * dot_pitch.y or v * factors[ u ].y + local function get_y( v, u, dot_pitch ) + return not factors[ u ] and math.floor( v ) * self.dot_pitch.y or v * factors[ u ].y end return { get_x = get_x, get_y = get_y } end )( ) - local iunits = ( function ( ) + self.iunits = ( function ( ) -- image size measurements local factors = { + d = { x = 1 / 39.6, y = 1 / 39.6 }, i = { x = 1, y = 1 }, -- imgsize (unity) c = { x = 5 / 4, y = 15 / 13 }, -- spacing - b = { y = button_height * 15 / 13 }, -- 2 x m_button_height + b = { y = self.button_height * 15 / 13 }, -- 2 x m_button_height } - local function get_x( v, u ) - return not factors[ u ] and math.floor( v ) * dot_pitch.ix or v * factors[ u ].x + local function get_x( v, u ) + return not factors[ u ] and math.floor( v ) * self.dot_pitch.ix or v * factors[ u ].x end - local function get_y( v, u ) - return not factors[ u ] and math.floor( v ) * dot_pitch.iy or v * factors[ u ].y + local function get_y( v, u ) + return not factors[ u ] and math.floor( v ) * self.dot_pitch.iy or v * factors[ u ].y end return { get_x = get_x, get_y = get_y } end )( ) - ----------------------- - -- translation helpers - ----------------------- + return self +end - local function element( name, params ) - return name .. "[" .. table.concat( params, ";" ) .. "]" - end +--------------------------- +-- RuntimeTranslator Class +--------------------------- - local function get_pos( params, pos_units, offsets ) - local replace = "%0.3f,%0.3f" - local pos_x, u1, pos_y, u2 = string.match( params[ 1 ], "^(-?[0-9.]+)([icp]?),(-?[0-9.]+)([icpb]?)$" ) +function RuntimeTranslator( screen_dpi, gui_scaling, has_padding ) - if not pos_x then return end + local self = UnitConversion( screen_dpi, gui_scaling, has_padding ) -- extend the base class - return string.format( replace, - pos_units.get_x( pos_x, u1 ) + offsets[ 1 ], - pos_units.get_y( pos_y, u2 ) + offsets[ 2 ] + self.margins = { { width = 0.0, height = 0.0 } } + + self.insert_margin = function ( width, height ) + table.insert( self.margins, 1, + { width = width + self.margins[ 1 ].width, height = height + self.margins[ 1 ].height } ) end - local function get_pos_and_dim_x( params, pos_units, dim_units, offsets ) - local replace = "%0.3f,%0.3f;%0.3f,0.000" - local pos_x, u1, pos_y, u2 = string.match( params[ 1 ], "^(-?[0-9.]+)([icp]?),(-?[0-9.]+)([icpb]?)$" ) - local dim_x, u3 = string.match( params[ 2 ], "^([0-9.]+)([iscp]?)$" ) - - if not pos_x or not dim_x then return end - - if u3 == "s" then - u3 = "i" - offsets[ 3 ] = offsets[ 3 ] + ( dim_x % 1 == 0 and dim_x - 1 or math.floor( dim_x ) ) * cell_margin_width - end - - return string.format( replace, - pos_units.get_x( pos_x, u1 ) + offsets[ 1 ], - pos_units.get_y( pos_y, u2 ) + offsets[ 2 ], - dim_units.get_x( dim_x, u3 ) + offsets[ 3 ] - ) - end - - local function get_pos_and_dim( params, pos_units, dim_units, offsets ) - local replace = "%0.3f,%0.3f;%0.3f,%0.3f" - local pos_x, u1, pos_y, u2 = string.match( params[ 1 ], "^(-?[0-9.]+)([icp]?),(-?[0-9.]+)([icpb]?)$" ) - local dim_x, u3, dim_y, u4 = string.match( params[ 2 ], "^([0-9.]+)([iscp]?),([0-9.]+)([iscpb]?)$" ) - - if not pos_x or not dim_x then return end - - if u3 == "s" then - u3 = "i" - offsets[ 3 ] = offsets[ 3 ] + ( dim_x % 1 == 0 and dim_x - 1 or math.floor( dim_x ) ) * cell_margin_width - end - if u4 == "s" then - u4 = "i" - offsets[ 4 ] = offsets[ 4 ] + ( dim_y % 1 == 0 and dim_y - 1 or math.floor( dim_y ) ) * cell_margin_height - end - - return string.format( replace, - pos_units.get_x( pos_x, u1 ) + offsets[ 1 ], - pos_units.get_y( pos_y, u2 ) + offsets[ 2 ], - dim_units.get_x( dim_x, u3 ) + offsets[ 3 ], - dim_units.get_y( dim_y, u4 ) + offsets[ 4 ] - ) - end - - ------------------------------ - -- generic element subclasses - ------------------------------ - - local function ElemPos( pos_units, length ) - return function ( name, params ) - local pos = get_pos( params, pos_units, { - -padding_width, - -padding_height - } ) - assert( pos and #params == length, "Cannot parse formspec element " .. name .. "[]" ) - return element( name, { pos, unpack( params, 2 ) } ) + self.remove_margin = function ( ) + if #self.margins > 1 then + table.remove( self.margins, 1 ) end end - local function ElemPosAndDim( pos_units, dim_units, length, is_unlimited ) - return function ( name, params ) - local pos_and_dim = get_pos_and_dim( params, pos_units, dim_units, { - -padding_width, - -padding_height, - 0, - 0 - } ) - assert( pos_and_dim and ( #params == length or is_unlimited and #params > length ), "Cannot parse formspec element " .. name .. "[]" ) - return element( name, { pos_and_dim, unpack( params, 3 ) } ) + self.get_pos = function ( pos_value, pos_units, offsets ) + if pos_value then + local pos_x, u1, pos_y, u2 = string.match( pos_value, "^(-?[0-9.]+)([icpd]?),(-?[0-9.]+)([icpdb]?)$" ) + + if not pos_x then return end + + return string.format( "%0.3f,%0.3f", + pos_units.get_x( pos_x, u1 ) + offsets[ 1 ] + self.margins[ 1 ].width, + pos_units.get_y( pos_y, u2 ) + offsets[ 2 ] + self.margins[ 1 ].height + ) end end - local function ElemPosAndDimX( pos_units, dim_units, length, is_unlimited ) - return function ( name, params ) - local pos_and_dim_x = get_pos_and_dim_x( params, pos_units, dim_units, { - -padding_width, - -padding_height, - 0 - } ) - assert( pos_and_dim_x and ( #params == length or is_unlimited and #params > length ), "Cannot parse formspec element " .. name .. "[]" ) - return element( name, { pos_and_dim_x, unpack( params, 3 ) } ) + self.get_pos_and_dim_x = function ( pos_value, dim_value, pos_units, dim_units, offsets ) + if pos_value and dim_value then + local pos_x, u1, pos_y, u2 = string.match( pos_value, "^(-?[0-9.]+)([icpd]?),(-?[0-9.]+)([icpdb]?)$" ) + local dim_x, u3 = string.match( dim_value, "^([0-9.]+)([iscp]?)$" ) + + if not pos_x or not dim_x then return end + + if u3 == "s" then + u3 = "i" + offsets[ 3 ] = offsets[ 3 ] + ( dim_x % 1 == 0 and dim_x - 1 or math.floor( dim_x ) ) * self.cell_margin_width + end + + return string.format( "%0.3f,%0.3f;%0.3f,0.000", + pos_units.get_x( pos_x, u1 ) + offsets[ 1 ] + self.margins[ 1 ].width, + pos_units.get_y( pos_y, u2 ) + offsets[ 2 ] + self.margins[ 1 ].height, + dim_units.get_x( dim_x, u3 ) + offsets[ 3 ] + ) end end - ------------------------------ - -- special element subclasses - ------------------------------ + self.get_pos_and_dim = function ( pos_value, dim_value, pos_units, dim_units, offsets ) + if pos_value and dim_value then + local pos_x, u1, pos_y, u2 = string.match( pos_value, "^(-?[0-9.]+)([icpd]?),(-?[0-9.]+)([icpdb]?)$" ) + local dim_x, u3, dim_y, u4 = string.match( dim_value, "^([0-9.]+)([iscpd]?),([0-9.]+)([iscpdb]?)$" ) - local function SizeElement( ) - local pattern = "^(%d+)([iscp]?),(%d+)([iscpb]?)$" - local replace = "%0.3f,%0.3f,true" + if not pos_x or not dim_x then return end - return function ( name, params ) - local dim, count = string.gsub( params[ 1 ], pattern, function ( dim_x, u1, dim_y, u2 ) - return string.format( replace, - units.get_x( dim_x, u1 ), - units.get_y( dim_y, u2 ) - ) - end ) - assert( count == 1, "Cannot parse formspec element size[]" ) - return element( "size", { dim } ) + if u3 == "s" then + u3 = "i" + offsets[ 3 ] = offsets[ 3 ] + ( dim_x % 1 == 0 and dim_x - 1 or math.floor( dim_x ) ) * self.cell_margin_width + end + if u4 == "s" then + u4 = "i" + offsets[ 4 ] = offsets[ 4 ] + ( dim_y % 1 == 0 and dim_y - 1 or math.floor( dim_y ) ) * self.cell_margin_height + end + + return string.format( "%0.3f,%0.3f;%0.3f,%0.3f", + pos_units.get_x( pos_x, u1 ) + offsets[ 1 ] + self.margins[ 1 ].width, + pos_units.get_y( pos_y, u2 ) + offsets[ 2 ] + self.margins[ 1 ].height, + dim_units.get_x( dim_x, u3 ) + offsets[ 3 ], + dim_units.get_y( dim_y, u4 ) + offsets[ 4 ] + ) end end - local function ListElement( ) - local pattern = "^(%d+)([icp]?),(%d+)([icpb]?)$" - local replace = "%0.3f,%0.3f" + return self +end - return function ( name, params ) - local pos, count = string.gsub( params[ 3 ], pattern, function ( pos_x, u1, pos_y, u2 ) - return string.format( replace, - units.get_x( pos_x, u1 ) - padding_width, - units.get_y( pos_y, u2 ) - padding_height - ) - end ) - assert( count == 1, "Cannot parse formspec element list[]" ) - return element( "list", { params[ 1 ], params[ 2 ], pos, unpack( params, 4 ) } ) - end +------------------------------ +-- Generic Element Subclasses +------------------------------ + +local function element( name, params ) + return name .. "[" .. table.concat( params, ";" ) .. "]" +end + +local function ElemPos( pos_units, length ) + return function ( tx, name, params ) + local pos = tx.get_pos( params[ 1 ], pos_units, { + -tx.padding_width, + -tx.padding_height + } ) + assert( pos and #params == length, "Cannot parse formspec element " .. name .. "[]" ) + return element( name, { pos, unpack( params, 2 ) } ) end +end - -- container[,] - - local function ContainerElement( ) - return function ( name, params ) - local pos = get_pos( params, units, { 0, 0 } ) - assert( pos and #params == 1, "Cannot parse formspec element container[]" ) - return element( "container", { pos } ) - end +local function ElemPosAndDim( pos_units, dim_units, length, is_unlimited ) + return function ( tx, name, params ) + local pos_and_dim = tx.get_pos_and_dim( params[ 1 ], params[ 2 ], pos_units, dim_units, { + -tx.padding_width, + -tx.padding_height, + 0, + 0 + } ) + assert( pos_and_dim and ( #params == length or is_unlimited and #params > length ), "Cannot parse formspec element " .. name .. "[]" ) + return element( name, { pos_and_dim, unpack( params, 3 ) } ) end +end - -- background[,;] - - local function BackgroundElement( ) - local pattern = "^(-?%d+),(-?%d+)$" - local replace = "%d,%d;0,0" - - return function ( name, params ) - local dim, count = string.gsub( params[ 1 ], pattern, function ( pos_x, pos_y ) - return string.format( replace, -pos_x, -pos_y ) - end ) - assert( count == 1, "Cannot parse formspec element background[]" ) - return element( "background", { dim, params[ 2 ], "true" } ) - end +local function ElemPosAndDimX( pos_units, dim_units, length, is_unlimited ) + return function ( tx, name, params ) + local pos_and_dim_x = tx.get_pos_and_dim_x( params[ 1 ], params[ 2 ], pos_units, dim_units, { + -tx.padding_width, + -tx.padding_height, + 0 + } ) + assert( pos_and_dim_x and ( #params == length or is_unlimited and #params > length ), "Cannot parse formspec element " .. name .. "[]" ) + return element( name, { pos_and_dim_x, unpack( params, 3 ) } ) end +end - -- bgimage[,;,;] +------------------------------ +-- Special Element Subclasses +------------------------------ - local function BgImageElement( ) +local function SizeElement( ) + local pattern = "^(%d+)([iscp]?),(%d+)([iscpb]?)$" + local replace = "%0.3f,%0.3f,true" + + return function ( tx, name, params ) + local dim, count = string.gsub( params[ 1 ], pattern, function ( dim_x, u1, dim_y, u2 ) + return string.format( replace, + tx.units.get_x( dim_x, u1 ), + tx.units.get_y( dim_y, u2 ) + ) + end ) + assert( count == 1, "Cannot parse formspec element size[]" ) + return element( "size", { dim } ) + end +end + +-- list[;;,;;] + +local function ListElement( ) + local pattern = "^(%d+)([icp]?),(%d+)([icpb]?)$" + local replace = "%0.3f,%0.3f" + + return function ( tx, name, params ) + local pos = tx.get_pos( params[ 3 ], tx.units, { + -tx.padding_width, + -tx.padding_height, + } ) + assert( pos and ( #params == 4 or #params == 5 ), "Cannot parse formspec element list[]" ) + return element( "list", { params[ 1 ], params[ 2 ], pos, unpack( params, 4 ) } ) + end +end + +-- margin[] +-- margin[,] + +local function MarginElement( ) + local pattern = "^(-?[0-9.]+)([icpd]?),(-?[0-9.]+)([icpdb]?)$" + + return function ( tx, name, params ) + if #params == 0 then + tx.insert_margin( tx.padding_width, tx.padding_height ) + else + local pos_x, u1, pos_y, u2 = string.match( params[ 1 ], pattern ) + assert( pos_x and #params == 1, "Cannot parse formspec element margin[]" ) + tx.insert_margin( tx.units.get_x( pos_x, u1 ), tx.units.get_y( pos_y, u2 ) ) + end + return "" -- remove virtual element + end +end + +-- margin_end[] + +local function MarginEndElement( ) + return function ( tx, name, params ) + assert( #params == 0, "Cannot parse formspec element container_end[]" ) + tx.remove_margin( ) + return "" -- remove virtual element + end +end + +-- background[,;] + +local function BackgroundElement( ) + local pattern = "^(-?%d+),(-?%d+)$" + local replace = "%d,%d;0,0" + + return function ( tx, name, params ) + local dim, count = string.gsub( params[ 1 ], pattern, function ( pos_x, pos_y ) + return string.format( replace, -pos_x, -pos_y ) + end ) + assert( count == 1, "Cannot parse formspec element background[]" ) + return element( "background", { dim, params[ 2 ], "true" } ) + end +end + +-- bgimage[,;,;] + +local function BgImageElement( ) + return function ( tx, name, params ) -- original formula: vx * spacing.x - ( spacing.x - imgsize.x ) / 2 local pos_offset_x = 0.5 * 1 / 5 local pos_offset_y = 0.5 * 2 / 15 - return function ( name, params ) - local pos_and_dim = get_pos_and_dim( params, units, units, { - -padding_width + pos_offset_x, - -padding_height + pos_offset_y, - 0, - 0 - } ) - assert( pos_and_dim and #params == 3, "Cannot parse formspec element bgimage[]" ) - return element( "background", { pos_and_dim, params[ 3 ], "false" } ) - end + local pos_and_dim = tx.get_pos_and_dim( params[ 1 ], params[ 2 ], tx.units, tx.units, { + -tx.padding_width + pos_offset_x, + -tx.padding_height + pos_offset_y, + 0, + 0 + } ) + assert( pos_and_dim and #params == 3, "Cannot parse formspec element bgimage[]" ) + return element( "background", { pos_and_dim, params[ 3 ], "false" } ) end +end - -- label[,;