Merge pull request #104 from argonui/optionpanel

Option Panel: Part 3 - Addition of fan made accessories options & code improvement
This commit is contained in:
Chr1Z 2022-12-19 23:09:32 +01:00 committed by GitHub
commit adbb590e36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 218 additions and 163 deletions

View File

@ -22,7 +22,7 @@
},
"Lighting_path": "Lighting.json",
"LuaScript": "require(\"core/Global\")",
"LuaScriptState": "{\"optionPanel\":[false,false,false,false,false,false,false,false]}",
"LuaScriptState": "{\"optionPanel\":{\"useSnapTags\":true,\"showDrawButton\":false,\"useClueClickers\":false,\"showTokenArranger\":false,\"showCleanUpHelper\":false,\"showHandHelper\":false}}",
"MusicPlayer_path": "MusicPlayer.json",
"Note": "",
"ObjectStates_order": [

View File

@ -1,11 +1,3 @@
-- Chaos Bag Manager
-- made by: Chr1Z
-- description: for easier managing of the chaos bag (adding / removing tokens)
information = {
version = "1.5",
last_updated = "24.11.2022"
}
local TOKEN_URL = {
ElderSign = "https://i.imgur.com/nEmqjmj.png",
plusOne = "https://i.imgur.com/uIx8jbY.png",
@ -61,7 +53,7 @@ buttonParameters.width = 300
buttonParameters.height = 300
local UPDATING = false
local tokenarranger
local tokenArranger
local name
local tokens = {}
@ -87,11 +79,10 @@ function onLoad()
self.addContextMenuItem("More Information", function()
printToAll("------------------------------", "White")
printToAll("Chaos Bag Manager v" .. information["version"] .. " by Chr1Z", "Orange")
printToAll("last updated: " .. information["last_updated"], "White")
printToAll("Chaos Bag Manager by Chr1Z", "Orange")
end)
tokenarranger = getObjectFromGUID("022907")
tokenArranger = getObjectFromGUID("022907")
end
-- get chaos bag from scripting zone and description
@ -182,11 +173,11 @@ function spawnCallback(obj)
end
function updateTokenArranger()
if tokenarranger and not UPDATING then
if tokenArranger and not UPDATING then
UPDATING = true
Wait.time(function()
UPDATING = false
tokenarranger.call("layout")
tokenArranger.call("layout")
end, 1.5)
end
end

View File

@ -1,12 +1,3 @@
-- Hand Helper
-- updated by: Chr1Z
-- original by: -
-- description: counts cards in your hand (all or unique), can discard a random card
information = {
version = "1.2",
last_updated = "11.10.2022"
}
MAT_GUIDS = { "8b081b", "bd0ff4", "383d8b", "0840d5" }
local BUTTON_PARAMETERS = {}
@ -69,8 +60,7 @@ function onLoad(saved_data)
-- context menu to display additional information
self.addContextMenuItem("More Information", function()
printToAll("------------------------------", "White")
printToAll("Hand Helper v" .. information["version"] .. " by Chr1Z", "Orange")
printToAll("last updated: " .. information["last_updated"], "White")
printToAll("Hand Helper by Chr1Z", "Orange")
printToAll("original by Tikatoy", "White")
printToAll("Note: 'Hidden' cards can't be randomly discarded.", "Yellow")
printToAll("Set them aside beforehand!", "Yellow")
@ -135,6 +125,11 @@ function updateValue()
self.editButton({index = 0, font_color = des and "Green" or "White", label = size})
end
-- allows change of color via external call
function externalColorChange(newColor)
changeColor(_, _, _, newColor)
end
-- get index of current color and move up one step (or down for right-click)
function changeColor(_, _, isRightClick, color)
if color then

View File

@ -1 +1 @@
{"":[0,11],"Auto-fail":[-100,7],"Bless":[101,8],"Cultist":[-2,4],"Curse":[-100,9],"Elder Sign":[100,2],"Elder Thing":[-4,6],"Frost":[-99,10],"Skull":[-1,3],"Tablet":[-3,5]}
{"":[0,11],"Auto-fail":[-100,7],"Bless":[101,8],"Cultist":[-2,4],"Curse":[-101,9],"Elder Sign":[100,2],"Elder Thing":[-4,6],"Frost":[-99,10],"Skull":[-1,3],"Tablet":[-3,5]}

View File

@ -1,14 +1,5 @@
-- Token Arranger
-- created by: Chr1Z
-- original by: Whimsical
-- description: displays the content of the chaos bag
information = {
version = "1.7",
last_updated = "13.11.2022"
}
-- names of tokens in order
local token_names = {
local TOKEN_NAMES = {
"Elder Sign",
"Skull",
"Cultist",
@ -23,7 +14,7 @@ local token_names = {
-- token modifiers for sorting (and order for same modifier)
-- order starts at 2 because there is a "+1" token
local token_precedence = {
local TOKEN_PRECEDENCE = {
["Elder Sign"] = { 100, 2 },
["Skull"] = { -1, 3 },
["Cultist"] = { -2, 4 },
@ -54,16 +45,13 @@ inputParameters.alignment = 3
inputParameters.validation = 2
inputParameters.tab = 2
-- tag for cloned tokens
TO_DELETE_TAG = "to_be_deleted"
updating = false
function onSave() return JSON.encode(token_precedence) end
function onSave() return JSON.encode(TOKEN_PRECEDENCE) end
function onLoad(save_state)
if save_state ~= nil then
token_precedence = JSON.decode(save_state)
TOKEN_PRECEDENCE = JSON.decode(save_state)
end
-- create UI
@ -82,7 +70,7 @@ function onLoad(save_state)
buttonParameters.click_function = attachIndex("tokenClick", i)
inputParameters.input_function = attachIndex2("tokenInput", i)
inputParameters.value = token_precedence[token_names[i]][1]
inputParameters.value = TOKEN_PRECEDENCE[TOKEN_NAMES[i]][1]
self.createButton(buttonParameters)
self.createInput(inputParameters)
@ -100,27 +88,29 @@ function onLoad(save_state)
self.addContextMenuItem("More Information", function()
printToAll("------------------------------", "White")
printToAll("Token Arranger v" .. information["version"] .. " by Chr1Z", "Orange")
printToAll("last updated: " .. information["last_updated"], "White")
printToAll("Token Arranger by Chr1Z", "Orange")
printToAll("original concept by Whimsical", "White")
end)
-- send object reference to bless/curse manager
Wait.time(function()
getObjectFromGUID("5933fb").setVar("tokenArranger", self)
end, 1)
Wait.time(function() getObjectFromGUID("5933fb").setVar("tokenArranger", self) end, 1)
end
function onDestroy()
deleteCopiedTokens()
-- remove object reference from bless/curse manager
getObjectFromGUID("5933fb").setVar("tokenArranger", nil)
end
function onPickUp()
deleteCopiedTokens()
end
-- helper functions to carry index
function attachIndex(click_function, index)
local fn_name = click_function .. index
_G[fn_name] = function(obj, player_color, alt_click)
_G[click_function](obj, player_color, alt_click, index)
_G[fn_name] = function(obj, player_color, isRightClick)
_G[click_function](obj, player_color, isRightClick, index)
end
return fn_name
end
@ -134,26 +124,23 @@ function attachIndex2(input_function, index)
end
-- click_function for buttons on chaos tokens
function tokenClick(obj, player_color, alt_click, index)
function tokenClick(_, _, isRightClick, index)
if not updating then
updating = true
if alt_click then
token_precedence[token_names[index]][1] = token_precedence[token_names[index]][1] - 1
else
token_precedence[token_names[index]][1] = token_precedence[token_names[index]][1] + 1
end
self.editInput({ index = index - 1, value = token_precedence[token_names[index]][1] })
local change = tonumber(isRightClick and "-1" or "1")
TOKEN_PRECEDENCE[TOKEN_NAMES[index]][1] = TOKEN_PRECEDENCE[TOKEN_NAMES[index]][1] + change
self.editInput({ index = index - 1, value = TOKEN_PRECEDENCE[TOKEN_NAMES[index]][1] })
layout()
end
end
-- input_function for input_boxes
function tokenInput(obj, player_color, input, selected, index)
function tokenInput(_, _, input, selected, index)
if selected == false and not updating then
updating = true
local num = tonumber(input)
if num ~= nil then
token_precedence[token_names[index]][1] = num
TOKEN_PRECEDENCE[TOKEN_NAMES[index]][1] = num
end
layout()
end
@ -194,10 +181,14 @@ function getChaosBag()
return chaosbag
end
-- deletes previously placed tokens
function deleteCopiedTokens()
for _, token in ipairs(getObjectsWithTag("to_be_deleted")) do token.destruct() end
end
-- main function (delete old tokens, clone chaos bag content, sort it and position it)
function layout(_, _, isRightClick)
-- delete previously pulled out tokens
for _, token in ipairs(getObjectsWithTag(TO_DELETE_TAG)) do token.destruct() end
deleteCopiedTokens()
-- stop here if right-clicked
if isRightClick then return end
@ -212,25 +203,24 @@ function layout(_, _, isRightClick)
smooth = false,
callback_function = function(tok)
chaos_bag.putObject(tok.clone())
tok.addTag(TO_DELETE_TAG)
tok.addTag("to_be_deleted")
end
}
end
-- wait until all tokens have finished spawning
Wait.condition(function() do_position() end,
function() return #chaos_bag_objects == #getObjectsWithTag(TO_DELETE_TAG) end)
Wait.condition(function() placeTokens() end, function() return #chaos_bag_objects == #getObjectsWithTag("to_be_deleted") end)
end
-- position tokens sorted by value
function do_position()
function placeTokens()
local data = {}
-- create table with tokens
for i, token in ipairs(getObjectsWithTag(TO_DELETE_TAG)) do
for i, token in ipairs(getObjectsWithTag("to_be_deleted")) do
local name = token.getName() or ""
local value = tonumber(name)
local precedence = token_precedence[name]
local precedence = TOKEN_PRECEDENCE[name]
data[i] = {
token = token,

View File

@ -11,6 +11,9 @@ optionPanel = {}
-- GUID of data helper
DATA_HELPER_GUID = "708279"
-- GUID of fan-made accessories bag (also just called "barrel")
BARREL_GUID = "aa8b38"
-- GUIDs that will not be interactable (e.g. parts of the table)
local NOT_INTERACTABLE = {
"6161b4",
@ -634,52 +637,6 @@ function onClick_load()
UI.hide('load_button')
end
function onClick_defaultSettings()
print("Dummy: Load default settings")
end
function onClick_toggleOption(_, id)
local state = self.UI.getAttribute("toggle" .. id, "isOn")
-- flip state (and handle stupid "False" value)
if state == "False" then
state = true
else
state = false
end
self.UI.setAttribute("toggle" .. id, "isOn", state)
id = tonumber(id)
optionPanel[id] = state
applyOptionPanelChange(id, state)
end
-- sets the option panel to the correct state (corresponding to 'optionPanel')
function updateOptionPanelState()
for id, enabled in pairs(optionPanel) do
if enabled then self.UI.setAttribute("toggle" .. id, "isOn", true) end
end
end
function applyOptionPanelChange(id, state)
-- option 1: Snap tags
if id == 1 then
playmatAPI.setLimitSnapsByType(state, "All")
-- option 2: Draw 1 button
elseif id == 2 then
playmatAPI.showDrawButton(state, "All")
-- option 3: Clickable clue counters
elseif id == 3 then
playmatAPI.clickableClues(state, "All")
-- update master clue counter
getObjectFromGUID("4a3aa4").setVar("useClickableCounters", state)
end
end
function onClick_toggleUi(_, title)
UI.hide('optionPanel')
UI.hide('load_ui')
@ -825,3 +782,142 @@ function urldecode(str)
function (h) return string.char(tonumber(h, 16)) end)
return str
end
---------------------------------------------------------
-- Option Panel related functionality
---------------------------------------------------------
-- loads the default options
function onClick_defaultSettings()
print("Dummy: Load default settings")
end
-- called by toggling an option
function onClick_toggleOption(_, id)
local state = self.UI.getAttribute(id, "isOn")
-- flip state (and handle stupid "False" value)
if state == "False" then
state = true
else
state = false
end
self.UI.setAttribute(id, "isOn", state)
optionPanel[id] = state
applyOptionPanelChange(id, state)
end
-- sets the option panel to the correct state (corresponding to 'optionPanel')
function updateOptionPanelState()
for id, enabled in pairs(optionPanel) do
if enabled then
self.UI.setAttribute(id, "isOn", true)
end
end
end
-- handles the applying of option selections and calls the respective functions based
---@param id String ID of the option that was selected or deselected
---@param state Boolean State of the option (true = enabled)
function applyOptionPanelChange(id, state)
-- option: Snap tags
if id == "useSnapTags" then
playmatAPI.setLimitSnapsByType(state, "All")
-- option: Draw 1 button
elseif id == "showDrawButton" then
playmatAPI.showDrawButton(state, "All")
-- option: Clickable clue counters
elseif id == "useClueClickers" then
playmatAPI.clickableClues(state, "All")
-- update master clue counter
getObjectFromGUID("4a3aa4").setVar("useClickableCounters", state)
-- option: Show token arranger
elseif id == "showTokenArranger" then
-- delete previously pulled out tokens
for _, token in ipairs(getObjectsWithTag("to_be_deleted")) do token.destruct() end
spawnOrRemoveHelper(state, "Token Arranger", {-42.3, 1.4, -46.5})
-- option: Show clean up helper
elseif id == "showCleanUpHelper" then
spawnOrRemoveHelper(state, "Clean Up Helper", {-68, 1.6, 35.5})
-- option: Show hand helper for each player
elseif id == "showHandHelper" then
spawnOrRemoveHelper(state, "Hand Helper", {-50.84, 1.6, 7.02}, {0, 270, 0}, "White")
spawnOrRemoveHelper(state, "Hand Helper", {-50.90, 1.6, -25.10}, {0, 270, 0}, "Orange")
spawnOrRemoveHelper(state, "Hand Helper", {-34.38, 1.6, 22.44}, {0, 000, 0}, "Green")
spawnOrRemoveHelper(state, "Hand Helper", {-16.69, 1.6, -22.42}, {0, 180, 0}, "Red")
-- option: Show chaos bag manager
elseif id == "showChaosBagManager" then
spawnOrRemoveHelper(state, "Chaos Bag Manager", {-67.8, 1.4, -49.5})
end
end
-- handler for spawn / remove functions of helper objects
---@param state Boolean Contains the state of the option: true = spawn it, false = remove it
---@param name String Name of the helper object
---@param position Vector Position of the object (where it will spawn or where it will be removed from)
---@param rotation Vector Rotation of the object for spawning (default: {0, 270, 0})
---@param color Color This is only needed for correctly setting the color of the "Hand Helper"
function spawnOrRemoveHelper(state, name, position, rotation, color)
if state then
spawnHelperObject(name, position, rotation, color)
Player["White"].pingTable(position)
else
removeHelperObject(name, position)
end
end
-- copies the specified tool (by name) from the barrel
---@param name String Name of the object that should be copied
---@param position Position Desired position of the object
function spawnHelperObject(name, position, rotation, color)
if rotation == nil then rotation = {0, 270, 0} end
for _, obj in ipairs(getObjectFromGUID(BARREL_GUID).getData().ContainedObjects) do
if obj["Nickname"] == name then
spawnObjectData({
data = obj,
position = position,
rotation = rotation,
callback_function = function(object)
if name == "Hand Helper" then
Wait.time(function() object.call("externalColorChange", color) end, 1)
elseif name == "Token Arranger" then
Wait.time(function() object.call("layout") end, 1)
end
end
})
return
end
end
end
-- removes the specified tool (by name) from the provided position
---@param name String Name of the object that should be removed
---@param position Position Position of the object
function removeHelperObject(name, position)
local search = Physics.cast({
direction = { 0, 1, 0 },
max_distance = 1,
type = 3,
size = {1, 1, 1},
origin = position,
orientation = { 0, 270, 0 }
})
for _, obj in ipairs(search) do
obj = obj.hit_object
if obj.getName() == name then
obj.destruct()
return
end
end
end

View File

@ -174,9 +174,7 @@ end
function doReset(color)
-- delete previously pulled out tokens by the token arranger
if tokenArranger then
for _, token in ipairs(getObjectsWithTag(tokenArranger.getVar("TO_DELETE_TAG"))) do
token.destruct()
end
tokenArranger.call("deleteCopiedTokens")
end
playerColor = color

View File

@ -3,14 +3,14 @@
<Text color="white" alignment="MiddleLeft"/>
<Toggle isOn="False" rectAlignment="MiddleRight"/>
<VerticalLayout class="window" active="false" color="black" visibility="Admin" outlineSize="1 1" outline="grey" allowDragging="true" returnToOriginalPositionWhenReleased="false" width="500" height="800"/>
<VerticalLayout class="group" outlineSize="1 1" outline="grey" color="#222222" padding="10" spacing="5"/>
<VerticalLayout class="text-column" padding="5 20 0 0"/>
<VerticalLayout class="window" visibility="Admin" active="false" color="black" width="500" height="700" outlineSize="1 1" outline="grey" showAnimation="SlideIn_Right" hideAnimation="SlideOut_Right" animationDuration="0.1"/>
<VerticalLayout class="group" visibility="Admin" outlineSize="1 1" outline="grey" color="#222222" padding="10" spacing="5"/>
<VerticalLayout class="text-column" visibility="Admin" padding="5 20 0 0"/>
<HorizontalLayout class="group-content" color="#444444" padding="5" spacing="5"/>
<HorizontalLayout class="group-content" visibility="Admin" color="#444444" padding="5" spacing="5"/>
<Text class="group-header" fontSize="24" font="font_teutonic-arkham"/>
<Text class="option-header" fontSize="16" font="font_teutonic-arkham"/>
<Text class="option-header" fontSize="18" font="font_teutonic-arkham"/>
<Text class="description" fontSize="12"/>
<Button class="bottomButtons" hoverClass="hover" pressClass="press" selectClass="select" color="#888888" minHeight="35" fontSize="24" font="font_teutonic-arkham"/>
@ -20,99 +20,84 @@
</Defaults>
<!-- Option Panel -->
<VerticalLayout id="optionPanel" class="window">
<VerticalLayout id="optionPanel" class="window" rectAlignment="LowerRight" offsetXY="-50 60">
<Panel minHeight="45" flexibleHeight="0" padding="10 10 0 0">
<Text font="font_teutonic-arkham" fontSize="35">Options</Text>
</Panel>
<VerticalLayout>
<!-- Group 1 -->
<!-- Group: playermat settings -->
<VerticalLayout class="group">
<Panel minHeight="44" image="option_image1" padding="5 0 0 0">
<Text class="group-header">PLAYERMAT SETTINGS</Text>
</Panel>
<!-- Option 1 -->
<!-- Option: enable snap tags -->
<HorizontalLayout class="group-content">
<VerticalLayout class="text-column">
<Text class="option-header">Enable snap tags</Text>
<Text class="description">Only cards with the tag "Asset" will snap (official cards are supported by default).&#xA;Disable this if you are having issues with custom content.</Text>
</VerticalLayout>
<Toggle id="toggle1" onValueChanged="onClick_toggleOption(1)" />
<Toggle id="useSnapTags" onValueChanged="onClick_toggleOption(useSnapTags)" />
</HorizontalLayout>
<!-- Option 2 -->
<!-- Option: show draw 1 button -->
<HorizontalLayout class="group-content">
<VerticalLayout class="text-column">
<Text class="option-header">Show "Draw 1" button</Text>
<Text class="description">Displays a button below the "Upkeep" button that draws a card from your deck. Useful for multi-handed solo play.</Text>
</VerticalLayout>
<Toggle id="toggle2" onValueChanged="onClick_toggleOption(2)"/>
<Toggle id="showDrawButton" onValueChanged="onClick_toggleOption(showDrawButton)"/>
</HorizontalLayout>
<!-- Option 3 -->
<!-- Option: use clickable clue-counters -->
<HorizontalLayout class="group-content">
<VerticalLayout class="text-column">
<Text class="option-header">Use clickable clue-counters</Text>
<Text class="description">Instead of automatically counting clues in the respective area on your playermat, this displays a clickable counter for clues.&#xA;Take note of each player's clue count before changing this option!</Text>
<Text class="description">Instead of automatically counting clues in the respective area on your playermat, this displays a clickable counter for clues.</Text>
</VerticalLayout>
<Toggle id="toggle3" onValueChanged="onClick_toggleOption(3)"/>
<Toggle id="useClueClickers" onValueChanged="onClick_toggleOption(useClueClickers)"/>
</HorizontalLayout>
</VerticalLayout>
<!-- Group 2 -->
<!-- Group: fan-made accessories -->
<VerticalLayout class="group">
<Panel minHeight="44" image="option_image2" padding="5 0 0 0">
<Text class="group-header">FAN-MADE ACCESSORIES</Text>
</Panel>
<!-- Option 4 -->
<!-- Option: show token arranger -->
<HorizontalLayout class="group-content">
<VerticalLayout class="text-column">
<Text class="option-header">Toggle Text 4</Text>
<Text class="description">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat...</Text>
<Text class="option-header">Show "Token Arranger"</Text>
<Text class="description">See the contents of the chaos bag at a glance! This tool displays a sorted table of the tokens to allow easier guessing of your odds.</Text>
</VerticalLayout>
<Toggle id="toggle4" onValueChanged="onClick_toggleOption(4)"/>
<Toggle id="showTokenArranger" onValueChanged="onClick_toggleOption(showTokenArranger)"/>
</HorizontalLayout>
<!-- Option 5 -->
<!-- Option: show clean up helper -->
<HorizontalLayout class="group-content">
<VerticalLayout class="text-column">
<Text class="option-header">Toggle Text 5</Text>
<Text class="description">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat...</Text>
<Text class="option-header">Show "Clean Up Helper"</Text>
<Text class="description">Useful for campaign-play: It resets play areas to allow continuous gameplay in the same savegame.</Text>
</VerticalLayout>
<Toggle id="toggle5" onValueChanged="onClick_toggleOption(5)"/>
<Toggle id="showCleanUpHelper" onValueChanged="onClick_toggleOption(showCleanUpHelper)"/>
</HorizontalLayout>
<!-- Option 6 -->
<!-- Option: show hand helper -->
<HorizontalLayout class="group-content">
<VerticalLayout class="text-column">
<Text class="option-header">Toggle Text 6</Text>
<Text class="description">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat...</Text>
<Text class="option-header">Show "Hand Helper"</Text>
<Text class="description">Never count your hand cards again! This tool does that for you and can even take "Dream-Enhancing Serum" into account. Also includes a button for randomly discard a card.</Text>
</VerticalLayout>
<Toggle id="toggle6" onValueChanged="onClick_toggleOption(6)"/>
</HorizontalLayout>
</VerticalLayout>
<!-- Group 3 -->
<VerticalLayout class="group">
<Panel minHeight="30">
<Text class="group-header">Group 3</Text>
</Panel>
<!-- Option 7 -->
<HorizontalLayout class="group-content">
<VerticalLayout class="text-column">
<Text class="option-header">Toggle Text 7</Text>
<Text class="description">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat...</Text>
</VerticalLayout>
<Toggle id="toggle7" onValueChanged="onClick_toggleOption(7)"/>
<Toggle id="showHandHelper" onValueChanged="onClick_toggleOption(showHandHelper)"/>
</HorizontalLayout>
<!-- Option 8 -->
<!-- Option: show chaos bag manager -->
<HorizontalLayout class="group-content">
<VerticalLayout class="text-column">
<Text class="option-header">Toggle Text 8</Text>
<Text class="description">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat...</Text>
<Text class="option-header">Show "Chaos Bag Manager"</Text>
<Text class="description">Panel for easy addition or removal of chaos tokens to the bag - very useful for EotE because of Frost tokens!</Text>
</VerticalLayout>
<Toggle id="toggle8" onValueChanged="onClick_toggleOption(8)"/>
<Toggle id="showChaosBagManager" onValueChanged="onClick_toggleOption(showChaosBagManager)"/>
</HorizontalLayout>
</VerticalLayout>
</VerticalLayout>