Module:Test2: Difference between revisions

From Resonite Wiki
No edit summary
No edit summary
 
(18 intermediate revisions by the same user not shown)
Line 8: Line 8:
attachment_point = function(self, color, order)
attachment_point = function(self, color, order)
return string.format(
return string.format(
"width: 30px; background-color: black; fill: %s; stroke: %s; order: %s;",
"height:20px; background-color: black; fill: %s; stroke: %s; order: %s;",
ColorToCss(MultA(color, 0.15)),
ColorToCss(MultA(color, 0.15)),
ColorToCss(color),
ColorToCss(color),
Line 36: Line 36:
:done() -- Close component title div
:done() -- Close component title div
:tag('div') -- duplicate button
:tag('div') -- duplicate button
:cssText('background-color: #3f9e44; /* Resonite Sub-Green */ border-radius: 8px; width: 2.5rem; display: flex; align-items:center; justify-content:center;')
: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]]")
:wikitext("[[File:Duplicate.svg|Duplicate|28px]]")
:done()
:done()
:tag('div') -- destroy button
:tag('div') -- destroy button
:cssText('background-color: #ae5458; /* Resonite Sub-Red */ border-radius: 8px; width: 2.5rem; display: flex; align-items:center; justify-content:center;')
: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]]")
:wikitext("[[File:Destroy.svg|Destroy|28px]]")
:done()
:done()
Line 72: Line 72:
fieldHandlers[Field.FieldType](fieldContentContainer, Field)
fieldHandlers[Field.FieldType](fieldContentContainer, Field)
end
end
local enumTypes = {
["TextHorizontalAlignment"] = true,
["TextVerticalAlignment"] = true,
["Elements.Assets.AlignmentMode"] = true,
["Alignment"] = true,
["DefaultSpace"] = true
}


function fieldHandlers.Sync(Container, Field)
function fieldHandlers.Sync(Container, Field)
Line 93: Line 85:
elseif Field.Type == 'float2' or Field.Type == 'float3' then
elseif Field.Type == 'float2' or Field.Type == 'float3' then
return CreateUIVector(Container, Field.Value)
return CreateUIVector(Container, Field.Value)
elseif enumTypes[Field.Type] then
elseif Field.Enum then
return CreateUIForwardBackwards(Container, Field.Value)
return CreateUIForwardBackwards(Container, Field.Value)
elseif Field.Type == "colorX" then
elseif Field.Type == "colorX" then
Line 129: Line 121:
local c = Container
local c = Container
:tag('div')
:tag('div')
:cssText('display:flex;flex:1;')
:cssText('display:flex;flex:1;gap:4px;')
c:tag('div') -- texture
c:tag('div') -- texture
:cssText('aspect-ratio:1;height:100%;background-color:white;')
:cssText('aspect-ratio:1;height:100%;background-color:white;')
Line 135: Line 127:
local c2 = c:tag('div') -- name and buttons
local c2 = c:tag('div') -- name and buttons
:cssText('display:flex;flex-direction:column;')
:cssText('display:flex;flex-direction:column;flex:1;')
c2:tag('div') -- name
c2:tag('div') -- name
Line 146: Line 138:
:done()
:done()
local buttons = c2:tag('div') -- buttons
local buttons = c2:tag('div') -- buttons
:cssText('display:flex;')
:cssText('display:flex;gap:4px;')


CreateUIButton(buttons, "Clear", false, false)
CreateUIButton(buttons, "Clear", false, false, true)
CreateUIButton(buttons, "Clear", false, false)
CreateUIButton(buttons, "", false, false, true)
CreateUIButton(buttons, "Clear", false, false)
CreateUIButton(buttons, "", false, false, true)
CreateUIButton(buttons, "Clear", false, false)
CreateUIButton(buttons, "📋", false, false, true)
return c
return c
Line 173: Line 165:
c2:tag('div'):cssText("display:flex;align-items:center;justify-content:center;"):wikitext((i-1)..':')
c2:tag('div'):cssText("display:flex;align-items:center;justify-content:center;"):wikitext((i-1)..':')
CreateUIAssetRef(c2, listElement)
CreateUIAssetRef(c2, listElement)
CreateUISmallButton(c2, 'X')
CreateUIButton(c2, 'X', false, true, false)
end
end


Line 206: Line 198:


function CreateFieldLabel(Container, Field)
function CreateFieldLabel(Container, Field)
    local hover_text =  Field and (Field.Name .. ' ' .. Field.FieldType .. '<' .. Field.Type .. '>') or ''
local c = Container
local c = Container
:tag('div')
:tag('div')
:attr('title', Field and (Field.Name .. ' ' .. Field.FieldType .. '<' .. Field.Type .. '>') or '') -- Add a basic mouseover description
:attr('title', hover_text) -- Add a basic mouseover description
:cssText("display:flex; width:30%; align-items:center;")
:cssText("display:flex; width:30%; align-items:center;")
local extrasButton = c
local extrasButton = c
:tag('div')
:tag('div')
:cssText('display:flex; gap: 2px; align-self:stretch; width:32px; margin-right:2px; background-color: #2B2F35; /* Resonite Mid */ border-radius: 8px;')
: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 color, is_impulse = GetColor(Field)
local segments = GetTypeSegments(Field)
local segments = GetTypeSegments(Field)
-- TODO: it's way too big and breaks everything, but not sure what to do about this
 
--CreateConnectorAttachmentPoint(extrasButton, "hover text", color, segments, false, true)
CreateConnectorAttachmentPoint(extrasButton, hover_text, color, segments, false, true)
extrasButton:tag('div') -- HTML div to contain the extras button
extrasButton:tag('div') -- HTML div to contain the extras button
:cssText('flex:1;text-align:center; display:flex;align-items:center;justify-content:center;order:2;')
:cssText('text-align:center; display:flex;align-items:center;justify-content:center;order:2;')
:wikitext("☰")
:wikitext("☰")
:done() -- Close extras button div
:done() -- Close extras button div
Line 276: Line 269:
:done()
:done()
:tag('div')
:tag('div')
:cssText('flex-grow: 1; background-color: ' .. ColorToCss(Color) .. ';')
-- 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()
:done()
:done()
Line 304: Line 301:
local c = Container
local c = Container
:tag('div')
:tag('div')
:cssText('box-sizing: border-box; background-color: #2b2f35; /* Resonite Mid */ border-radius: 8px; width: 1.5rem; height: 1.5rem; padding: 2px;')
: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
if Checked then
c:wikitext('[[File:Checkmark.png|checkmark|20px]]')
c:wikitext('[[File:Checkmark.png|20px]]')
end
end
end
end


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


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


function CreateUIButton(Container, Text, LimitHeight, LimitWidth)
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('display:flex; flex-direction: column; align-items:stretch; justify-content: center;')
:cssText('display:flex; flex-direction: column; align-items:stretch; justify-content: center;' .. (Flex and 'flex:1;' or ''))
:tag('div')
:tag('div')
:cssText('background-color: rgb(43, 46, 54); border-radius: 8px; flex-grow:0; display:flex; justify-content: center; align-items:center;' .. (LimitHeight and 'height:1.5rem;' or '') .. (LimitWidth and 'width:1.5rem;' or ''))
: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)
:wikitext(Text)
:done()
:done()
Line 435: Line 433:
:attr('title', hover_text or "") -- Add a basic mouseover description
:attr('title', hover_text or "") -- Add a basic mouseover description
:cssText(css:attachment_point(color, is_input and 1 or 3))
:cssText(css:attachment_point(color, is_input and 1 or 3))
:wikitext(mw.getCurrentFrame():expandTemplate({title = 'ProtoFluxConnector', args = {Type=connector_type, Segments=segments}}))
:wikitext(mw.getCurrentFrame():expandTemplate({title = 'ProtoFluxConnector', args = {Type=connector_type, Segments=segments, Height=20}}))
end
end



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