1
0
mirror of https://github.com/ComfyFactory/ComfyFactorio.git synced 2025-03-25 21:29:06 +02:00

Add support for more bottom buttons + minor fixes

This commit is contained in:
Gerkiz 2023-12-06 22:52:13 +01:00
parent 574d9764c1
commit 7f59d7e868
9 changed files with 504 additions and 178 deletions

View File

@ -0,0 +1,164 @@
--made by Hanakocz
-- modified by gerkiz
--charge your armor equipment from nearby accumulators!
--change global.charging_station_multiplier if you want different conversion rate than 1:1.
local Event = require 'utils.event'
local SpamProtection = require 'utils.spam_protection'
local BottomFrame = require 'utils.gui.bottom_frame'
local Gui = require 'utils.gui'
local Color = require 'utils.color_presets'
local module_name = '[color=blue][Charging station][/color] '
local function draw_charging_gui(player, activate_custom_buttons)
local frame = player.gui.top.charging_station
if not frame or not frame.valid then
frame =
player.gui.top.add(
{
type = 'sprite-button',
name = 'charging_station',
sprite = 'item/battery-mk2-equipment',
tooltip = {
'modules.charging_station_tooltip'
},
style = Gui.button_style
}
)
frame.style.minimal_height = 38
frame.style.maximal_height = 38
end
frame.visible = not activate_custom_buttons
end
local function discharge_accumulators(surface, position, force, power_needs)
local accumulators = surface.find_entities_filtered {name = 'accumulator', force = force, position = position, radius = 13}
local power_drained = 0
power_needs = power_needs * global.charging_station_multiplier
for _, accu in pairs(accumulators) do
if accu.valid then
if accu.energy > 3000000 and power_needs > 0 then
if power_needs >= 2000000 then
power_drained = power_drained + 2000000
accu.energy = accu.energy - 2000000
power_needs = power_needs - 2000000
else
power_drained = power_drained + power_needs
accu.energy = accu.energy - power_needs
end
elseif power_needs <= 0 then
break
end
end
end
return power_drained / global.charging_station_multiplier
end
local function charge(player)
local msg = player.print(module_name .. 'No valid armor to charge was found.', Color.warning)
if not player.character then
return msg
end
local armor_inventory = player.get_inventory(defines.inventory.character_armor)
if not armor_inventory.valid then
return msg
end
local armor = armor_inventory[1]
if not armor.valid_for_read then
return msg
end
local grid = armor.grid
if not grid or not grid.valid then
return msg
end
local equip = grid.equipment
for _, piece in pairs(equip) do
if piece.valid and piece.generator_power == 0 then
local energy_needs = piece.max_energy - piece.energy
if energy_needs > 0 then
local energy = discharge_accumulators(player.surface, player.position, player.force, energy_needs)
if energy > 0 then
if piece.energy + energy >= piece.max_energy then
piece.energy = piece.max_energy
else
piece.energy = piece.energy + energy
end
end
end
end
end
end
local function on_player_joined_game(event)
local player = game.get_player(event.player_index)
if not player or not player.valid then
return
end
local activate_custom_buttons = BottomFrame.get('activate_custom_buttons')
draw_charging_gui(player, activate_custom_buttons)
if activate_custom_buttons then
BottomFrame.add_inner_frame(
{
player = player,
element_name = 'charging_station',
tooltip = {
'modules.charging_station_tooltip'
},
sprite = 'item/battery-mk2-equipment'
}
)
end
end
local function on_gui_click(event)
if not event then
return
end
if not event.element then
return
end
if not event.element.valid then
return
end
if event.element.name == 'charging_station' then
local player = game.players[event.player_index]
local is_spamming = SpamProtection.is_spamming(player, nil, 'Charging Station Gui Click')
if is_spamming then
return
end
charge(player)
return
end
end
local function on_init()
global.charging_station_multiplier = 1
end
Event.on_init(on_init)
Event.add(defines.events.on_player_joined_game, on_player_joined_game)
Event.add(defines.events.on_gui_click, on_gui_click)
Event.add(
BottomFrame.events.bottom_quickbar_location_changed,
function(event)
local player_index = event.player_index
if not player_index then
return
end
local player = game.get_player(player_index)
if not player or not player.valid then
return
end
local bottom_frame_data = event.data
if bottom_frame_data and bottom_frame_data.top then
draw_charging_gui(player, false)
else
draw_charging_gui(player, true)
end
end
)

View File

@ -28,5 +28,6 @@ Public.friendly_pet = require 'maps.mountain_fortress_v3.locomotive.friendly_pet
Public.market = require 'maps.mountain_fortress_v3.locomotive.market'
Public.permission_groups = require 'maps.mountain_fortress_v3.locomotive.permission_groups'
Public.spawn_locomotive = require 'maps.mountain_fortress_v3.locomotive.spawn_locomotive'
Public.charging_station = require 'maps.mountain_fortress_v3.charging_station'
return Public

View File

@ -27,7 +27,7 @@ function Public.get_distance(position)
end
function Public.add_loot(surface, position, chest)
local loot_stats = Public.get('loot_stats')
local loot_stats = Public.get('loot_stats') -- loot_stats.normal == 48
local budget = loot_stats.normal + abs(position.y) * 1.75
budget = budget * random(25, 175) * 0.01
@ -80,7 +80,7 @@ function Public.add_loot(surface, position, chest)
end
function Public.add_loot_rare(surface, position, chest, magic)
local loot_stats = Public.get('loot_stats')
local loot_stats = Public.get('loot_stats') -- loot_stats.rare == 48
local budget = (magic * loot_stats.rare) + abs(position.y) * 1.75
budget = budget * random(25, 175) * 0.01

View File

@ -10,7 +10,6 @@ require 'modules.no_deconstruction_of_neutral_entities'
require 'modules.spawners_contain_biters'
require 'maps.mountain_fortress_v3.ic.main'
require 'modules.wave_defense.main'
require 'modules.charging_station'
local Event = require 'utils.event'
local Gui = require 'utils.gui'

View File

@ -513,7 +513,7 @@ local function get_random_items()
{'copper-cable', scale(20000000, 100000000)},
{'copper-plate', scale(5000000, 80000000)},
{'electric-engine-unit', scale(30000, 200000)},
{'electronic-circuit', scale(5000000, 50000000)},
{'electronic-circuit', scale(5000000, 30000000)},
{'engine-unit', scale(90000, 750000)},
{'explosives', scale(700000, 3000000)},
{'iron-gear-wheel', scale(400000, 3000000)},

View File

@ -693,6 +693,7 @@ local function on_player_joined_game(event)
local player = game.get_player(event.player_index)
create_gui_button(player)
Task.delay(delay_tooltip_token, {player_index = player.index})
BottomFrame.add_inner_frame({player = player, element_name = auto_stash_button_name, tooltip = this.tooltip, sprite = 'item/wooden-chest'})
end
Gui.on_click(
@ -739,19 +740,6 @@ Event.on_configuration_changed(do_whitelist)
Event.on_init(do_whitelist)
Event.add(defines.events.on_player_joined_game, on_player_joined_game)
Event.add(
BottomFrame.events.bottom_quickbar_button_name,
function(data)
local event = data.event
if not event then
return
end
local player = event.player
auto_stash(player, event)
end
)
Event.add(
BottomFrame.events.bottom_quickbar_location_changed,
function(event)

View File

@ -4,6 +4,9 @@ local Server = require 'utils.server'
local Color = require 'utils.color_presets'
local Event = require 'utils.event'
local Global = require 'utils.global'
local BottomFrame = require 'utils.gui.bottom_frame'
local Gui = require 'utils.gui'
local SpamProtection = require 'utils.spam_protection'
local this = {
players = {},
@ -19,6 +22,8 @@ Global.register(
local Public = {}
local clear_corpse_button_name = Gui.uid_name()
commands.add_command(
'spaghetti',
'Does spaghett.',
@ -428,6 +433,55 @@ function Public.insert_all_items(player)
end
end
local function create_clear_corpse_frame(player, bottom_frame_data)
local button
if Gui.get_mod_gui_top_frame() then
button =
Gui.add_mod_button(
player,
{
type = 'sprite-button',
name = clear_corpse_button_name,
sprite = 'entity/behemoth-biter',
tooltip = {'commands.clear_corpse'},
style = Gui.button_style
}
)
else
button =
player.gui.top[clear_corpse_button_name] or
player.gui.top.add(
{
type = 'sprite-button',
sprite = 'entity/behemoth-biter',
name = clear_corpse_button_name,
tooltip = {'commands.clear_corpse'},
style = Gui.button_style
}
)
button.style.font_color = {r = 0.11, g = 0.8, b = 0.44}
button.style.font = 'heading-1'
button.style.minimal_height = 40
button.style.maximal_width = 40
button.style.minimal_width = 38
button.style.maximal_height = 38
button.style.padding = 1
button.style.margin = 0
end
bottom_frame_data = bottom_frame_data or BottomFrame.get_player_data(player)
if bottom_frame_data ~= nil and not bottom_frame_data.top then
if button and button.valid then
button.visible = false
end
else
if button and button.valid then
button.visible = true
end
end
end
function Public.get(key)
if key then
return this[key]
@ -471,6 +525,36 @@ Event.add(
function(event)
local player = game.players[event.player_index]
on_player_joined_game(player)
create_clear_corpse_frame(player)
BottomFrame.add_inner_frame({player = player, element_name = clear_corpse_button_name, tooltip = {'commands.clear_corpse'}, sprite = 'entity/behemoth-biter'})
end
)
Gui.on_click(
clear_corpse_button_name,
function(event)
local is_spamming = SpamProtection.is_spamming(event.player, nil, 'Clear Corpse')
if is_spamming then
return
end
clear_corpses(event)
end
)
Event.add(
BottomFrame.events.bottom_quickbar_location_changed,
function(event)
local player_index = event.player_index
if not player_index then
return
end
local player = game.get_player(player_index)
if not player or not player.valid then
return
end
local bottom_frame_data = event.data
create_clear_corpse_frame(player, bottom_frame_data)
end
)

View File

@ -1,11 +1,14 @@
local Misc = require 'utils.commands.misc'
local Event = require 'utils.event'
local Global = require 'utils.global'
local Gui = require 'utils.gui'
local SpamProtection = require 'utils.spam_protection'
local Task = require 'utils.task_token'
local Server = require 'utils.server'
local try_get_data = Server.try_get_data
local set_data = Server.set_data
local this = {
players = {},
storage = {},
activate_custom_buttons = false,
bottom_quickbar_button = {},
bottom_quickbar_button_data = {}
@ -18,118 +21,182 @@ Global.register(
end
)
local Public = {}
Public.events = {
bottom_quickbar_button_name = Event.generate_event_name('bottom_quickbar_button_name'),
bottom_quickbar_respawn_raise = Event.generate_event_name('bottom_quickbar_respawn_raise'),
bottom_quickbar_location_changed = Event.generate_event_name('bottom_quickbar_location_changed')
local Public = {
events = {
bottom_quickbar_respawn_raise = Event.generate_event_name('bottom_quickbar_respawn_raise'),
bottom_quickbar_location_changed = Event.generate_event_name('bottom_quickbar_location_changed')
}
}
local main_frame_name = Gui.uid_name()
local clear_corpse_button_name = Gui.uid_name()
local bottom_quickbar_button_name = Gui.uid_name()
local set_location
local get_player_data
local bottom_dataset = 'bottom_frame_data'
function Public.get_player_data(player, remove_user_data)
if remove_user_data then
if this.players[player.index] then
this.players[player.index] = nil
local main_frame_name = Gui.uid_name()
local sections = {
[1] = 1,
[2] = 1,
[3] = 2,
[4] = 2,
[5] = 3,
[6] = 3,
[7] = 4,
[8] = 4,
[9] = 5,
[10] = 5,
[11] = 6,
[12] = 6
}
local restore_bottom_location_token =
Task.register(
function(event)
local player_index = event.player_index
local player = game.get_player(player_index)
if not player or not player.valid then
return
end
local state = event.state
if not state then
return
end
local bottom_right = event.bottom_right or 'bottom_right'
local above = event.above or false
local data = get_player_data(player)
data.bottom_right = bottom_right
data.above = above
data.state = state
set_location(player, state)
end
)
local function remove_player(index)
this.players[index] = nil
this.storage[index] = nil
this.bottom_quickbar_button[index] = nil
end
get_player_data = function(player, remove_user_data)
if remove_user_data then
this.players[player.index] = nil
this.storage[player.index] = nil
return
end
if not this.players[player.index] then
this.players[player.index] = {
state = 'bottom_right'
state = 'bottom_right',
section = {},
direction = 'vertical',
row_index = 1,
row_selection = 1,
row_selection_added = 1
}
this.storage[player.index] = {}
end
return this.players[player.index]
return this.players[player.index], this.storage[player.index]
end
function Public.get(key)
if key then
return this[key]
else
return this
--- Refreshes all inner frames for a given player
local function refresh_inner_frames(player)
if not player or not player.valid then
return
end
end
function Public.set(key, value)
if key and (value or value == false) then
this[key] = value
return this[key]
elseif key then
return this[key]
else
return this
local player_data, storage_data = get_player_data(player)
if not player_data or not storage_data or not player_data.frame or not player_data.frame.valid then
return
end
end
function Public.remove_player(index)
this.players[index] = nil
this.bottom_quickbar_button[index] = nil
end
local main_frame = player_data.frame
function Public.reset()
local players = game.players
for i = 1, #players do
local player = players[i]
if player and player.valid then
if not player.connected then
this.players[player.index] = nil
this.bottom_quickbar_button[player.index] = nil
local horizontal_flow = main_frame.add {type = 'flow', direction = 'horizontal'}
horizontal_flow.style.horizontal_spacing = 0
for row_index, row_index_data in pairs(storage_data) do
if row_index_data and type(row_index_data) == 'table' then
local section_row_index = player_data.section[row_index]
local vertical_flow = horizontal_flow.add {type = 'flow', direction = 'vertical'}
vertical_flow.style = 'shortcut_bar_column'
if not section_row_index then
player_data.section[row_index] = {}
section_row_index = player_data.section[row_index]
end
if not section_row_index.inside_frame or not section_row_index.inside_frame.valid then
section_row_index.inner_frame = vertical_flow
end
for row_selection, row_selection_data in pairs(row_index_data) do
if section_row_index[row_selection] and section_row_index[row_selection].valid then
section_row_index[row_selection].destroy()
end
section_row_index[row_selection] =
section_row_index.inner_frame.add {
type = 'sprite-button',
sprite = row_selection_data.sprite,
name = row_selection_data.name,
tooltip = row_selection_data.tooltip or '',
style = 'quick_bar_page_button'
}
end
end
end
end
----! Gui Functions ! ----
local function create_gui_button(player, frame_data)
local button
if Gui.get_mod_gui_top_frame() then
button =
Gui.add_mod_button(
player,
{
type = 'sprite-button',
name = clear_corpse_button_name,
sprite = 'entity/behemoth-biter',
tooltip = {'commands.clear_corpse'},
style = Gui.button_style
}
)
else
button =
player.gui.top[clear_corpse_button_name] or
player.gui.top.add(
{
type = 'sprite-button',
sprite = 'entity/behemoth-biter',
name = clear_corpse_button_name,
tooltip = {'commands.clear_corpse'},
style = Gui.button_style
}
)
button.style.font_color = {r = 0.11, g = 0.8, b = 0.44}
button.style.font = 'heading-1'
button.style.minimal_height = 40
button.style.maximal_width = 40
button.style.minimal_width = 38
button.style.maximal_height = 38
button.style.padding = 1
button.style.margin = 0
---Adds a new inner frame to the bottom frame
-- local BottomFrame = require 'utils.gui.bottom_frame'
-- BottomFrame.add_inner_frame({player = player, element_name = Gui.uid_name(), tooltip = 'Some tooltip', sprite = 'item/raw-fish' })
---@param data any
local function add_inner_frame(data)
if not data then
return
end
local player = data.player
local element_name = data.element_name
local tooltip = data.tooltip
local sprite = data.sprite
if not player or not player.valid then
return error('Given player was not valid', 2)
end
if not element_name then -- the element_name to pick from the row_selection
return error('Element name is missing', 2)
end
if not sprite then
return error('Sprite is missing', 2)
end
frame_data = frame_data or Public.get_player_data(player)
if not (this.activate_custom_buttons and frame_data ~= nil and not frame_data.top) then
if button and button.valid then
button.visible = true
end
else
if button and button.valid then
button.visible = false
end
local player_data, storage_data = get_player_data(player)
if not player_data or not storage_data or not player_data.frame or not player_data.frame.valid then
return
end
if player_data.row_index > 6 then
return error('Having more than 6 rows is currently not supported.', 2)
end
player_data.row_index = sections[player_data.row_selection_added]
if not storage_data[player_data.row_index] then
storage_data[player_data.row_index] = {}
end
local storage_data_section = storage_data[player_data.row_index]
storage_data_section[player_data.row_selection] = {
name = element_name,
sprite = sprite,
tooltip = tooltip
}
player_data.row_selection = player_data.row_selection + 1
player_data.row_selection_added = player_data.row_selection_added + 1
player_data.row_selection = player_data.row_selection > 2 and 1 or player_data.row_selection
end
local function destroy_frame(player)
@ -140,6 +207,12 @@ local function destroy_frame(player)
end
end
--- Creates a new frame
---@param player LuaPlayer
---@param alignment string
---@param location table
---@param data any
---@return unknown
local function create_frame(player, alignment, location, data)
local gui = player.gui
local frame = gui.screen[main_frame_name]
@ -166,16 +239,10 @@ local function create_frame(player, alignment, location, data)
frame.style.padding = 3
frame.style.top_padding = 4
if alignment == 'vertical' then
frame.style.minimal_height = 96
end
frame.location = location
if data.portable then
frame.caption = ''
end
local inner_frame =
frame.add {
type = 'frame',
@ -183,35 +250,28 @@ local function create_frame(player, alignment, location, data)
}
inner_frame.style = 'quick_bar_inner_panel'
inner_frame.add {
type = 'sprite-button',
sprite = 'entity/behemoth-biter',
name = clear_corpse_button_name,
tooltip = {'commands.clear_corpse'},
style = 'quick_bar_page_button'
}
local bottom_quickbar_button =
inner_frame.add {
type = 'sprite-button',
name = bottom_quickbar_button_name,
style = 'quick_bar_page_button'
}
if this.bottom_quickbar_button_data and this.bottom_quickbar_button_data.sprite and this.bottom_quickbar_button_data.tooltip and not data.top then
bottom_quickbar_button.sprite = this.bottom_quickbar_button_data.sprite
bottom_quickbar_button.tooltip = this.bottom_quickbar_button_data.tooltip
else
frame.destroy()
frame.location = location
if data.portable then
frame.caption = ''
end
this.bottom_quickbar_button[player.index] = {name = bottom_quickbar_button_name, frame = bottom_quickbar_button}
if data.top then
frame.visible = false
else
frame.visible = true
end
data.frame = inner_frame
data.parent = frame
data.section = data.section or {}
data.section_data = data.section_data or {}
data.alignment = alignment
return frame
end
local function set_location(player, state)
local data = Public.get_player_data(player)
set_location = function(player, state)
local data = get_player_data(player)
local alignment = 'vertical'
local location
@ -224,12 +284,13 @@ local function set_location(player, state)
if data.above then
location = {
x = (resolution.width / 2) - ((259) * scale),
y = (resolution.height - (150 * scale))
y = (resolution.height - (-12 + (40 * 5) * scale))
}
alignment = 'horizontal'
else
location = {
x = (resolution.width / 2) - ((54 + 444) * scale),
-- x = (resolution.width / 2) - ((54 + 528 - 44) * scale),
x = (resolution.width / 2) - ((455 + (data.row_index * 40)) * scale),
y = (resolution.height - (96 * scale))
}
end
@ -237,8 +298,9 @@ local function set_location(player, state)
elseif state == 'bottom_right' then
if data.above then
location = {
x = (resolution.width / 2) - ((-376) * scale),
y = (resolution.height - (150 * scale))
-- x = (resolution.width / 2) - ((-262 - (40 * t[data.row_index])) * scale),
x = (resolution.width / 2) - ((-460 + (data.row_index * 40)) * scale),
y = (resolution.height - (-12 + (40 * 5) * scale))
}
alignment = 'horizontal'
else
@ -259,14 +321,28 @@ local function set_location(player, state)
data.state = state
local secs = Server.get_current_time()
if secs ~= nil then
set_data(
bottom_dataset,
player.name,
{
bottom_state = data.bottom_state,
above = data.above,
state = data.state
}
)
end
create_frame(player, alignment, location, data)
refresh_inner_frames(player)
end
--- Sets then frame location of the given player
---@param player LuaPlayer?
---@param value boolean
local function set_top(player, value)
local data = Public.get_player_data(player)
local data = get_player_data(player)
data.top = value or false
Public.set_location(player, 'bottom_right')
end
@ -275,7 +351,7 @@ end
---@param player LuaPlayer
---@return table|nil
local function get_location(player)
local data = Public.get_player_data(player)
local data = get_player_data(player)
return data and data.state or nil
end
@ -295,7 +371,7 @@ function Public.toggle_player_frame(player, state)
local gui = player.gui
local frame = gui.screen[main_frame_name]
if frame and frame.valid then
local data = Public.get_player_data(player)
local data = get_player_data(player)
if state then
data.visible = true
frame.visible = true
@ -306,43 +382,44 @@ function Public.toggle_player_frame(player, state)
end
end
Gui.on_click(
clear_corpse_button_name,
function(event)
local is_spamming = SpamProtection.is_spamming(event.player, nil, 'Clear Corpse')
if is_spamming then
return
end
Misc.clear_corpses(event)
function Public.get(key)
if key then
return this[key]
else
return this
end
)
end
Gui.on_click(
bottom_quickbar_button_name,
function(event)
local is_spamming = SpamProtection.is_spamming(event.player, nil, 'Custom Bottom_quickbar_button_name')
if is_spamming then
return
end
local data = Public.get_player_data(event.player)
if data and data.top then
return
end
event.data = data
Event.raise(Public.events.bottom_quickbar_button_name, {event = event})
function Public.set(key, value)
if key and (value or value == false) then
this[key] = value
return this[key]
elseif key then
return this[key]
else
return this
end
)
end
function Public.reset()
local players = game.players
for i = 1, #players do
local player = players[i]
if player and player.valid then
if not player.connected then
this.players[player.index] = nil
end
end
end
end
Event.add(
defines.events.on_player_joined_game,
function(event)
if this.activate_custom_buttons then
local player = game.get_player(event.player_index)
local data = Public.get_player_data(player)
local data = get_player_data(player)
set_location(player, data.state)
create_gui_button(player, data)
end
end
)
@ -352,7 +429,7 @@ Event.add(
function(event)
if this.activate_custom_buttons then
local player = game.get_player(event.player_index)
local data = Public.get_player_data(player)
local data = get_player_data(player)
set_location(player, data.state)
end
end
@ -363,7 +440,7 @@ Event.add(
function(event)
local player = game.get_player(event.player_index)
if this.activate_custom_buttons then
local data = Public.get_player_data(player)
local data = get_player_data(player)
set_location(player, data.state)
end
end
@ -392,7 +469,7 @@ Event.add(
function(event)
if this.activate_custom_buttons then
local player = game.get_player(event.player_index)
local data = Public.get_player_data(player)
local data = get_player_data(player)
set_location(player, data.state)
end
end
@ -401,7 +478,7 @@ Event.add(
Event.add(
defines.events.on_player_removed,
function(event)
Public.remove_player(event.player_index)
remove_player(event.player_index)
end
)
@ -414,8 +491,12 @@ Event.add(
if this.activate_custom_buttons then
local player = game.get_player(event.player_index)
local data = Public.get_player_data(player)
local data = get_player_data(player)
set_location(player, data.state)
local secs = Server.get_current_time()
if secs ~= nil then
try_get_data(bottom_dataset, bottom_dataset.index, restore_bottom_location_token)
end
end
end
)
@ -429,16 +510,25 @@ Event.add(
if this.activate_custom_buttons then
local player = game.get_player(event.player_index)
local data = Public.get_player_data(player)
create_gui_button(player, data)
local data = get_player_data(player)
if data.frame and data.frame.valid then
if data.top then
data.frame.visible = false
else
data.frame.visible = true
end
end
end
end
)
Public.main_frame_name = main_frame_name
Public.get_player_data = get_player_data
Public.remove_player = remove_player
Public.set_location = set_location
Public.get_location = get_location
Public.set_top = set_top
Public.add_inner_frame = add_inner_frame
Gui.screen_to_bypass(main_frame_name)
return Public

View File

@ -570,7 +570,7 @@ local function build_config_gui(data)
if bottom_frame and bottom_frame.top then
switch_state = 'left'
end
add_switch(scroll_pane, switch_state, 'top_location', 'Position - top', 'Toggle to select if you want the autostash button at the top or the bottom.')
add_switch(scroll_pane, switch_state, 'top_location', 'Position - top', 'Toggle to select if you want the bottom buttons at the top or the bottom.')
end
scroll_pane.add({type = 'line'})