Module:Test2

From Resonite Wiki

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