Module:Test2: Difference between revisions

From Resonite Wiki
mNo edit summary
No edit summary
 
(94 intermediate revisions by 2 users not shown)
Line 3: Line 3:
local p = {}
local p = {}


local fieldHandlers = {}
local ProtofluxColor = require("Module:ProtoFlux_Type_Color")


local css = {
attachment_point = function(self, color, order)
return string.format(
"height:20px; background-color: black; fill: %s; stroke: %s; order: %s;",
ColorToCss(MultA(color, 0.15)),
ColorToCss(color),
order
)
end,
}


-- Type colors - RGB values to be included in the HTML output when a color is needed. This will be replaced with CSS at some point.
local fieldHandlers = {}
local typeColor =  
{
User = '255, 128, 255',
Impulse = '179, 255, 255',
bool = '115, 115, 115',
AsyncImpulse = '204, 179, 255',
String = '245, 31, 31',
Dummy = '255, 0, 255',
IFormatProvider = '168, 143, 214',
float = '0, 255, 255',
ColorProfile = '255, 196, 54',
colorX = '255, 89, 0',
Component = '112, 76, 85',
float3 = '0, 255, 255',
float2 = '0, 255, 255',
    ulong = '0, 255, 127',
    int = '0, 255, 0',
    Continuation = '255, 255, 178',
    Slot = '191, 255, 127'
}


--- Method that generates the HTML output
--- Method that generates the HTML output
Line 37: Line 27:
local componentContainer = mw.html.create( 'div' )
local componentContainer = mw.html.create( 'div' )
componentContainer
componentContainer
    :attr('class', frame.args.Inline and '' or 'floatright')
:attr('class', frame.args.Inline and '' or 'floatright')
:cssText('color: rgb(224, 224, 224); background-color: rgb(18, 20, 28); width: 512px; display: flex; flex-direction: column; align-items: stretch; gap: 3px;')
:cssText('color: rgb(224, 224, 224); background-color: rgb(18, 20, 28); width: 512px; display: flex; flex-direction: column; align-items: stretch; gap: 6px; padding: 8px;')
:tag('div') -- HTML div to contain the component title
:tag('div') -- HTML div to contain the component title and buttons
:cssText('color: rgb(247, 247, 112); border-radius: 10px; background-color: rgb(43, 46, 54); text-align:center; font-size: 1.25rem; font-weight: bold; flex-grow: 1;')
:cssText('display:flex;gap:4px;')
:wikitext(frame.args.Name)
:tag('div') -- component title
:done() -- Close node title div
:cssText('color: #f8f770; /* Resonite Yellow */ border-radius: 8px; background-color: #2b2f35; /* Resonite Mid */ text-align:center; font-size: 1.25rem; font-weight: bold; flex-grow: 1;')
:wikitext(frame.args.Name)
:done() -- Close component title div
:tag('div') -- duplicate button
:cssText('background-color: #24512c; /* Resonite Sub-Green */ border-radius: 8px; width: 2.5rem; display: flex; align-items:center; justify-content:center;')
:wikitext("[[File:Duplicate.svg|Duplicate|28px]]")
:done()
:tag('div') -- destroy button
:cssText('background-color: #5d323a; /* Resonite Sub-Red */ border-radius: 8px; width: 2.5rem; display: flex; align-items:center; justify-content:center;')
:wikitext("[[File:Destroy.svg|Destroy|28px]]")
:done()
:done() -- Close title and buttons div
-- Iterate over each row, and populate it with the inputs/outputs. If the node is asymmetric,
-- Iterate over each row, and populate it with the inputs/outputs. If the node is asymmetric,
Line 59: Line 60:
function CreateField(Container, Field)
function CreateField(Container, Field)
local fieldContentContainer = Container
local fieldContentContainer = Container
:tag('div') -- HTML div to contain the field. We specify a min-height of 40px for consistency.
:tag('div') -- HTML div to contain the field.
:cssText('display: flex;')
:cssText('display: flex;')
:tag('div') -- HTML div to contain the field
:tag('div') -- HTML div to contain the field
:cssText( 'flex-grow: 2; overflow: hidden; display: flex; flex-direction: row;')
:cssText( 'flex-grow: 2; overflow: hidden; display: flex; flex-direction: row;')
if fieldHandlers[Field.FieldType] == nil then
fieldContentContainer:wikitext("ERROR: No handler for field type " .. Field.FieldType)
return
end
fieldHandlers[Field.FieldType](fieldContentContainer, Field)
fieldHandlers[Field.FieldType](fieldContentContainer, Field)
end
end


function fieldHandlers.Sync(Container, Field)
function fieldHandlers.Sync(Container, Field)
Container
CreateFieldLabel(Container,Field)
:tag('div') -- HTML div to contain the field label
CreateUISync(Container, Field)
:attr('title', Field and (Field.Name .. ' <' .. Field.Type .. '>') or '') -- Add a basic mouseover description
end
:cssText('text-align: left; width:30%; overflow: hidden; text-overflow: ellipsis; padding-left: 4px;')
 
:wikitext( (Field and Field.Name or '') .. ':' )
function CreateUISync(Container, Field)
:done() -- Close field label div
if Field.Type == 'bool' then
if Field.Type == 'Bool' then
return CreateUICheckbox(Container, Field.Value)
Container
elseif Field.Type == 'int' or Field.Type == 'String' or Field.Type == 'float' or Field.Type == 'double' then
:tag('div')
return CreateUIInputBox(Container, Field.Value)
:cssText('display:flex; flex-direction: column; align-items:center; justify-content: center;')
elseif Field.Type == 'float2' or Field.Type == 'float3' then
:tag('div')
return CreateUIVector(Container, Field.Value)
:cssText('background-color: rgb(43, 46, 54); border-radius: 6px; width: 1.5rem; height: 1.5rem;')
elseif Field.Enum then
elseif Field.Type == 'Int' or Field.Type == 'String' then
return CreateUIForwardBackwards(Container, Field.Value)
Container
elseif Field.Type == "colorX" then
:tag('div')
:cssText('display:flex; flex-direction: column; align-items:stretch; flex-grow:1;')
:tag('div')
:cssText('border: 2px solid rgb(43, 46, 54); border-radius: 8px; background: rgb(30, 33, 38); flex-grow:1;')
elseif Field.Type == 'Float' or Field.Type == 'Double' then
Container
:tag('div')
:cssText('display:flex; flex-direction: row; align-items:center; flex-grow:1; gap: 4px;')
:tag('div')
:cssText('height: 0.5rem; border-radius: 8px; background: rgb(43, 46, 54); flex-grow:5;')
:tag('div')
:cssText('height:1em; width: 1em; position:relative; top: -0.25rem; border-radius: 1em; background-color:white;')
:done()
:done()
:tag('div')
:cssText('height: 1.5rem; border: 2px solid rgb(43, 46, 54); border-radius: 8px; background: rgb(30, 33, 38); flex-grow:1; display:flex; justify-content: center; align-items:center;')
:wikitext('0')
elseif Field.Type == "TextHorizontalAlignment" then
CreateUIForwardBackwards(Container, "Left")
elseif Field.Type == "TextVerticalAlignment" then
CreateUIForwardBackwards(Container, "Top")
elseif Field.Type == "Elements.Assets.AlignmentMode" then
CreateUIForwardBackwards(Container, "Geometric")
elseif Field.Type == "Alignment" then
CreateUIForwardBackwards(Container, "TopLeft")
elseif Field.Type == "ColorX" then
local c = Container
local c = Container
:tag('div')
:tag('div')
:cssText('display:flex; flex-direction: column; align-items:stretch; flex-grow:1; gap: 4px;')
:cssText('display:flex; flex-direction: column; align-items:stretch; flex-grow:1; gap: 4px;')
:tag('div')
CreateUIColor(c, Field.Value)
:cssText('display:flex; height: 1.5rem; flex-direction: row; align-items:stretch; flex-grow:1; gap: 4px;')
 
:wikitext('R')
local c2 = c:tag('div')
:tag('div')
:cssText('border: 2px solid rgb(43, 46, 54); border-radius: 8px; background: rgb(30, 33, 38); flex-grow:1; display:flex; justify-content: center; align-items:center;')
:wikitext('0')
:done()
:wikitext('G')
:tag('div')
:cssText('border: 2px solid rgb(43, 46, 54); border-radius: 8px; background: rgb(30, 33, 38); flex-grow:1; display:flex; justify-content: center; align-items:center;')
:wikitext('0')
:done()
:wikitext('B')
:tag('div')
:cssText('border: 2px solid rgb(43, 46, 54); border-radius: 8px; background: rgb(30, 33, 38); flex-grow:1; display:flex; justify-content: center; align-items:center;')
:wikitext('0')
:done()
:wikitext('A')
:tag('div')
:cssText('border: 2px solid rgb(43, 46, 54); border-radius: 8px; background: rgb(30, 33, 38); flex-grow:1; display:flex; justify-content: center; align-items:center;')
:wikitext('0')
:done()
:tag('div')
:cssText('display:flex; height: 1.5rem; flex-direction: row; align-items:stretch; flex-grow:1;')
:tag('div')
:cssText('background-color: black; flex-grow: 1;')
:done()
:tag('div')
:cssText('background-color: white; flex-grow: 1;')
:done()
:done()
:done()
:tag('div')
:cssText('display:flex; flex-direction: row; align-items: stretch; flex-grow: 1; gap: 4px;')
:cssText('display:flex; flex-direction: row; align-items: stretch; flex-grow: 1; gap: 4px;')
:tag('div')
:tag('div')
Line 148: Line 99:
:wikitext('Profile:')
:wikitext('Profile:')
:done()
:done()
CreateUIForwardBackwards(c, "sRGB")
CreateUIForwardBackwards(c2, Field.Value[5])
return c
else
else
Container
return Container
:wikitext( Field.FieldType .. '<' .. Field.Type .. '>')
:wikitext( Field.FieldType .. '<' .. Field.Type .. '>')
end
end
end
function fieldHandlers.SyncRef(Container,Field)
CreateFieldLabel(Container,Field)
CreateUISyncRefEditor(Container, Field.Value)
end
function fieldHandlers.AssetRef(Container,Field)
CreateFieldLabel(Container,Field)
CreateUIAssetRef(Container,Field)
end
function CreateUIAssetRef(Container,Field)
if Field.Type == "ITexture2D" then
local c = Container
:tag('div')
:cssText('display:flex;flex:1;gap:4px;')
c:tag('div') -- texture
:cssText('aspect-ratio:1;height:100%;background-color:white;')
:done() -- close texture
local c2 = c:tag('div') -- name and buttons
:cssText('display:flex;flex-direction:column;flex:1;')
c2:tag('div') -- name
:cssText('text-align:center; font-style:italic;')
:wikitext('null')
:done()
c2:tag('div') -- info
:cssText('text-align:center;')
:wikitext('---')
:done()
local buttons = c2:tag('div') -- buttons
:cssText('display:flex;gap:4px;')
CreateUIButton(buttons, "Clear", false, false, true)
CreateUIButton(buttons, "⤴", false, false, true)
CreateUIButton(buttons, "⧉", false, false, true)
CreateUIButton(buttons, "📋", false, false, true)
return c
end
return CreateUISyncRefEditor(Container, Field.Value)
end
function fieldHandlers.SyncAssetList(Container,Field)
local c = Container
:tag('div')
:cssText('display: flex; flex-direction: column; flex-grow: 1; gap: 4px;')
:tag('div') -- HTML div to contain the field label
:attr('title', Field and (Field.Name .. ' ' .. Field.FieldType .. '<' .. Field.Type .. '>') or '') -- Add a basic mouseover description
:cssText('text-align: center;')
:wikitext( (Field and Field.Name or '') .. ' (list):' )
:done() -- Close field label div
for i=1,#Field.Value do
local listElement = {Type=Field.Type,Value=Field.Value[i]}
local c2 = c:tag('div'):cssText('display:flex;gap:4px;')
c2:tag('div'):cssText("display:flex;align-items:center;justify-content:center;"):wikitext((i-1)..':')
CreateUIAssetRef(c2, listElement)
CreateUIButton(c2, 'X', false, true, false)
end
CreateUINormalButton(c, 'Add')
end
function fieldHandlers.SyncObject(Container, Field)
Container = Container
:tag('div')
:cssText('display:flex; flex-direction:column;gap:6px;width:100%;')
CreateFieldLabel(Container, Field)
local c = Container
:tag('div') -- Indented contianer
:cssText('display:flex;flex:1;')
c:tag('div') -- Black line
:cssText('background-color:black;width:4px;margin: 0 6px;')
:done() -- Close Black line
fieldContainer = c:tag('div'):cssText("display:flex;flex-direction:column;gap:6px;flex:1;")
for i=1,#Field.Fields do
CreateField(fieldContainer, Field.Fields[i])
end
end
function fieldHandlers.DelegateButton(Container, Field)
CreateUIRefInputBox(Container, Field.Name)
end
function CreateFieldLabel(Container, Field)
    local hover_text =  Field and (Field.Name .. ' ' .. Field.FieldType .. '<' .. Field.Type .. '>') or ''
local c = Container
:tag('div')
:attr('title', hover_text) -- Add a basic mouseover description
:cssText("display:flex; width:30%; align-items:center;")
local extrasButton = c
:tag('div')
:cssText('display:flex; align-items:center; justify-content:center; gap: 2px; align-self:stretch; width:32px; margin-right:2px; background-color: #2B2F35; /* Resonite Mid */ border-radius: 8px;')
local color, is_impulse = GetColor(Field)
local segments = GetTypeSegments(Field)
CreateConnectorAttachmentPoint(extrasButton, hover_text, color, segments, false, true)
extrasButton:tag('div') -- HTML div to contain the extras button
:cssText('text-align:center; display:flex;align-items:center;justify-content:center;order:2;')
:wikitext("☰")
:done() -- Close extras button div
local light = {r=0.71,g=0.75,b=0.81,a=1} -- Resonite Light
local textColor = LerpColor(light, color, 0.1)
c:tag('div') -- HTML div to contain the field label
:cssText('text-align: left; overflow: hidden; text-overflow: ellipsis; flex: 1;color:' .. ColorToCss(textColor))
:wikitext( (Field and Field.Name or '') .. ':' )
:done() -- Close field label div
end
function LerpColor(from, to, lerp)
return {r=Lerp(from['r'], to['r'], lerp),g=Lerp(from['g'], to['g'], lerp),b=Lerp(from['b'], to['b'], lerp),a=Lerp(from['a'] or 1, to['a'] or 1, lerp),}
end
function Lerp(from, to, lerp)
return from * (1-lerp) + to * lerp
end
function CreateUIVector(Container, Vector)
local c = Container
:tag('div')
:cssText('display:flex; height: 1.5rem; flex-direction: row; align-items:stretch; flex-grow:1; gap: 4px;')
local letters = {"x", "y", "z", "w"}
for i=1,#Vector do
CreateUIBasicText(c,letters[i] or "?")
CreateUIInputBox(c, Vector[1])
end
end
function CreateUIColor(Container, Color)
local ColorNoAlpha = {Color[1], Color[2], Color[3], 1}
local c = Container
:tag('div')
:cssText('display:flex; height: 1.5rem; flex-direction: row; align-items:stretch; flex-grow:1; gap: 4px;')
CreateUIBasicText(c, 'R')
CreateUIInputBox(c, Color[1])
CreateUIBasicText(c, 'G')
CreateUIInputBox(c, Color[2])
CreateUIBasicText(c, 'B')
CreateUIInputBox(c, Color[3])
CreateUIBasicText(c, 'A')
CreateUIInputBox(c, Color[4])
c:tag('div')
:cssText('display:flex; height: 1.5rem; flex-direction: row; align-items:stretch; flex-grow:1;')
:tag('div')
:cssText('flex-grow: 1; background-color: ' .. ColorToCss(ColorNoAlpha) .. ';')
:done()
:tag('div')
-- TODO: add the checkerboard here, I wish I could just use a background-image but mediawiki blocks those
:cssText('flex-grow: 1; background-color: white;')
:tag('div')
:cssText('height: 100%; background-color: ' .. ColorToCss(Color) .. ';')
:done()
:done()
:done()
end
function CreateUIBasicText(Container, Text)
Container
:wikitext(Text)
:done()
return Container;
end
end


Line 166: Line 296:
CreateUISmallButton(c, '>>')
CreateUISmallButton(c, '>>')
return Container
return Container
end
function CreateUICheckbox(Container, Checked)
local c = Container
:tag('div')
:cssText('display:flex; align-items:center;box-sizing: border-box; background-color: #2b2f35; /* Resonite Mid */ border-radius: 8px; width: 1.5rem; height: 1.5rem; padding: 2px;')
if Checked then
c:wikitext('[[File:Checkmark.png|20px]]')
end
end
end


function CreateUISmallButton(Container, Text)
function CreateUISmallButton(Container, Text)
return CreateUIButton(Container, Text, true, true, false)
end
function CreateUINormalButton(Container, Text)
return CreateUIButton(Container, Text, true, false, false)
end
function CreateUIButton(Container, Text, LimitHeight, LimitWidth, Flex)
local grow = (not(LimitHeight and LimitWidth) and 'flex-grow:1;' or '')
Container
Container
:tag('div')
:tag('div')
:cssText('width: 1.5rem; background-color: rgb(43, 46, 54); border-radius: 8px; flex-grow:0; display:flex; justify-content: center; align-items:center;')
:cssText('display:flex; flex-direction: column; align-items:stretch; justify-content: center;' .. (Flex and 'flex:1;' or ''))
:wikitext(Text)
:tag('div')
:done()
:cssText('background-color: rgb(43, 46, 54); border-radius: 8px; display:flex; justify-content: center; align-items:center;' .. (LimitHeight and 'height:1.5rem;' or '') .. (LimitWidth and 'width:1.5rem;' or '') .. grow)
:wikitext(Text)
:done()
return Container
return Container
end
end


function CreateUIInputBox(Container, Text, Italics)
function CreateUIInputBox(Container, Text)
local Italics = false
if not Text then
Text = 'null'
Italics = true
end
Container
Container
:tag('div')
:tag('div')
:cssText('background: rgb(43, 46, 54); border-radius: 8px; flex-grow:1; display:flex; justify-content: center; align-items:center;' ..  (Italics and 'font-style:italic;' or ''))
:cssText('box-sizing: border-box; height: 1.5rem; border: 3px solid rgb(43, 46, 54); border-radius: 8px; background: rgb(30, 33, 38); flex-grow:1; display:flex; justify-content: center; align-items:center;'  ..  (Italics and 'font-style:italic;' or ''))
:wikitext(Text)
:done()
end
 
function CreateUIRefInputBox(Container, Text)
local Italics = false
if not Text then
Text = 'null'
Italics = true
end
Container
:tag('div')
:cssText('height: 1.5rem; background: rgb(43, 46, 54); border-radius: 8px; flex-grow:1; display:flex; justify-content: center; align-items:center;' ..  (Italics and 'font-style:italic;' or ''))
:wikitext(Text)
:wikitext(Text)
:done()
:done()
Line 189: Line 359:
local c = Container
local c = Container
:tag('div')
:tag('div')
:cssText('display:flex; height: 1.5rem; flex-direction: row; align-items:stretch; flex-grow:1; gap: 4px;')
:cssText('display:flex; flex-direction: row; align-items:stretch; flex-grow:1; gap: 4px;')
CreateUISmallButton(c, '⤴')
CreateUISmallButton(c, '⤴')
CreateUISmallButton(c, '↑')
CreateUISmallButton(c, '↑')
CreateUIInputBox(c, Text or 'null')
CreateUIRefInputBox(c, Text)
CreateUISmallButton(c, '∅')
CreateUISmallButton(c, '∅')
return c
end
end


function fieldHandlers.SyncRef(Container,Field)
function ColorToCss(color)
Container
if color['r'] then
:tag('div') -- HTML div to contain the field label
return  ("rgba(%s,%s,%s,%s)"):format(color["r"]*255, color["g"]*255, color["b"]*255, color["a"] or 1)
:attr('title', Field and (Field.Name .. ' <' .. Field.Type .. '>') or '') -- Add a basic mouseover description
end
:cssText('text-align: left; width:30%; overflow: hidden; text-overflow: ellipsis; padding-left: 4px;')
return ("rgba(%s,%s,%s,%s)"):format(color[1]*255, color[2]*255, color[3]*255, color[4] or 1)
:wikitext( (Field and Field.Name or '') .. ':' )
end
:done() -- Close field label div
 
CreateUISyncRefEditor(Container, 'null')
--- Returns a CSS color for the data type or impulse and whether the type is an impulse
-- @param connector Table containing a Name (Optional) and Type (Required) for the input/output.
function GetColor(connector)
if connector == nil then error("connector is nil") end
if connector == nil or connector.Type == nil then error("Missing Type") end
local color = ProtofluxColor.get_impulse_color(connector.Type)
if color ~= nil then
return color, true
end
return GetTypeColor(connector), false
end
 
--- Returns a CSS color for the data type
-- @param connector Table containing a Name (Optional) and Type (Required) for the input/output or global.
function GetTypeColor(connector)
if connector == nil then error("connector is nil") end
if connector == nil or connector.Type == nil then error("Missing Type") end
local color = ProtofluxColor.get_type_color(connector.Type)
return MultRGB(color, 1.5)
end
 
function MultRGB(color, fact)
return {
r=color.r*fact,
g=color.g*fact,
b=color.b*fact,
a=color.a,
}
end
end


function fieldHandlers.SyncAssetList(Container,Field)
function MultA(color, fact)
local c = Container
return {
:tag('div')
r=color.r,
:cssText('display: flex; flex-direction: column; flex-grow: 1;')
g=color.g,
:tag('div') -- HTML div to contain the field label
b=color.b,
:attr('title', Field and (Field.Name .. ' <' .. Field.Type .. '>') or '') -- Add a basic mouseover description
a=(color.a or 1)*fact,
:cssText('text-align: center;')
}
:wikitext( (Field and Field.Name or '') .. '(list):' )
:done() -- Close field label div
CreateUISyncRefEditor(c, 'null')
end
end


function fieldHandlers.AssetRef(Container,Field)
--- Creates a new input or output attachement point for a connector row.
Container
-- @param container The Scribunto HTML node we'll be creating this row inside of. Should be a container of some sort.
:tag('div') -- HTML div to contain the field label
-- @param hover_text Text to show when hovering, possibly nil
:attr('title', Field and (Field.Name .. ' <' .. Field.Type .. '>') or '') -- Add a basic mouseover description
-- @param color The color of this attachment point (table with r, g, b, optionally a).
:cssText('text-align: left; width:30%; overflow: hidden; text-overflow: ellipsis; padding-left: 4px;')
-- @param segments The number of segments for the type, used for vectors or matrices.
:wikitext( (Field and Field.Name or '') .. ':' )
-- @param is_impulse True if this is an impulse, false if it is a data type.
:done() -- Close field label div
-- @param is_input True if this is an input side attachment point, false if it is an output side attachment point.
Container
function CreateConnectorAttachmentPoint(container, hover_text, color, segments, is_impulse, is_input)
:tag('div')
local connector_type = nil
:cssText('display:flex; height: 1.5rem; flex-direction: row; align-items:stretch; flex-grow:1; gap: 4px;')
if is_impulse then connector_type = "InputArrow"
:tag('div')
elseif is_input then connector_type = "InputBox"
:cssText('width: 1.5rem; background-color: rgb(43, 46, 54); border-radius: 8px; flex-grow:0; display:flex; justify-content: center; align-items:center;')
else connector_type = "OutputBox"
:wikitext('⤴')
end
:done()
 
:tag('div')
container
:cssText('width: 1.5rem; background-color: rgb(43, 46, 54); border-radius: 8px; flex-grow:0; display:flex; justify-content: center; align-items:center;')
:tag( 'div' )
:wikitext('↑')
:attr('title', hover_text or "") -- Add a basic mouseover description
:done()
:cssText(css:attachment_point(color, is_input and 1 or 3))
:tag('div')
:wikitext(mw.getCurrentFrame():expandTemplate({title = 'ProtoFluxConnector', args = {Type=connector_type, Segments=segments, Height=20}}))
:cssText('border: 2px solid rgb(43, 46, 54); border-radius: 8px; background: rgb(30, 33, 38); flex-grow:1;')
:done()
:tag('div')
:cssText('width: 1.5rem; background-color: rgb(43, 46, 54); border-radius: 8px; flex-grow:0; display:flex; justify-content: center; align-items:center;')
:wikitext('')
:done()
end
end


--- Returns an RGBA value representing the type color within Resonite
local type_segments = {
-- @param Connector Table containing a Name (Optional) and Type (Required) for the input/output attachment point.
enabled = {
-- @param Alpha The alpha to use in the RGBA value.  
bool=true,
function GetTypeColor(Connector, Alpha)
byte=true,
return (Connector and 'rgba(' .. (typeColor[Connector.Type] or '0, 0, 0') .. ',' .. Alpha .. ')' or 'rgba(0, 0, 0, 0)')
sbyte=true,
ushort=true,
short=true,
uint=true,
int=true,
ulong=true,
long=true,
float=true,
double=true,
decimal=true,
},
suffix = {
["2"]=2,
["3"]=3,
["4"]=4,
--["2x2"]=2,
--["3x3"]=3,
--["4x4"]=4,
},
}
 
--- Returns a CSS color for the data type
-- @param connector Table containing a Name (Optional) and Type (Required) for the input/output.
function GetTypeSegments(connector)
if connector == nil then error("connector is nil") end
if connector == nil or connector.Type == nil then error("Missing Type") end
local base = connector.Type:match("^%a+")
if not type_segments.enabled[base] then return 1 end
local suffix = connector.Type:sub(#base+1)
return type_segments.suffix[suffix] or 1
end
end


return p
return p

Latest revision as of 11:03, 12 February 2024

The Text Component

Text
Duplicate
Destroy
persistent:
UpdateOrder:
0
Enabled:
Font:
FontChain on Root (ID16F00)
Content:
null
ParseRichText:
NullContent:
null
Size:
64
HorizontalAlign:
<<
Left
>>
VerticalAlign:
<<
Top
>>
AlignmentMode:
<<
Geometric
>>
Color:
R
0
G
0
B
0
A
1
Profile:
<<
sRGB
>>
Materials (list):
0:
UI_TextUnlitMaterial on Root (ID1A600)
X
Add
LineHeight:
0.8
MaskPattern:
null
HorizontalAutoSize:
VerticalAutoSize:
AutoSizeMin:
8
AutoSizeMax:
64
CaretPosition:
-1
SelectionStart:
-1
CaretColor:
R
0
G
0
B
0
A
0
Profile:
<<
Linear
>>
SelectionColor:
R
0
G
0
B
0
A
0
Profile:
<<
Linear
>>
interactionTarget:
_legacyFontMaterial:
null
_legacyAlign:
<<
TopLeft
>>

The RelativePositioner Component

RelativePositioner
Duplicate
Destroy
persistent:
UpdateOrder:
0
Enabled:
Reference:
null
ReferenceBoundsSpace:
LocalSpace:
null
UseParentSpace:
Default:
<<
WorldRoot
>>
OverrideRootSpace:
null
Use Global Space
Use Local Space
Use Parent Space
ReferenceAnchor:
x
0
y
0
z
0
ReferenceOffset:
x
0
y
0
z
0
DestroyAfterDone:
_target:
null

The AssetMultiplexer<ITexture2D> Component

AssetMultiplexer<ITexture2D>
Duplicate
Destroy
persistent:
UpdateOrder:
0
Enabled:
Target:
null
Index:
0
Assets (list):
0:
null
---
Clear
📋
X
1:
null
---
Clear
📋
X
2:
null
---
Clear
📋
X
Add

-- Package definition to return to Scribunto - this allows us to define methods that can be called
-- when this module is targeted.
local p = {}

local ProtofluxColor = require("Module:ProtoFlux_Type_Color")

local css = {
	attachment_point = function(self, color, order)
		return string.format(
			"height:20px; background-color: black; fill: %s; stroke: %s; order: %s;",
			ColorToCss(MultA(color, 0.15)),
			ColorToCss(color),
			order
		)
	end,
}

local fieldHandlers = {}

--- Method that generates the HTML output
--	@param frame A Scribunto frame instance. frame.args contains the parameters passed into the module call
function p.GenerateUI( frame )
	-- Parse the JSON input, returning an empty array if no argument was passed
	local fields = mw.text.jsonDecode(frame.args.Fields or '[]')
	
	-- Create an HTML div element to contain our component UI
	local componentContainer = mw.html.create( 'div' )
	componentContainer
		:attr('class', frame.args.Inline and '' or 'floatright')
		:cssText('color: rgb(224, 224, 224); background-color: rgb(18, 20, 28); width: 512px; display: flex; flex-direction: column; align-items: stretch; gap: 6px; padding: 8px;')
		:tag('div') -- HTML div to contain the component title and buttons
			:cssText('display:flex;gap:4px;')
			:tag('div') -- component title
				:cssText('color: #f8f770; /* Resonite Yellow */ border-radius: 8px; background-color: #2b2f35; /* Resonite Mid */ text-align:center; font-size: 1.25rem; font-weight: bold; flex-grow: 1;')
				:wikitext(frame.args.Name)
				:done() -- Close component title div
			:tag('div') -- duplicate button
				:cssText('background-color: #24512c; /* Resonite Sub-Green */ border-radius: 8px; width: 2.5rem; display: flex; align-items:center; justify-content:center;')
				:wikitext("[[File:Duplicate.svg|Duplicate|28px]]")
				:done()
			:tag('div') -- destroy button
				:cssText('background-color: #5d323a; /* Resonite Sub-Red */ border-radius: 8px; width: 2.5rem; display: flex; align-items:center; justify-content:center;')
				:wikitext("[[File:Destroy.svg|Destroy|28px]]")
				:done()
			:done() -- Close title and buttons div
	
	-- Iterate over each row, and populate it with the inputs/outputs. If the node is asymmetric,
	-- the value passed for either input or output might be nil.
	for i=1,#fields do
		CreateField(componentContainer, fields[i])
	end
			
	-- Return the HTML generated above to the wiki page this script is invoked from.
	return tostring(componentContainer) .. '[[Category:Components:All]]'
end

--- Creates a new Component field row in the output
-- @param Container The Scribunto HTML node we'll be creating this row inside of. Should be a container of some sort.
-- @param Field Table containing a Name, FieldType, and Type for the field on this row.
function CreateField(Container, Field)
	local fieldContentContainer = Container
		:tag('div') -- HTML div to contain the field.
			:cssText('display: flex;')
			:tag('div') -- HTML div to contain the field
				:cssText( 'flex-grow: 2; overflow: hidden; display: flex; flex-direction: row;')
	
	if fieldHandlers[Field.FieldType] == nil then
		fieldContentContainer:wikitext("ERROR: No handler for field type " .. Field.FieldType)
		return
	end

	fieldHandlers[Field.FieldType](fieldContentContainer, Field)
end

function fieldHandlers.Sync(Container, Field)
	CreateFieldLabel(Container,Field)
	CreateUISync(Container, Field)
end

function CreateUISync(Container, Field)
	if Field.Type == 'bool' then
		return CreateUICheckbox(Container, Field.Value)
	elseif Field.Type == 'int' or Field.Type == 'String' or Field.Type == 'float' or Field.Type == 'double' then
		return CreateUIInputBox(Container, Field.Value)
	elseif Field.Type == 'float2' or Field.Type == 'float3' then
		return CreateUIVector(Container, Field.Value)
	elseif Field.Enum then
		return CreateUIForwardBackwards(Container, Field.Value)
	elseif Field.Type == "colorX" then
		local c = Container
			:tag('div')
				:cssText('display:flex; flex-direction: column; align-items:stretch; flex-grow:1; gap: 4px;')
		CreateUIColor(c, Field.Value)

		local c2 = c:tag('div')
					:cssText('display:flex; flex-direction: row; align-items: stretch; flex-grow: 1; gap: 4px;')
					:tag('div')
						:cssText('display:flex; justify-content: center; align-items:center;')
						:wikitext('Profile:')
						:done()
		CreateUIForwardBackwards(c2, Field.Value[5])
		return c
	else
		return Container
			:wikitext( Field.FieldType .. '<' .. Field.Type .. '>')
	end
end

function fieldHandlers.SyncRef(Container,Field)
	CreateFieldLabel(Container,Field)
	CreateUISyncRefEditor(Container, Field.Value)
end

function fieldHandlers.AssetRef(Container,Field)
	CreateFieldLabel(Container,Field)
	CreateUIAssetRef(Container,Field)
end

function CreateUIAssetRef(Container,Field)
	if Field.Type == "ITexture2D" then
		local c = Container
			:tag('div')
				:cssText('display:flex;flex:1;gap:4px;')
		c:tag('div') -- texture
			:cssText('aspect-ratio:1;height:100%;background-color:white;')
			:done() -- close texture
		
		local c2 = c:tag('div') -- name and buttons
			:cssText('display:flex;flex-direction:column;flex:1;')
		
		c2:tag('div') -- name
			:cssText('text-align:center; font-style:italic;')
			:wikitext('null')
			:done()
		c2:tag('div') -- info
			:cssText('text-align:center;')
			:wikitext('---')
			:done()
		local buttons = c2:tag('div') -- buttons
			:cssText('display:flex;gap:4px;')

		CreateUIButton(buttons, "Clear", false, false, true)
		CreateUIButton(buttons, "⤴", false, false, true)
		CreateUIButton(buttons, "⧉", false, false, true)
		CreateUIButton(buttons, "📋", false, false, true)
		
		return c
	end
	return CreateUISyncRefEditor(Container, Field.Value)
end

function fieldHandlers.SyncAssetList(Container,Field)
	local c = Container
		:tag('div')
			:cssText('display: flex; flex-direction: column; flex-grow: 1; gap: 4px;')
			:tag('div') -- HTML div to contain the field label
				:attr('title', Field and (Field.Name .. ' ' .. Field.FieldType .. '<' .. Field.Type .. '>') or '') -- Add a basic mouseover description
				:cssText('text-align: center;')
				:wikitext( (Field and Field.Name or '') .. ' (list):' )
				:done() -- Close field label div
	
	for i=1,#Field.Value do
		local listElement = {Type=Field.Type,Value=Field.Value[i]}
		local c2 = c:tag('div'):cssText('display:flex;gap:4px;')
		c2:tag('div'):cssText("display:flex;align-items:center;justify-content:center;"):wikitext((i-1)..':')
		CreateUIAssetRef(c2, listElement)
		CreateUIButton(c2, 'X', false, true, false)
	end

	CreateUINormalButton(c, 'Add')
end

function fieldHandlers.SyncObject(Container, Field)
	Container = Container
		:tag('div')
			:cssText('display:flex; flex-direction:column;gap:6px;width:100%;')
	
	CreateFieldLabel(Container, Field)

	local c = Container
		:tag('div') -- Indented contianer
			:cssText('display:flex;flex:1;')

	c:tag('div') -- Black line
		:cssText('background-color:black;width:4px;margin: 0 6px;')
		:done() -- Close Black line

	fieldContainer = c:tag('div'):cssText("display:flex;flex-direction:column;gap:6px;flex:1;")
	
	for i=1,#Field.Fields do
		CreateField(fieldContainer, Field.Fields[i])
	end
end

function fieldHandlers.DelegateButton(Container, Field)
	CreateUIRefInputBox(Container, Field.Name)
end

function CreateFieldLabel(Container, Field)
    local hover_text =  Field and (Field.Name .. ' ' .. Field.FieldType .. '<' .. Field.Type .. '>') or ''
	local c = Container
		:tag('div')
			:attr('title', hover_text) -- Add a basic mouseover description
			:cssText("display:flex; width:30%; align-items:center;")
	
	local extrasButton = c
		:tag('div')
		:cssText('display:flex; align-items:center; justify-content:center; gap: 2px; align-self:stretch; width:32px; margin-right:2px; background-color: #2B2F35; /* Resonite Mid */ border-radius: 8px;')
	
	local color, is_impulse = GetColor(Field)
	local segments = GetTypeSegments(Field)

	CreateConnectorAttachmentPoint(extrasButton, hover_text, color, segments, false, true)
	
	extrasButton:tag('div') -- HTML div to contain the extras button
		:cssText('text-align:center; display:flex;align-items:center;justify-content:center;order:2;')
		:wikitext("☰")
		:done() -- Close extras button div
	
	local light = {r=0.71,g=0.75,b=0.81,a=1} -- Resonite Light
	
	local textColor = LerpColor(light, color, 0.1)

	c:tag('div') -- HTML div to contain the field label
		:cssText('text-align: left; overflow: hidden; text-overflow: ellipsis; flex: 1;color:' .. ColorToCss(textColor))
		:wikitext( (Field and Field.Name or '') .. ':' )
		:done() -- Close field label div

end

function LerpColor(from, to, lerp)
	return {r=Lerp(from['r'], to['r'], lerp),g=Lerp(from['g'], to['g'], lerp),b=Lerp(from['b'], to['b'], lerp),a=Lerp(from['a'] or 1, to['a'] or 1, lerp),}
end

function Lerp(from, to, lerp)
	return from * (1-lerp) + to * lerp
end

function CreateUIVector(Container, Vector)
	local c = Container
		:tag('div')
			:cssText('display:flex; height: 1.5rem; flex-direction: row; align-items:stretch; flex-grow:1; gap: 4px;')
	
	local letters = {"x", "y", "z", "w"}

	for i=1,#Vector do
		CreateUIBasicText(c,letters[i] or "?")
		CreateUIInputBox(c, Vector[1])
	end
end

function CreateUIColor(Container, Color)
	local ColorNoAlpha = {Color[1], Color[2], Color[3], 1}
	local c = Container
		:tag('div')
			:cssText('display:flex; height: 1.5rem; flex-direction: row; align-items:stretch; flex-grow:1; gap: 4px;')
	CreateUIBasicText(c, 'R')
	CreateUIInputBox(c, Color[1])
	CreateUIBasicText(c, 'G')
	CreateUIInputBox(c, Color[2])
	CreateUIBasicText(c, 'B')
	CreateUIInputBox(c, Color[3])
	CreateUIBasicText(c, 'A')
	CreateUIInputBox(c, Color[4])
	c:tag('div')
		:cssText('display:flex; height: 1.5rem; flex-direction: row; align-items:stretch; flex-grow:1;')
			:tag('div')
				:cssText('flex-grow: 1; background-color: ' .. ColorToCss(ColorNoAlpha) .. ';')
				:done()
			:tag('div')
				-- TODO: add the checkerboard here, I wish I could just use a background-image but mediawiki blocks those
				:cssText('flex-grow: 1; background-color: white;')
				:tag('div')
					:cssText('height: 100%; background-color: ' .. ColorToCss(Color) .. ';')
					:done()
				:done()
		:done()
end

function CreateUIBasicText(Container, Text)
	Container
		:wikitext(Text)
		:done()
	return Container;
end

function CreateUIForwardBackwards(Container, Text)
	local c = Container
		:tag('div')
			:cssText('display:flex; height: 1.5rem; flex-direction: row; align-items:stretch; flex-grow:1; gap: 4px;')
	CreateUISmallButton(c, '<<')
		:tag('div')
			:cssText('border-radius: 8px; background: rgb(8, 8, 10); flex-grow:1; display:flex; justify-content: center; align-items:center;')
			:wikitext(Text)
			:done()
	CreateUISmallButton(c, '>>')
	return Container
end

function CreateUICheckbox(Container, Checked)
	local c = Container
		:tag('div')
			:cssText('display:flex; align-items:center;box-sizing: border-box; background-color: #2b2f35; /* Resonite Mid */ border-radius: 8px; width: 1.5rem; height: 1.5rem; padding: 2px;')
	if Checked then
		c:wikitext('[[File:Checkmark.png|20px]]')
	end
end

function CreateUISmallButton(Container, Text)
	return CreateUIButton(Container, Text, true, true, false)
end

function CreateUINormalButton(Container, Text)
	return CreateUIButton(Container, Text, true, false, false)
end

function CreateUIButton(Container, Text, LimitHeight, LimitWidth, Flex)
	local grow = (not(LimitHeight and LimitWidth) and 'flex-grow:1;' or '')
	Container
		:tag('div')
			:cssText('display:flex; flex-direction: column; align-items:stretch; justify-content: center;' .. (Flex and 'flex:1;' or ''))
			:tag('div')
				:cssText('background-color: rgb(43, 46, 54); border-radius: 8px; display:flex; justify-content: center; align-items:center;' .. (LimitHeight and 'height:1.5rem;' or '') .. (LimitWidth and 'width:1.5rem;' or '') .. grow)
				:wikitext(Text)
				:done()
	return Container
end

function CreateUIInputBox(Container, Text)
	local Italics = false
	if not Text then
		Text = 'null'
		Italics = true
	end
	
	Container
		:tag('div')
			:cssText('box-sizing: border-box; height: 1.5rem; border: 3px solid rgb(43, 46, 54); border-radius: 8px; background: rgb(30, 33, 38); flex-grow:1; display:flex; justify-content: center; align-items:center;'  ..  (Italics and 'font-style:italic;' or ''))
			:wikitext(Text)
			:done()
end

function CreateUIRefInputBox(Container, Text)
	local Italics = false
	if not Text then
		Text = 'null'
		Italics = true
	end
	
	Container
		:tag('div')
			:cssText('height: 1.5rem; background: rgb(43, 46, 54); border-radius: 8px; flex-grow:1; display:flex; justify-content: center; align-items:center;' ..  (Italics and 'font-style:italic;' or ''))
			:wikitext(Text)
			:done()
	return Container
end

function CreateUISyncRefEditor(Container, Text)
	local c = Container
	:tag('div')
		:cssText('display:flex; flex-direction: row; align-items:stretch; flex-grow:1; gap: 4px;')
	
	CreateUISmallButton(c, '⤴')
	CreateUISmallButton(c, '↑')
	CreateUIRefInputBox(c, Text)
	CreateUISmallButton(c, '∅')

	return c
end

function ColorToCss(color)
	if color['r'] then
		return  ("rgba(%s,%s,%s,%s)"):format(color["r"]*255, color["g"]*255, color["b"]*255, color["a"] or 1)
	end
	return ("rgba(%s,%s,%s,%s)"):format(color[1]*255, color[2]*255, color[3]*255, color[4] or 1)
end

--- Returns a CSS color for the data type or impulse and whether the type is an impulse
-- @param connector Table containing a Name (Optional) and Type (Required) for the input/output.
function GetColor(connector)
	if connector == nil then error("connector is nil") end
	if connector == nil or connector.Type == nil then error("Missing Type") end
	local color = ProtofluxColor.get_impulse_color(connector.Type)
	if color ~= nil then
		return color, true
	end
	return GetTypeColor(connector), false
end

--- Returns a CSS color for the data type
-- @param connector Table containing a Name (Optional) and Type (Required) for the input/output or global.
function GetTypeColor(connector)
	if connector == nil then error("connector is nil") end
	if connector == nil or connector.Type == nil then error("Missing Type") end
	local color = ProtofluxColor.get_type_color(connector.Type)
	return MultRGB(color, 1.5)
end

function MultRGB(color, fact)
	return {
		r=color.r*fact,
		g=color.g*fact,
		b=color.b*fact,
		a=color.a,
	}
end

function MultA(color, fact)
	return {
		r=color.r,
		g=color.g,
		b=color.b,
		a=(color.a or 1)*fact,
	}
end

--- Creates a new input or output attachement point for a connector row.
--	@param container The Scribunto HTML node we'll be creating this row inside of. Should be a container of some sort.
--  @param hover_text Text to show when hovering, possibly nil
--	@param color The color of this attachment point (table with r, g, b, optionally a).
--	@param segments The number of segments for the type, used for vectors or matrices.
--	@param is_impulse True if this is an impulse, false if it is a data type.
--	@param is_input True if this is an input side attachment point, false if it is an output side attachment point.
function CreateConnectorAttachmentPoint(container, hover_text, color, segments, is_impulse, is_input)
	local connector_type = nil
	if is_impulse then connector_type = "InputArrow"
	elseif is_input then connector_type = "InputBox"
	else connector_type = "OutputBox"
	end

	container
		:tag( 'div' )
			:attr('title', hover_text or "") -- Add a basic mouseover description
			:cssText(css:attachment_point(color, is_input and 1 or 3))
			:wikitext(mw.getCurrentFrame():expandTemplate({title = 'ProtoFluxConnector', args = {Type=connector_type, Segments=segments, Height=20}}))
end

local type_segments = {
	enabled = {
		bool=true,
		byte=true,
		sbyte=true,
		ushort=true,
		short=true,
		uint=true,
		int=true,
		ulong=true,
		long=true,
		float=true,
		double=true,
		decimal=true,
	},
	suffix = {
		["2"]=2,
		["3"]=3,
		["4"]=4,
		--["2x2"]=2,
		--["3x3"]=3,
		--["4x4"]=4,
	},
}

--- Returns a CSS color for the data type
-- @param connector Table containing a Name (Optional) and Type (Required) for the input/output.
function GetTypeSegments(connector)
	if connector == nil then error("connector is nil") end
	if connector == nil or connector.Type == nil then error("Missing Type") end
	local base = connector.Type:match("^%a+")
	if not type_segments.enabled[base] then return 1 end
	local suffix = connector.Type:sub(#base+1)
	return type_segments.suffix[suffix] or 1
end

return p