Feature: Toolbar Menu (#268)

* Fix left flow not using uids

* Mock Toolbar menu

* Fix task list after core gui change

* Allow show/hide override

* Fix autofill permissions

* Copy style from toolbar on change

* Open and close automatically

* Removed hacky prevent default

* Fixed more core issues

* Add reset button

* Allow for custom draw order on join

* Add methods to reorder ui flows

* Impliment move buttons

* Add locale

* Add toolbar to player data

* Better player data layout

* Picked a suitable datastore id

* Update locale for readme

* Fix swaping left flow order

* Fix datastore updates

* Code cleanup

* Fix incorrect top flow hashing on load

* Fix loading of malformed data

* Fixed loading state of left flows

* Dont save default data

* Dont open menu on join

* Lint

* Remove incorrect new index metamethod

* Revert method used for move_toolbar_button

* Fixed missing toolbar button

* Fixed desync between visibilty and toggle state

* Fix bad gui element path

* Fixed enable state of toggle button

* Change order of operations

* Fix reset not showing top flow
This commit is contained in:
Cooldude2606
2024-01-16 00:01:00 +00:00
committed by GitHub
parent 96813816b9
commit 1c8a97a339
19 changed files with 912 additions and 168 deletions

View File

@@ -122,10 +122,10 @@ end)
]]
local Gui = require 'expcore.gui.prototype'
require 'expcore.gui.helper_functions'
require 'expcore.gui.core_defines'
require 'expcore.gui.top_flow'
require 'expcore.gui.left_flow'
require 'expcore.gui.helper_functions'
require 'expcore.gui.defines'
local Roles = _C.opt_require('expcore.roles')

View File

@@ -25,7 +25,7 @@ Gui.element{
height = 36
}
:on_click(function(player, _,_)
Gui.toggle_top_flow(player)
Gui.toggle_top_flow(player, false)
end)
Gui.core_defines.hide_top_flow = hide_top_flow
@@ -45,7 +45,7 @@ Gui.element{
height = 20
}
:on_click(function(player, _,_)
Gui.toggle_top_flow(player)
Gui.toggle_top_flow(player, true)
end)
Gui.core_defines.show_top_flow = show_top_flow

View File

@@ -38,8 +38,10 @@ example_flow_with_button:add_to_left_flow(true)
]]
function Gui._prototype_element:add_to_left_flow(open_on_join)
_C.error_if_runtime()
if not self.name then error("Elements for the top flow must have a static name") end
Gui.left_elements[self] = open_on_join or false
self.open_on_join = open_on_join or false
table.insert(Gui.left_elements, self)
return self
end
@@ -60,26 +62,38 @@ function Gui.left_toolbar_button(sprite, tooltip, element_define, authenticator)
local button = Gui.toolbar_button(sprite, tooltip, authenticator)
-- Add on_click handler to handle click events comming from the player
button:on_click(function(player, _,_)
local top_flow = Gui.get_top_flow(player)
local element = top_flow[button.name]
local visibility_state = Gui.toggle_left_element(player, element_define)
button:on_click(function(player, _, _)
-- Raise custom event that tells listening elements if the element has changed visibility by a player clicking
-- Used in warp gui to handle the keep open logic
button:raise_custom_event{
button:raise_event{
name = Gui.events.on_visibility_changed_by_click,
element = element,
state = visibility_state
element = Gui.get_top_element(player, button),
state = Gui.toggle_left_element(player, element_define)
}
end)
-- Add property to the left flow element with the name of the button
-- This is for the ability to reverse lookup the button from the left flow element
element_define.toolbar_button = button.name
element_define.toolbar_button = button
button.left_flow_element = element_define
return button
end
Gui._left_flow_order_src = "<default>"
--- Get the order of elements in the left flow, first argument is player but is unused in the default method
function Gui.get_left_flow_order(_)
return Gui.left_elements
end
--- Inject a custom left flow order provider, this should accept a player and return a list of elements definitions to draw
function Gui.inject_left_flow_order(provider)
Gui.get_left_flow_order = provider
local debug_info = debug.getinfo(2, "Sn")
local file_name = debug_info.source:match('^.+/currently%-playing/(.+)$'):sub(1, -5)
local func_name = debug_info.name or "<anonymous:"..debug_info.linedefined..">"
Gui._left_flow_order_src = file_name..":"..func_name
end
--[[-- Draw all the left elements onto the left flow, internal use only with on join
@tparam LuaPlayer player the player that you want to draw the elements for
@@ -92,22 +106,33 @@ function Gui.draw_left_flow(player)
local hide_button = left_flow.gui_core_buttons[hide_left_flow]
local show_hide_button = false
for element_define, open_on_join in pairs(Gui.left_elements) do
-- Get the order to draw the elements in
local flow_order = Gui.get_left_flow_order(player)
if #flow_order ~= #Gui.left_elements then
error(string.format("Left flow order provider (%s) did not return the correct element count, expect %d got %d",
Gui._left_flow_order_src, #Gui.left_elements, #flow_order
))
end
for _, element_define in ipairs(flow_order) do
-- Draw the element to the left flow
local draw_success, left_element = xpcall(function()
return element_define(left_flow)
end, debug.traceback)
if not draw_success then
error('There as been an error with an element draw function: '..element_define.defined_at..'\n\t'..left_element)
log('There as been an error with an element draw function: '..element_define.defined_at..'\n\t'..left_element)
goto continue
end
-- Check if it should be open by default
local open_on_join = element_define.open_on_join
local visible = type(open_on_join) == 'boolean' and open_on_join or false
if type(open_on_join) == 'function' then
local success, err = pcall(open_on_join, player)
local success, err = xpcall(open_on_join, debug.traceback, player)
if not success then
error('There as been an error with an open on join hander for a gui element:\n\t'..err)
log('There as been an error with an open on join hander for a gui element:\n\t'..err)
goto continue
end
visible = err
end
@@ -116,23 +141,35 @@ function Gui.draw_left_flow(player)
left_element.visible = visible
show_hide_button = show_hide_button or visible
-- Get the assosiated element define
local top_flow = Gui.get_top_flow(player)
-- Check if the the element has a button attached
if element_define.toolbar_button then
-- Check if the topflow contains the button
local button = top_flow[element_define.toolbar_button]
if button then
-- Style the button
Gui.toolbar_button_style(button, visible)
end
Gui.toggle_toolbar_button(player, element_define.toolbar_button, visible)
end
::continue::
end
hide_button.visible = show_hide_button
end
--- Reorder the left flow elements to match that returned by the provider, uses a method equivalent to insert sort
function Gui.reorder_left_flow(player)
local left_flow = Gui.get_left_flow(player)
-- Get the order to draw the elements in
local flow_order = Gui.get_left_flow_order(player)
if #flow_order ~= #Gui.left_elements then
error(string.format("Left flow order provider (%s) did not return the correct element count, expect %d got %d",
Gui._left_flow_order_src, #Gui.left_elements, #flow_order
))
end
-- Reorder the elements, index 1 is the core ui buttons so +1 is required
for index, element_define in ipairs(flow_order) do
local element = left_flow[element_define.name]
left_flow.swap_children(index+1, element.get_index_in_parent())
end
end
--[[-- Update the visible state of the hide button, can be used to check if any frames are visible
@tparam LuaPlayer player the player to update the left flow for
@treturn boolean true if any left element is visible
@@ -144,7 +181,7 @@ local visible = Gui.update_left_flow(player)
function Gui.update_left_flow(player)
local left_flow = Gui.get_left_flow(player)
local hide_button = left_flow.gui_core_buttons[hide_left_flow]
for element_define, _ in pairs(Gui.left_elements) do
for _, element_define in ipairs(Gui.left_elements) do
local left_element = left_flow[element_define.name]
if left_element.visible then
hide_button.visible = true
@@ -169,20 +206,18 @@ function Gui.hide_left_flow(player)
-- Set the visible state of all elements in the flow
hide_button.visible = false
for element_define, _ in pairs(Gui.left_elements) do
for _, element_define in ipairs(Gui.left_elements) do
left_flow[element_define.name].visible = false
-- Check if the the element has a toobar button attached
if element_define.toolbar_button then
-- Check if the topflow contains the button
local button = top_flow[element_define.toolbar_button]
local button = top_flow[element_define.toolbar_button.name]
if button then
-- Style the button
Gui.toolbar_button_style(button, false)
-- Get the button define from the reverse lookup on the element
local button_define = Gui.defines[element_define.toolbar_button]
Gui.toggle_toolbar_button(player, element_define.toolbar_button, false)
-- Raise the custom event if all of the top checks have passed
button_define:raise_custom_event{
element_define.toolbar_button:raise_event{
name = Gui.events.on_visibility_changed_by_click,
element = button,
state = false
@@ -192,6 +227,12 @@ function Gui.hide_left_flow(player)
end
end
--- Checks if an element is loaded, used internally when the normal left gui assumptions may not hold
function Gui.left_flow_loaded(player, element_define)
local left_flow = Gui.get_left_flow(player)
return left_flow[element_define.name] ~= nil
end
--[[-- Get the element define that is in the left flow, use in events without an element refrence
@tparam LuaPlayer player the player that you want to get the element for
@tparam table element_define the element that you want to get
@@ -203,7 +244,7 @@ local frame = Gui.get_left_element(game.player, example_flow_with_button)
]]
function Gui.get_left_element(player, element_define)
local left_flow = Gui.get_left_flow(player)
return left_flow[element_define.name]
return assert(left_flow[element_define.name], "Left element failed to load")
end
--[[-- Toggles the visible state of a left element for a given player, can be used to set the visible state
@@ -220,23 +261,15 @@ Gui.toggle_top_flow(game.player, example_flow_with_button, true)
]]
function Gui.toggle_left_element(player, element_define, state)
local left_flow = Gui.get_left_flow(player)
local top_flow = Gui.get_top_flow(player)
-- Set the visible state
local element = left_flow[element_define.name]
local element = Gui.get_left_element(player, element_define)
if state == nil then state = not element.visible end
element.visible = state
Gui.update_left_flow(player)
-- Check if the the element has a button attached
if element_define.toolbar_button then
-- Check if the topflow contains the button
local button = top_flow[element_define.toolbar_button]
if button then
-- Style the button
Gui.toolbar_button_style(button, state)
end
Gui.toggle_toolbar_button(player, element_define.toolbar_button, state)
end
return state
end

View File

@@ -23,20 +23,46 @@ local Gui = {
--- The prototype used to store the functions of an element define
_prototype_element = {},
--- The prototype metatable applied to new element defines
_mt_element = {
__call = function(self, parent, ...)
local element = self._draw(self, parent, ...)
if self._style then self._style(element.style, element, ...) end
if self.name and self.name ~= element.name then
error("Static name \""..self.name.."\" expected but got: "..tostring(element.name))
end
return element and self:triggers_events(element)
end
}
_mt_element = {}
}
--- Allow access to the element prototype methods
Gui._mt_element.__index = Gui._prototype_element
--- Allows the define to be called to draw the element
function Gui._mt_element.__call(self, parent, ...)
local element, no_events = self._draw(self, parent, ...)
if self._style then self._style(element.style, element, ...) end
-- Asserts to catch common errors
if element then
if self.name and self.name ~= element.name then
error("Static name \""..self.name.."\" expected but got: "..tostring(element.name))
end
local event_triggers = element.tags and element.tags.ExpGui_event_triggers
if event_triggers and table.array_contains(event_triggers, self.uid) then
error("Element::triggers_events should not be called on the value you return from the definition")
end
elseif self.name then
error("Static name \""..self.name.."\" expected but no element was returned from the definition")
end
-- Register events by default, but allow skipping them
if no_events == self.no_events then
return element
else
return element and self:triggers_events(element)
end
end
--- Get where a function was defined as a string
local function get_defined_at(level)
local debug_info = debug.getinfo(level, "Sn")
local file_name = debug_info.source:match('^.+/currently%-playing/(.+)$'):sub(1, -5)
local func_name = debug_info.name or "<anonymous:"..debug_info.linedefined..">"
return file_name..":"..func_name
end
--- Element Define.
-- @section elementDefine
@@ -97,20 +123,21 @@ function Gui.element(element_define)
if element_define.name == Gui.unique_static_name then
element_define.name = "ExpGui_"..tostring(uid)
end
element.name = element_define.name
for k, v in pairs(element_define) do
if element[k] == nil then
element[k] = v
end
end
element._draw = function(_, parent)
return parent.add(element_define)
end
else
Gui.debug_info[uid].draw = 'Function'
Gui.debug_info[uid].draw = get_defined_at(element_define)
element._draw = element_define
end
-- Add the define to the base module
local debug_info = debug.getinfo(2, "Sn")
local file_name = debug_info.source:match('^.+/currently%-playing/(.+)$'):sub(1, -5)
local func_name = debug_info.name or "<anonymous:"..debug_info.linedefined..">"
element.defined_at = file_name..":"..func_name
element.defined_at = get_defined_at(3)
Gui.file_paths[uid] = element.defined_at
Gui.defines[uid] = element
@@ -154,6 +181,7 @@ end)
]]
function Gui._prototype_element:style(style_define)
_C.error_if_runtime()
-- Add the definition function
if type(style_define) == 'table' then
Gui.debug_info[self.uid].style = style_define
@@ -163,7 +191,7 @@ function Gui._prototype_element:style(style_define)
end
end
else
Gui.debug_info[self.uid].style = 'Function'
Gui.debug_info[self.uid].style = get_defined_at(style_define)
self._style = style_define
end
@@ -176,6 +204,7 @@ end
@treturn table the element define is returned to allow for event handlers to be registered
]]
function Gui._prototype_element:static_name(name)
_C.error_if_runtime()
if name == Gui.unique_static_name then
self.name = "ExpGui_"..tostring(self.uid)
else
@@ -185,16 +214,20 @@ function Gui._prototype_element:static_name(name)
end
--[[-- Used to link an element to an element define such that any event on the element will call the handlers on the element define
-- You should not call this on the element you return from your constructor because this is done automatically
@tparam LuaGuiElement element The element that will trigger calls to the event handlers
@treturn LuaGuiElement The element passed as the argument to allow for cleaner returns
]]
function Gui._prototype_element:triggers_events(element)
if not self._has_events then return element end
local tags = element.tags
if not tags then
element.tags = { ExpGui_event_triggers = { self.uid } }
return element
elseif not tags.ExpGui_event_triggers then
tags.ExpGui_event_triggers = { self.uid }
elseif table.array_contains(tags.ExpGui_event_triggers, self.uid) then
error("Element::triggers_events called multiple times on the same element with the same definition")
else
table.insert(tags.ExpGui_event_triggers, self.uid)
end
@@ -203,21 +236,28 @@ function Gui._prototype_element:triggers_events(element)
return element
end
--- Explicitly skip events on the element returned by your definition function
function Gui._prototype_element:no_events(element)
return element, self.no_events
end
--[[-- Set the handler which will be called for a custom event, only one handler can be used per event per element
@tparam string event_name the name of the event you want to handler to be called on, often from Gui.events
@tparam function handler the handler that you want to be called when the event is raised
@treturn table the element define so more handleres can be registered
@usage-- Register a handler to "my_custom_event" for this element
element_deinfe:on_custom_event('my_custom_event', function(event)
element_deinfe:on_event('my_custom_event', function(event)
event.player.print(player.name)
end)
]]
function Gui._prototype_element:on_custom_event(event_name, handler)
function Gui._prototype_element:on_event(event_name, handler)
_C.error_if_runtime()
table.insert(Gui.debug_info[self.uid].events, event_name)
Gui.events[event_name] = event_name
self[event_name] = handler
self._has_events = true
return self
end
@@ -226,13 +266,13 @@ end
@treturn table the element define so more events can be raised
@usage Raising a custom event
element_define:raise_custom_event{
element_define:raise_event{
name = 'my_custom_event',
element = element
}
]]
function Gui._prototype_element:raise_custom_event(event)
function Gui._prototype_element:raise_event(event)
-- Check the element is valid
local element = event.element
if not element or not element.valid then
@@ -270,15 +310,14 @@ local function event_handler_factory(event_name)
for _, uid in pairs(event_triggers) do
local element_define = Gui.defines[uid]
if element_define then
element_define:raise_custom_event(event)
element_define:raise_event(event)
end
end
end)
Gui.events[event_name] = event_name
return function(self, handler)
table.insert(Gui.debug_info[self.uid].events, debug.getinfo(1, "n").name)
self[event_name] = handler
return self
return self:on_event(event_name, handler)
end
end

View File

@@ -6,12 +6,16 @@
local Gui = require 'expcore.gui.prototype'
local mod_gui = require 'mod-gui' --- @dep mod-gui
local toolbar_button_size = 36
local hide_top_flow = Gui.core_defines.hide_top_flow.name
local show_top_flow = Gui.core_defines.show_top_flow.name
--- Top Flow.
-- @section topFlow
-- Triggered when a user changed the visibility of a left flow element by clicking a button
Gui.events.on_toolbar_button_toggled = 'on_toolbar_button_toggled'
--- Contains the uids of the elements that will shown on the top flow and their auth functions
-- @table top_elements
Gui.top_elements = {}
@@ -21,8 +25,30 @@ Gui.top_elements = {}
Gui.top_flow_button_style = mod_gui.button_style
--- The style that should be used for buttons on the top flow when their flow is visible
-- @field Gui.top_flow_button_visible_style
Gui.top_flow_button_visible_style = 'menu_button_continue'
-- @field Gui.top_flow_button_toggled_style
Gui.top_flow_button_toggled_style = 'menu_button_continue'
--[[-- Styles a top flow button depending on the state given
@tparam LuaGuiElement button the button element to style
@tparam boolean state The state the button is in
@usage-- Sets the button to the visible style
Gui.toolbar_button_style(button, true)
@usage-- Sets the button to the hidden style
Gui.toolbar_button_style(button, false)
]]
function Gui.toolbar_button_style(button, state, size)
if state then
button.style = Gui.top_flow_button_toggled_style
else
button.style = Gui.top_flow_button_style
end
button.style.minimal_width = size or toolbar_button_size
button.style.height = size or toolbar_button_size
button.style.padding = -2
end
--[[-- Gets the flow refered to as the top flow, each player has one top flow
@function Gui.get_top_flow(player)
@@ -48,11 +74,43 @@ end)
]]
function Gui._prototype_element:add_to_top_flow(authenticator)
_C.error_if_runtime()
if not self.name then error("Elements for the top flow must have a static name") end
Gui.top_elements[self] = authenticator or true
self.authenticator = authenticator or true
table.insert(Gui.top_elements, self)
return self
end
--- Returns true if the top flow has visible elements
function Gui.top_flow_has_visible_elements(player)
local top_flow = Gui.get_top_flow(player)
for _, child in pairs(top_flow.children) do
if child.name ~= hide_top_flow then
if child.visible then
return true
end
end
end
return false
end
Gui._top_flow_order_src = "<default>"
--- Get the order of elements in the top flow, first argument is player but is unused in the default method
function Gui.get_top_flow_order(_)
return Gui.top_elements
end
--- Inject a custom top flow order provider, this should accept a player and return a list of elements definitions to draw
function Gui.inject_top_flow_order(provider)
Gui.get_top_flow_order = provider
local debug_info = debug.getinfo(2, "Sn")
local file_name = debug_info.source:match('^.+/currently%-playing/(.+)$'):sub(1, -5)
local func_name = debug_info.name or "<anonymous:"..debug_info.linedefined..">"
Gui._top_flow_order_src = file_name..":"..func_name
end
--[[-- Updates the visible state of all the elements on the players top flow, uses authenticator
@tparam LuaPlayer player the player that you want to update the top flow for
@@ -62,21 +120,62 @@ Gui.update_top_flow(game.player)
]]
function Gui.update_top_flow(player)
local top_flow = Gui.get_top_flow(player)
local hide_button = top_flow[hide_top_flow]
local is_visible = hide_button.visible
-- Get the order to draw the elements in
local flow_order = Gui.get_top_flow_order(player)
if #flow_order ~= #Gui.top_elements then
error(string.format("Top flow order provider (%s) did not return the correct element count, expect %d got %d",
Gui._top_flow_order_src, #Gui.top_elements, #flow_order
))
end
-- Set the visible state of all elements in the flow
for element_define, authenticator in pairs(Gui.top_elements) do
for index, element_define in ipairs(flow_order) do
-- Ensure the element exists
local element = top_flow[element_define.name]
if not element then
element = element_define(top_flow)
else
top_flow.swap_children(index+1, element.get_index_in_parent())
end
-- Set the visible state
local allowed = authenticator
local allowed = element_define.authenticator
if type(allowed) == 'function' then allowed = allowed(player) end
element.visible = is_visible and allowed or false
element.visible = allowed or false
-- If its not visible and there is a left element, then hide it
if element_define.left_flow_element and not element.visible and Gui.left_flow_loaded(player, element_define.left_flow_element) then
Gui.toggle_left_element(player, element_define.left_flow_element, false)
end
end
-- Check if there are any visible elements in the top flow
if not Gui.top_flow_has_visible_elements(player) then
-- None are visible so hide the top_flow and its show button
Gui.toggle_top_flow(player, false)
local left_flow = Gui.get_left_flow(player)
local show_button = left_flow.gui_core_buttons[show_top_flow]
show_button.visible = false
end
end
--- Reorder the top flow elements to match that returned by the provider, uses a method equivalent to insert sort
function Gui.reorder_top_flow(player)
local top_flow = Gui.get_top_flow(player)
-- Get the order to draw the elements in
local flow_order = Gui.get_top_flow_order(player)
if #flow_order ~= #Gui.top_elements then
error(string.format("Top flow order provider (%s) did not return the correct element count, expect %d got %d",
Gui._top_flow_order_src, #Gui.top_elements, #flow_order
))
end
-- Reorder the elements, index 1 is the core ui buttons so +1 is required
for index, element_define in ipairs(flow_order) do
local element = top_flow[element_define.name]
top_flow.swap_children(index+1, element.get_index_in_parent())
end
end
@@ -119,7 +218,33 @@ local button = Gui.get_top_element(game.player, example_button)
]]
function Gui.get_top_element(player, element_define)
local top_flow = Gui.get_top_flow(player)
return top_flow[element_define.name]
return assert(top_flow[element_define.name], "Top element failed to load")
end
--[[-- Toggles the state of a toolbar button for a given player, can be used to set the visual state
@tparam LuaPlayer player the player that you want to toggle the element for
@tparam table element_define the element that you want to toggle
@tparam[opt] boolean state with given will set the state, else state will be toggled
@treturn boolean the new visible state of the element
@usage-- Toggle your example button
Gui.toggle_toolbar_button(game.player, toolbar_button)
@usage-- Show your example button
Gui.toggle_toolbar_button(game.player, toolbar_button, true)
]]
function Gui.toggle_toolbar_button(player, element_define, state)
local toolbar_button = Gui.get_top_element(player, element_define)
if state == nil then state = toolbar_button.style.name ~= Gui.top_flow_button_toggled_style end
Gui.toolbar_button_style(toolbar_button, state, toolbar_button.style.minimal_width)
element_define:raise_event{
name = Gui.events.on_toolbar_button_toggled,
element = toolbar_button,
player = player,
state = state
}
return state
end
--[[-- Creates a button on the top flow with consistent styling
@@ -143,31 +268,47 @@ function Gui.toolbar_button(sprite, tooltip, authenticator)
name = Gui.unique_static_name
}
:style{
minimal_width = 36,
height = 36,
minimal_width = toolbar_button_size,
height = toolbar_button_size,
padding = -2
}
:add_to_top_flow(authenticator)
end
--[[-- Styles a top flow button depending on the state given
@tparam LuaGuiElement button the button element to style
@tparam boolean state The state the button is in
--[[-- Creates a toggle button on the top flow with consistent styling
@tparam string sprite the sprite that you want to use on the button
@tparam ?string|Concepts.LocalizedString tooltip the tooltip that you want the button to have
@tparam[opt] function authenticator used to decide if the button should be visible to a player
@usage-- Sets the button to the visible style
Gui.toolbar_button_style(button, true)
@usage-- Sets the button to the hidden style
Gui.toolbar_button_style(button, false)
@usage-- Add a button to the toolbar
local toolbar_button =
Gui.toolbar_toggle_button('entity/inserter', 'Nothing to see here', function(player)
return player.admin
end)
:on_event(Gui.events.on_toolbar_button_toggled, function(player, element, event)
game.print(table.inspect(event))
end)
]]
function Gui.toolbar_button_style(button, state)
if state then
button.style = Gui.top_flow_button_visible_style
else
button.style = Gui.top_flow_button_style
end
button.style.minimal_width = 36
button.style.height = 36
button.style.padding = -2
function Gui.toolbar_toggle_button(sprite, tooltip, authenticator)
local button =
Gui.element{
type = 'sprite-button',
sprite = sprite,
tooltip = tooltip,
style = Gui.top_flow_button_style,
name = Gui.unique_static_name
}
:style{
minimal_width = toolbar_button_size,
height = toolbar_button_size,
padding = -2
}
:add_to_top_flow(authenticator)
button:on_click(function(player, _, _)
Gui.toggle_toolbar_button(player, button)
end)
return button
end