mNo edit summary |
No edit summary |
||
(121 intermediate revisions by 2 users not shown) | |||
Line 3: | Line 3: | ||
local p = {} | local p = {} | ||
local | local ProtofluxColor = require("Module:ProtoFlux_Type_Color") | ||
local css = { | |||
local | 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 | --- Method that generates the HTML output | ||
Line 36: | Line 27: | ||
local componentContainer = mw.html.create( 'div' ) | local componentContainer = mw.html.create( 'div' ) | ||
componentContainer | 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: | :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(' | :cssText('display:flex;gap:4px;') | ||
:tag('div') -- component title | |||
:done() -- Close | :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 58: | Line 60: | ||
function CreateField(Container, Field) | function CreateField(Container, Field) | ||
local fieldContentContainer = Container | local fieldContentContainer = Container | ||
:tag('div') -- HTML div to contain the field | :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) | ||
if Field.Type == ' | CreateFieldLabel(Container,Field) | ||
Container | 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') | :tag('div') | ||
:cssText('display:flex; flex-direction: column; align-items: | :cssText('display:flex; flex-direction: column; align-items:stretch; flex-grow:1; gap: 4px;') | ||
CreateUIColor(c, Field.Value) | |||
:cssText(' | |||
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 | else | ||
Container | return Container | ||
:wikitext( Field.FieldType .. '<' .. Field.Type .. '>') | :wikitext( Field.FieldType .. '<' .. Field.Type .. '>') | ||
end | end | ||
Line 90: | Line 108: | ||
function fieldHandlers.SyncRef(Container,Field) | function fieldHandlers.SyncRef(Container,Field) | ||
Container | CreateFieldLabel(Container,Field) | ||
:tag('div') | CreateUISyncRefEditor(Container, Field.Value) | ||
end | |||
:tag('div') | |||
:cssText(' | function fieldHandlers.AssetRef(Container,Field) | ||
:wikitext(' | 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() | :done() | ||
:tag('div') | c2:tag('div') -- info | ||
:cssText(' | :cssText('text-align:center;') | ||
:wikitext(' | :wikitext('---') | ||
:done() | :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') | :tag('div') | ||
:cssText(' | :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') | :tag('div') | ||
:cssText(' | :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 | end | ||
function | function CreateFieldLabel(Container, Field) | ||
local hover_text = Field and (Field.Name .. ' ' .. Field.FieldType .. '<' .. Field.Type .. '>') or '' | |||
local c = Container | |||
:tag('div') | :tag('div') | ||
: | :attr('title', hover_text) -- Add a basic mouseover description | ||
:cssText("display:flex; width:30%; align-items:center;") | |||
local extrasButton = c | |||
:tag('div') | :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') | :tag('div') | ||
:cssText(' | :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') | :tag('div') | ||
:cssText(' | :cssText('display:flex; height: 1.5rem; flex-direction: row; align-items:stretch; flex-grow:1; gap: 4px;') | ||
: | CreateUIBasicText(c, 'R') | ||
:done() | 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 | end | ||
function | function CreateUIBasicText(Container, Text) | ||
Container | Container | ||
:tag('div') | :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') | :tag('div') | ||
:cssText(' | :cssText('border-radius: 8px; background: rgb(8, 8, 10); flex-grow:1; display:flex; justify-content: center; align-items:center;') | ||
:wikitext( | :wikitext(Text) | ||
:done() | :done() | ||
CreateUISmallButton(c, '>>') | |||
return Container | |||
end | |||
function CreateUICheckbox(Container, Checked) | |||
local c = Container | |||
:tag('div') | :tag('div') | ||
:cssText('width: 1.5rem; background-color: rgb(43, 46, 54); border-radius: 8px | :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') | :tag('div') | ||
:cssText('border: | :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() | :done() | ||
end | |||
function CreateUIRefInputBox(Container, Text) | |||
local Italics = false | |||
if not Text then | |||
Text = 'null' | |||
Italics = true | |||
end | |||
Container | |||
:tag('div') | :tag('div') | ||
:cssText(' | :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( | :wikitext(Text) | ||
:done() | :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 | end | ||
--- Returns | --- Returns a CSS color for the data type or impulse and whether the type is an impulse | ||
-- @param | -- @param connector Table containing a Name (Optional) and Type (Required) for the input/output. | ||
-- @param | function GetColor(connector) | ||
function GetTypeColor( | if connector == nil then error("connector is nil") end | ||
return ( | 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 | end | ||
return p | return p |
Latest revision as of 11:03, 12 February 2024
The Text Component
The RelativePositioner Component
The AssetMultiplexer<ITexture2D> Component
-- 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