Merge branch 'release/5.5.0'

This commit is contained in:
Cooldude2606
2019-05-24 20:14:06 +01:00
58 changed files with 5239 additions and 75 deletions

View File

@@ -32,10 +32,13 @@ return {
'modules.addons.scorched-earth',
'modules.addons.pollution-grading',
'modules.addons.random-player-colours',
-- GUI
'modules.commands.debug',
-- Config Files
'config.command_auth_admin', -- commands tagged with admin_only are blocked for non admins
'config.command_auth_roles', -- commands must be allowed via the role config
'config.command_auth_runtime_disable', -- allows commands to be enabled and disabled during runtime
'config.permission_groups', -- loads some predefined permission groups
'config.roles', -- loads some predefined roles
'expcore.gui.test'
}

View File

@@ -44,6 +44,7 @@ Roles.new_role('Senior Administrator','SAdmin')
:set_parent('Administrator')
:allow{
'command/interface',
'command/debug',
'command/toggle-cheat-mode'
}
@@ -237,7 +238,7 @@ Roles.override_player_roles{
FlipHalfling90={'Moderator','Member'},
Gizan={'Pay to Win','Moderator','Member'},
Hobbitkicker={'Moderator','Member'},
jess_gaming={'Trainee','Member'},
jessi_gaming={'Trainee','Member'},
Koroto={'Moderator','Member'},
mafisch3={'Moderator','Member'},
maplesyrup01={'Moderator','Member'},

View File

@@ -4,7 +4,10 @@
-- all files which are loaded (including the config files) are present in ./config/file_loader.lua
-- this file is the landing point for all scenarios please DO NOT edit directly, further comments are to aid development
log('[START] -----| Explosive Gaming Scenario Loader |-----')
-- Info on the data lifecycle and how we use it: https://github.com/Refactorio/RedMew/wiki/The-data-lifecycle
log('[INFO] Setting up lua environment')
require 'resources.data_stages'
_LIFECYCLE = _STAGE.control -- Control stage
@@ -22,6 +25,7 @@ require 'resources.version'
ext_require = require('expcore.common').ext_require
-- Please go to config/file_loader.lua to edit the files that are loaded
log('[INFO] Getting file loader config')
local files = require 'config.file_loader'
-- Loads all files from the config and logs that they are loaded
@@ -30,7 +34,7 @@ local errors = {}
for index,path in pairs(files) do
-- Loads the next file in the list
log(string.format('[INFO] Loading files %3d/%s',index,total_file_count))
log(string.format('[INFO] Loading file %3d/%s (%s)',index,total_file_count,path))
local success,file = pcall(require,path)
-- Error Checking
@@ -49,4 +53,5 @@ end
-- Logs all errors again to make it make it easy to find
log('[INFO] All files loaded with '..#errors..' errors:')
for _,error in pairs(errors) do log(error) end
for _,error in pairs(errors) do log(error) end
log('[END] -----| Explosive Gaming Scenario Loader |-----')

121
expcore/Gui/buttons.lua Normal file
View File

@@ -0,0 +1,121 @@
--- Gui class define for buttons and sprite buttons
--[[
>>>> Functions
Button.new_button(name) --- Creates a new button element define
Button._prototype:on_click(player,element) --- Registers a handler for when the button is clicked
Button._prototype:on_left_click(player,element) --- Registers a handler for when the button is clicked with the left mouse button
Button._prototype:on_right_click(player,element) --- Registers a handler for when the button is clicked with the right mouse button
Button._prototype:set_sprites(sprite,hovered_sprite,clicked_sprite) --- Adds sprites to a button making it a spirte button
Button._prototype:set_click_filter(filter,...) --- Adds a click / mouse button filter to the button
Button._prototype:set_key_filter(filter,...) --- Adds a control key filter to the button
Other functions present from expcore.gui.core
]]
local mod_gui = require 'mod-gui'
local Gui = require 'expcore.gui.core'
local Button = {
_prototype=Gui._prototype_factory{
on_click = Gui._event_factory('on_click'),
on_left_click = Gui._event_factory('on_left_click'),
on_right_click = Gui._event_factory('on_right_click'),
}
}
--- Creates a new button element define
-- @tparam[opt] name string the optional debug name that can be added
-- @treturn table the new button element define
function Button.new_button(name)
local self = Gui._define_factory(Button._prototype)
self.draw_data.type = 'button'
self.draw_data.style = mod_gui.button_style
if name then
self:debug_name(name)
end
Gui.on_click(self.name,function(event)
local mouse_button = event.button
local keys = {alt=event.alt,control=event.control,shift=event.shift}
event.keys = keys
if self.post_authenticator then
if not self.post_authenticator(event.player,self.name) then return end
end
if mouse_button == defines.mouse_button_type.left and self.events.on_left_click then
self.events.on_left_click(event.player,event.element)
elseif mouse_button == defines.mouse_button_type.right and self.events.on_right_click then
self.events.on_right_click(event.player,event.element)
end
if self.mouse_button_filter and not self.mouse_button_filter[mouse_button] then return end
if self.key_button_filter then
for key,state in pairs(self.key_button_filter) do
if state and not keys[key] then return end
end
end
if self.events.on_click then
self.events.on_click(event.player,event.element,event)
end
end)
return self
end
--- Adds sprites to a button making it a spirte button
-- @tparam sprite SpritePath the sprite path for the default sprite for the button
-- @tparam[opt] hovered_sprite SpritePath the sprite path for the sprite when the player hovers over the button
-- @tparam[opt] clicked_sprite SpritePath the sprite path for the sprite when the player clicks the button
-- @treturn self returns the button define to allow chaining
function Button._prototype:set_sprites(sprite,hovered_sprite,clicked_sprite)
self.draw_data.type = 'sprite-button'
self.draw_data.sprite = sprite
self.draw_data.hovered_sprite = hovered_sprite
self.draw_data.clicked_sprite = clicked_sprite
return self
end
--- Adds a click / mouse button filter to the button
-- @tparam filter ?string|table either a table of mouse buttons or the first mouse button to filter, with a table true means allowed
-- @tparam[opt] ... when filter is not a table you can add the mouse buttons one after each other
-- @treturn self returns the button define to allow chaining
function Button._prototype:set_click_filter(filter,...)
if type(filter) == 'string' then
filter = {[filter]=true}
for _,v in pairs({...}) do
filter[v] = true
end
end
for k,v in pairs(filter) do
if type(v) == 'string' then
filter[k] = defines.mouse_button_type[v]
end
end
self.mouse_button_filter = filter
return self
end
--- Adds a control key filter to the button
-- @tparam filter ?string|table either a table of control keys or the first control keys to filter, with a table true means allowed
-- @tparam[opt] ... when filter is not a table you can add the control keyss one after each other
-- @treturn self returns the button define to allow chaining
function Button._prototype:set_key_filter(filter,...)
if type(filter) == 'string' then
filter = {[filter]=true}
for _,v in pairs({...}) do
filter[v] = true
end
end
self.key_button_filter = filter
return self
end
return Button

191
expcore/Gui/center.lua Normal file
View File

@@ -0,0 +1,191 @@
--- Gui structure define for center gui frames
--[[
>>>> Functions
CenterFrames.get_flow(player) --- Gets the center flow for a player
CenterFrames.clear_flow(player) --- Clears the center flow for a player
CenterFrames.draw_frame(player,name) --- Draws the center frame for a player, if already open then will do nothing
CenterFrames.redraw_frame(player,name) --- Draws the center frame for a player, if already open then will destroy it and redraw
CenterFrames.toggle_frame(player,name,state) --- Toggles if the frame is currently open or not, will open if closed and close if open
CenterFrames.new_frame(permision_name) --- Sets the frame to be the current active gui when opened and closes all other frames
CenterFrames._prototype:on_draw(player,frame) --- Use to draw your elements onto the new frame
CenterFrames._prototype:set_auto_focus(state) --- Sets the frame to be the current active gui when opened and closes all other frames
CenterFrames._prototype:draw_frame(player) --- Draws this frame to the player, if already open does nothing (will call on_draw to draw to the frame)
CenterFrames._prototype:redraw_frame(player) --- Draws this frame to the player, if already open it will remove it and redraw it (will call on_draw to draw to the frame)
CenterFrames._prototype:toggle_frame(player) --- Toggles if the frame is open, if open it will close it and if closed it will open it
CenterFrames._prototype:event_handler(action) --- Creates an event handler that will trigger one of its functions, use with Event.add
]]
local Gui = require 'expcore.gui.core'
local Toolbar = require 'expcore.gui.toolbar'
local Game = require 'utils.game'
local CenterFrames = {
_prototype = Gui._prototype_factory{
on_draw = Gui._event_factory('on_draw')
}
}
--- Gets the center flow for a player
-- @tparam player LuapPlayer the player to get the flow for
-- @treturn LuaGuiElement the center flow
function CenterFrames.get_flow(player)
player = Game.get_player_from_any(player)
return player.gui.center
end
--- Clears the center flow for a player
-- @tparam player LuapPlayer the player to clear the flow for
function CenterFrames.clear_flow(player)
local flow = CenterFrames.get_flow(player)
flow.clear()
end
--- Draws the center frame for a player, if already open then will do nothing
-- @tparam player LuapPlayer the player that will have the frame drawn
-- @tparam name string the name of the hui that will drawn
-- @treturn LuaGuiElement the new frame that was made
function CenterFrames.draw_frame(player,name)
local define = Gui.get_define(name,true)
if define then
return define:draw_frame(player)
end
end
--- Draws the center frame for a player, if already open then will destroy it and redraw
-- @tparam player LuapPlayer the player that will have the frame drawn
-- @tparam name string the name of the hui that will drawn
-- @treturn LuaGuiElement the new frame that was made
function CenterFrames.redraw_frame(player,name)
local define = Gui.get_define(name,true)
if define then
return define:draw_frame(player)
end
end
--- Toggles if the frame is currently open or not, will open if closed and close if open
-- @tparam player LuapPlayer the player that will have the frame toggled
-- @tparam name string the name of the hui that will be toggled
-- @tparam[opt] state boolean when set will force a state for the frame
-- @treturn boolean if the frame if no open or closed
function CenterFrames.toggle_frame(player,name,state)
local define = Gui.get_define(name,true)
if define then
if state == true then
define:draw_frame(player)
return true
elseif state == false then
local flow = CenterFrames.get_flow(player)
if flow[define.name] then
flow[define.name].destroy()
end
return false
else
return define:toggle_frame(player)
end
end
end
--- Creates a new center frame define
-- @tparam permision_name string the name that can be used with the permision system
-- @treturn table the new center frame define
function CenterFrames.new_frame(permision_name)
local self = Toolbar.new_button(permision_name)
self:on_click(function(player,element)
self:toggle_frame(player)
end)
local mt = getmetatable(self)
mt.__index = CenterFrames._prototype
mt.__call = self.event_handler
Gui.on_custom_close(self.name,function(event)
local element = event.element
if element and element.valid then element.destroy() end
end)
return self
end
--- Sets the frame to be the current active gui when opened and closes all other frames
-- @tparam[opt=true] state boolean when true will auto close other frames and set this frame as player.opened
function CenterFrames._prototype:set_auto_focus(state)
if state == false then
self.auto_focus = false
else
self.auto_focus = true
end
end
--- Draws this frame to the player, if already open does nothing (will call on_draw to draw to the frame)
-- @tparam player LuaPlayer the player to draw the frame for
-- @treturn LuaGuiElement the new frame that was drawn
function CenterFrames._prototype:draw_frame(player)
player = Game.get_player_from_any(player)
local flow = CenterFrames.get_flow(player)
if flow[self.name] then
return flow[self.name]
end
if self.auto_focus then
flow.clear()
end
local frame = flow.add{
type='frame',
name=self.name
}
if self.auto_focus then
player.opened = frame
end
if self.events.on_draw then
self.events.on_draw(player,frame)
end
return frame
end
--- Draws this frame to the player, if already open it will remove it and redraw it (will call on_draw to draw to the frame)
-- @tparam player LuaPlayer the player to draw the frame for
-- @treturn LuaGuiElement the new frame that was drawn
function CenterFrames._prototype:redraw_frame(player)
player = Game.get_player_from_any(player)
local flow = CenterFrames.get_flow(player)
if flow[self.name] then
flow[self.name].destroy()
end
return self:draw_frame(player)
end
--- Toggles if the frame is open, if open it will close it and if closed it will open it
-- @tparam player LuaPlayer the player to draw the frame for
-- @treturn boolean with the gui frame is now open
function CenterFrames._prototype:toggle_frame(player)
player = Game.get_player_from_any(player)
local flow = CenterFrames.get_flow(player)
if flow[self.name] then
flow[self.name].destroy()
return false
else
self:draw_frame(player)
return true
end
end
--- Creates an event handler that will trigger one of its functions, use with Event.add
-- @tparam[opt=update] action string the action to take on this event
function CenterFrames._prototype:event_handler(action)
action = action or 'update'
return function(event)
local player = Game.get_player_by_index(event.player_index)
self[action](self,player)
end
end
return CenterFrames

266
expcore/Gui/checkboxs.lua Normal file
View File

@@ -0,0 +1,266 @@
--- Gui class define for checkboxs and radiobuttons
--[[
>>>> Using an option set
An option set is a set of radio buttons where only one of them can be active at a time, this means that when one
is clicked all the other ones are set to false, an option set must be defined before hand and will always store
its state but is not limited by how it can categorize the store.
First you must register the store with a name and a update callback, and an optional function for categorize:
local example_option_set =
Gui.new_option_set('example-option-set',function(value,category)
game.print('Example options set '..category..' is now: '..tostring(value))
end,Gui.player_store)
Then you must register some radiobutton defines and include them in the option set:
local example_option_one =
Gui.new_radiobutton()
:set_caption('Option One')
:add_as_option(example_option_set,'One')
local example_option_two =
Gui.new_radiobutton()
:set_caption('Option Two')
:add_as_option(example_option_set,'Two')
Note that these radiobuttons can still have on_element_update events but this may result in a double trigger of events as
the option set update is always triggered; also add_store cant be used as the option set acts as the store however get
and set store will still work but will effect the option set rather than the indivual radiobuttons.
>>>> Functions
Checkbox.new_checkbox(name) --- Creates a new checkbox element define
Checkbox._prototype_checkbox:on_element_update(callback) --- Registers a handler for when an element instance updates
Checkbox._prototype_checkbox:on_store_update(callback) --- Registers a handler for when the stored value updates
Checkbox.new_radiobutton(name) --- Creates a new radiobutton element define
Checkbox._prototype_radiobutton:on_element_update(callback) --- Registers a handler for when an element instance updates
Checkbox._prototype_radiobutton:on_store_update(callback) --- Registers a handler for when the stored value updates
Checkbox._prototype_radiobutton:add_as_option(option_set,option_name) --- Adds this radiobutton to be an option in the given option set (only one can be true at a time)
Checkbox.new_option_set(name,callback,categorize) --- Registers a new option set that can be linked to radiobutotns (only one can be true at a time)
Checkbox.draw_option_set(name,element) --- Draws all radiobuttons that are part of an option set at once (Gui.draw will not work)
Checkbox.reset_radiobutton(element,exclude,recursive) --- Sets all radiobutotn in a element to false (unless excluded) and can act recursivly
Other functions present from expcore.gui.core
]]
local Gui = require 'expcore.gui.core'
local Store = require 'expcore.store'
local Game = require 'utils.game'
--- Event call for on_checked_state_changed and store update
-- @tparam define table the define that this is acting on
-- @tparam element LuaGuiElement the element that triggered the event
-- @tparam value boolean the new state of the checkbox
local function event_call(define,element,value)
if define.events.on_element_update then
local player = Game.get_player_by_index(element.player_index)
define.events.on_element_update(player,element,value)
end
end
--- Store call for store update
-- @tparam define table the define that this is acting on
-- @tparam element LuaGuiElement the element that triggered the event
-- @tparam value boolean the new state of the checkbox
local function store_call(define,element,value)
element.state = value
event_call(define,element,value)
end
local Checkbox = {
option_sets={},
option_categorize={},
_prototype_checkbox=Gui._prototype_factory{
on_element_update = Gui._event_factory('on_element_update'),
on_store_update = Gui._event_factory('on_store_update'),
add_store = Gui._store_factory(store_call),
add_sync_store = Gui._sync_store_factory(store_call)
},
_prototype_radiobutton=Gui._prototype_factory{
on_element_update = Gui._event_factory('on_element_update'),
on_store_update = Gui._event_factory('on_store_update'),
add_store = Gui._store_factory(store_call),
add_sync_store = Gui._sync_store_factory(store_call)
}
}
--- Creates a new checkbox element define
-- @tparam[opt] name string the optional debug name that can be added
-- @treturn table the new checkbox element define
function Checkbox.new_checkbox(name)
local self = Gui._define_factory(Checkbox._prototype_checkbox)
self.draw_data.type = 'checkbox'
self.draw_data.state = false
if name then
self:debug_name(name)
end
self.post_draw = function(element)
if self.store then
local category = self.categorize and self.categorize(element) or nil
local state = self:get_store(category,true)
if state then element.state = true end
end
end
Gui.on_checked_state_changed(self.name,function(event)
local element = event.element
if self.option_set then
local value = Checkbox.option_sets[self.option_set][element.name]
local category = self.categorize and self.categorize(element) or value
self:set_store(category,value)
elseif self.store then
local value = element.state
local category = self.categorize and self.categorize(element) or value
self:set_store(category,value)
else
local value = element.state
event_call(self,element,value)
end
end)
return self
end
--- Creates a new radiobutton element define, has all functions checkbox has
-- @tparam[opt] name string the optional debug name that can be added
-- @treturn table the new button element define
function Checkbox.new_radiobutton(name)
local self = Checkbox.new_checkbox(name)
self.draw_data.type = 'radiobutton'
local mt = getmetatable(self)
mt.__index = Checkbox._prototype_radiobutton
return self
end
--- Adds this radiobutton to be an option in the given option set (only one can be true at a time)
-- @tparam option_set string the name of the option set to add this element to
-- @tparam option_name string the name of this option that will be used to idenitife it
-- @tparam self the define to allow chaining
function Checkbox._prototype_radiobutton:add_as_option(option_set,option_name)
self.option_set = option_set
self.option_name = option_name or self.name
Checkbox.option_sets[option_set][self.option_name] = self.name
Checkbox.option_sets[option_set][self.name] = self.option_name
self:add_store(Checkbox.option_categorize[option_set])
return self
end
--- Gets the stored value of the radiobutton or the option set if present
-- @tparam category[opt] string the category to get such as player name or force name
-- @treturn any the value that is stored for this define
function Checkbox._prototype_radiobutton:get_store(category,internal)
if not self.store then return end
local location = not internal and self.option_set or self.store
if self.categorize then
return Store.get_child(location,category)
else
return Store.get(location)
end
end
--- Sets the stored value of the radiobutton or the option set if present
-- @tparam category[opt] string the category to get such as player name or force name
-- @tparam value any the value to set for this define, must be valid for its type ie boolean for checkbox etc
-- @treturn boolean true if the value was set
function Checkbox._prototype_radiobutton:set_store(category,value,internal)
if not self.store then return end
local location = not internal and self.option_set or self.store
if self.categorize then
return Store.set_child(location,category,value)
else
return Store.set(location,category)
end
end
--- Registers a new option set that can be linked to radiobutotns (only one can be true at a time)
-- @tparam name string the name of the option set, must be unique
-- @tparam callback function the update callback when the value of the option set chagnes
-- callback param - value string - the new selected option for this option set
-- callback param - category string - the category that updated if categorize was used
-- @tpram categorize function the function used to convert an element into a string
-- @treturn string the name of this option set to be passed to add_as_option
function Checkbox.new_option_set(name,callback,categorize)
Store.register(name,function(value,category)
local options = Checkbox.option_sets[name]
for opt_name,define_name in pairs(options) do
if Gui.defines[define_name] then
local define = Gui.get_define(define_name)
local state = opt_name == value
define:set_store(category,state,true)
end
end
callback(value,category)
end)
Checkbox.option_categorize[name] = categorize
Checkbox.option_sets[name] = {}
return name
end
--- Draws all radiobuttons that are part of an option set at once (Gui.draw will not work)
-- @tparam name string the name of the option set to draw the radiobuttons of
-- @tparam element LuaGuiElement the parent element that the radiobuttons will be drawn to
function Checkbox.draw_option_set(name,element)
if not Checkbox.option_sets[name] then return end
local options = Checkbox.option_sets[name]
for _,option in pairs(options) do
if Gui.defines[option] then
Gui.defines[option]:draw_to(element)
end
end
end
--- Sets all radiobutotn in a element to false (unless excluded) and can act recursivly
-- @tparam element LuaGuiElement the root gui element to start setting radio buttons from
-- @tparam[opt] exclude ?string|table the name of the radiobutton to exclude or a table of radiobuttons where true will set the state true
-- @tparam[opt=false] recursive boolean if true will recur as much as possible, if a number will recur that number of times
-- @treturn boolean true if successful
function Checkbox.reset_radiobuttons(element,exclude,recursive)
if not element or not element.valid then return end
exclude = type(exclude) == 'table' and exclude or exclude ~= nil and {[exclude]=true} or {}
recursive = type(recursive) == 'number' and recursive-1 or recursive
for _,child in pairs(element.children) do
if child and child.valid and child.type == 'radiobutton' then
local state = exclude[child.name] or false
local define = Gui.defines[child.name]
if define then
local category = define.categorize and define.categorize(child) or state
define:set_store(category,state)
else
child.state = state
end
elseif child.children and (type(recursive) == 'number' and recursive >= 0 or recursive == true) then
Checkbox.reset_radiobutton(child,exclude,recursive)
end
end
return true
end
return Checkbox

513
expcore/Gui/core.lua Normal file
View File

@@ -0,0 +1,513 @@
--- Core gui file for making element defines and element classes (use require 'expcore.gui')
-- see utils.gui for event handlering
-- see expcore.gui.test for examples for element defines
--[[
>>>> Basic useage with no defines
This module can be igroned if you are only wanting only event handlers as utils.gui adds the following:
Gui.uid_name() --- Generates a unqiue name to register events to
Gui.on_checked_state_changed(callback) --- Register a handler for the on_gui_checked_state_changed event
Gui.on_click(callback) --- Register a handler for the on_gui_click event
Gui.on_elem_changed(callback) --- Register a handler for the on_gui_elem_changed
Gui.on_selection_state_changed(callback) --- Register a handler for the on_gui_selection_state_changed event
Gui.on_text_changed(callback) --- Register a handler for the on_gui_text_changed event
Gui.on_value_changed(callback) --- Register a handler for the on_gui_value_changed event
Note that all event handlers will include event.player as a valid player and that if the player or the
element is not valid then the callback will not be run.
>>>> Interal factory functions
There are a few factory function that are used by the class definations the use of these function are important to
know about but should only be used when making a new class deination rather than an element defination. See one of
the existing class definations for an example of when to use these.
>>>> Basic prototype functions
Using a class defination you can create a new element dinfation in our examples we will be using the checkbox.
local checkbox_example = Gui.new_checkbox()
Although all class definations are stored in Gui.classes the main function used to make new element defination are
made aviable in the top level gui module. All functions which return a new element defination will accept a name argument
which is a name which is used while debuging and is not required to be used (has not been used in examples)
Every element define will accept a caption and tooltip (although some may not show) and to do this you would use the two
set function provided for the element defines:
checkbox_example:set_caption('Example Checkbox')
checkbox_example:set_tooltip('Example checkbox')
Each element define can have event handlers set, for our example checkbox we only have access to on_change which will trigger
when the state of the checkbox changes; if we want to assign handlers using the utils.gui methods then we can get the uid by calling
the uid function on the element define; however, each element can only have one handler (of each event) so it is not possible to use
Gui.on_checked_state_changed and on_change at the same time in our example.
checkbox_example:on_change(function(player,element,value)
player.print('Example checkbox is now: '..tostring(value))
end)
local checkbox_example_uid = checkbox_example:uid()
Gui.on_click(checkbox_example_uid,function(event)
event.player.print('You clicked the example checkbox!')
end)
Finally you will want to draw your element defines for which you can call deirectly on the deinfe or use Gui.draw to do; when Gui.draw is
used it can be given either the element define, the define's uid or the debug name of the define (if set):
checkbox_example:draw_to(parent_element)
Gui.draw(checkbox_example_uid,parent_element)
>>>> Using authenticators with draw
When an element is drawn to its parent it can always be used but if you want to limit who can use it then you can use an authenticator. There
are two types which can be used: post and pre; using a pre authenticator will mean that the draw function is stoped before the element is added
to the parent element while using a post authenticator will draw the element to the parent but will disable the element from interaction. Both may
be used if you have use for such.
-- unless global.checkbox_example_allow_pre_auth is true then the checkbox will not be drawn
checkbox_example:set_pre_authenticator(function(player,define_name)
player.print('Example checkbox pre auth callback ran')
return global.checkbox_example_allow_pre_auth
end)
-- unless global.checkbox_example_allow_post_auth is true then the checkbox will be drawn but deactiveated (provided pre auth returns true)
checkbox_example:set_post_authenticator(function(player,define_name)
player.print('Example checkbox pre auth callback ran')
return global.checkbox_example_allow_post_auth
end)
>>>> Using store
A powerful assept of this gui system is allowing an automatic store for the state of a gui element, this means that when a gui is closed and re-opened
the elements which have a store will retain they value even if the element was previously destroied. The store is not limited to only per player and can
be catergorised by any method you want such as one that is shared between all players or by all players on a force. Using a method that is not limited to
one player means that when one player changes the state of the element it will be automaticlly updated for all other player (even if the element is already drawn)
and so this is a powerful and easy way to sync gui elements.
-- note the example below is the same as checkbox_example:add_store(Gui.player_store)
checkbox_example:add_store(function(element)
local player = Game.get_player_by_index(element.player_index)
return player.force.name
end)
Of course this tool is not limited to only player interactions; the current satate of a define can be gotten using a number of methods and the value can
even be updated by the script and have all instances of the element define be updated. When you use a category then we must give a category to the get
and set functions; in our case we used Gui.player_store which uses the player's name as the category which is why 'Cooldude2606' is given as a argument,
if we did not set a function for add_store then all instances for all players have the same value and so a category is not required.
checkbox_example:get_store('Cooldude2606')
Gui.get_store(name,'Cooldude2606')
checkbox_example:set_store('Cooldude2606',true)
Gui.set_store(name,'Cooldude2606',true)
These methods use the Store module which means that if you have the need to access these sotre location (for example if you want to add a watch function) then
you can get the store location of any define using checkbox_example.store
Important note about event handlers: when the store is updated it will also trigger the event handlers (such as on_element_update) for that define but only
for the valid instances of the define which means if a player does not have the element drawn on a gui then it will not trigger the events; if you want a
trigger for all updates then you can use on_store_update however you will be required to parse the category which may or may not be a
player name (depends what store categorize function you use)
>>>> Example formating
local checkbox_example =
Gui.new_checkbox()
:set_caption('Example Checkbox')
:set_tooltip('Example checkbox')
:add_store(Gui.player_store)
:on_element_update(function(player,element,value)
player.print('Example checkbox is now: '..tostring(value))
end)
>>>> Functions
Gui._prototype_factory(tbl) --- Used internally to create new prototypes for element defines
Gui._event_factory(name) --- Used internally to create event handler adders for element defines
Gui._store_factory(callback) --- Used internally to create store adders for element defines
Gui._sync_store_factory(callback) --- Used internally to create synced store adders for element defines
Gui._define_factory(prototype) --- Used internally to create new element defines from a class prototype
Gui._prototype:uid() --- Gets the uid for the element define
Gui._prototype:debug_name(name) --- Sets a debug alias for the define
Gui._prototype:set_caption(caption) --- Sets the caption for the element define
Gui._prototype:set_tooltip(tooltip) --- Sets the tooltip for the element define
Gui._prototype:on_element_update(callback) --- Add a hander to run on the general value update event, different classes will handle this event differently
Gui._prototype:set_pre_authenticator(callback) --- Sets an authenticator that blocks the draw function if check fails
Gui._prototype:set_post_authenticator(callback) --- Sets an authenticator that disables the element if check fails
Gui._prototype:draw_to(element) --- Draws the element using what is in the draw_data table, allows use of authenticator if present, registers new instances if store present
Gui.draw(name,element) --- Draws a copy of the element define to the parent element, see draw_to
Gui._prototype:add_store(categorize) --- Adds a store location for the define that will save the state of the element, categorize is a function that returns a string
Gui._prototype:add_sync_store(location,categorize) --- Adds a store location for the define that will sync between games, categorize is a function that returns a string
Gui._prototype:on_store_update(callback) --- Adds a event callback for when the store changes are other events are not gauenteted to be raised
Gui.player_store(element) --- A categorize function to be used with add_store, each player has their own value
Gui.force_store(element) --- A categorize function to be used with add_store, each force has its own value
Gui.surface_store(element) --- A categorize function to be used with add_store, each surface has its own value
Gui._prototype:get_store(category) --- Gets the value in this elements store, category needed if categorize function used
Gui._prototype:set_store(category,value) --- Sets the value in this elements store, category needed if categorize function used
Gui.get_store(name,category) --- Gets the value that is stored for a given element define, category needed if categorize function used
Gui.set_store(name,category,value) --- Sets the value stored for a given element define, category needed if categorize function used
Gui.toggle_enable(element) --- Will toggle the enabled state of an element
Gui.toggle_visible(element) --- Will toggle the visiblity of an element
]]
local Gui = require 'utils.gui'
local Game = require 'utils.game'
local Store = require 'expcore.store'
local Instances = require 'expcore.gui.instances'
Gui._prototype = {} -- Stores the base prototype of all element defines
Gui.classes = {} -- Stores the class definations used to create element defines
Gui.defines = {} -- Stores the indivdual element definations
Gui.names = {} -- Stores debug names to link to gui uids
--- Used internally to create new prototypes for element defines
-- @tparam tbl table a table that will have functions added to it
-- @treturn table the new table with the keys added to it
function Gui._prototype_factory(tbl)
for k,v in pairs(Gui._prototype) do
if not tbl[k] then tbl[k] = v end
end
return tbl
end
--- Used internally to create event handler adders for element defines
-- @tparam name string the key that the event will be stored under, should be the same as the event name
-- @treturn function the function that can be used to add an event handler
function Gui._event_factory(name)
--- Gui._prototype:on_event(callback)
--- Add a hander to run on this event, replace event with the event, different classes have different events
-- @tparam callback function the function that will be called on the event
-- callback param - player LuaPlayer - the player who owns the gui element
-- callback param - element LuaGuiElement - the element that caused the event
-- callback param - value any - (not always present) the updated value for the element
-- callback param - ... any - other class defines may add more params
-- @treturn self the element define to allow chaining
return function(self,callback)
if type(callback) ~= 'function' then
return error('Event callback must be a function',2)
end
self.events[name] = callback
return self
end
end
--- Used internally to create store adders for element defines
-- @tparam callback a callback is called when there is an update to the stored value and stould set the state of the element
-- @treturn function the function that can be used to add a store the the define
function Gui._store_factory(callback)
--- Gui._prototype:add_store(categorize)
--- Adds a store location for the define that will save the state of the element, categorize is a function that returns a string
-- @tparam[opt] categorize function if present will be called to convert an element into a category string
-- categorize param - element LuaGuiElement - the element that needs to be converted
-- categorize return - string - a determistic string that referses to a category such as player name or force name
-- @treturn self the element define to allow chaining
return function(self,categorize)
if self.store then return end
self.store = Store.uid_location()
self.categorize = categorize
Instances.register(self.name,self.categorize)
Store.register(self.store,function(value,category)
if self.events.on_store_update then
self.events.on_store_update(value,category)
end
if Instances.is_registered(self.name) then
Instances.apply_to_elements(self.name,category,function(element)
callback(self,element,value)
end)
end
end)
return self
end
end
--- Used internally to create synced store adders for element defines
-- @tparam callback a callback is called when there is an update to the stored value and stould set the state of the element
-- @treturn function the function that can be used to add a sync store the the define
function Gui._sync_store_factory(callback)
--- Gui._prototype:add_sync_store(location,categorize)
--- Adds a store location for the define that will sync between games, categorize is a function that returns a string
-- @tparam location string a unique string location, unlike add_store a uid location should not be used to avoid migration problems
-- @tparam[opt] categorize function if present will be called to convert an element into a category string
-- categorize param - element LuaGuiElement - the element that needs to be converted
-- categorize return - string - a determistic string that referses to a category such as player name or force name
-- @treturn self the element define to allow chaining
return function(self,location,categorize)
if self.store then return end
if Store.is_registered(location) then
return error('Location for store is already registered: '..location,2)
end
self.store = location
self.categorize = categorize
Instances.register(self.name,self.categorize)
Store.register_synced(self.store,function(value,category)
if self.events.on_store_update then
self.events.on_store_update(value,category)
end
if Instances.is_registered(self.name) then
Instances.apply_to_elements(self,category,function(element)
callback(self,element,value)
end)
end
end)
return self
end
end
--- Used internally to create new element defines from a class prototype
-- @tparam prototype table the class prototype that will be used for the element define
-- @treturn table the new element define with all functions accessed via __index metamethod
function Gui._define_factory(prototype)
local uid = Gui.uid_name()
local define = setmetatable({
name=uid,
events={},
draw_data={
name=uid
}
},{
__index=prototype,
__call=function(self,element,...)
return self:draw_to(element,...)
end
})
Gui.defines[define.name] = define
return define
end
--- Gets the uid for the element define
-- @treturn string the uid of this element define
function Gui._prototype:uid()
return self.name
end
--- Sets a debug alias for the define
-- @tparam name string the debug name for the element define that can be used to get this element define
-- @treturn self the element define to allow chaining
function Gui._prototype:debug_name(name)
self.debug_name = name
return self
end
--- Sets the caption for the element define
-- @tparam caption string the caption that will be drawn with the element
-- @treturn self the element define to allow chaining
function Gui._prototype:set_caption(caption)
self.draw_data.caption = caption
return self
end
--- Sets the tooltip for the element define
-- @tparam tooltip string the tooltip that will be displayed for this element when drawn
-- @treturn self the element define to allow chaining
function Gui._prototype:set_tooltip(tooltip)
self.draw_data.tooltip = tooltip
return self
end
--- Sets an authenticator that blocks the draw function if check fails
-- @tparam callback function the function that will be ran to test if the element should be drawn or not
-- callback param - player LuaPlayer - the player that the element is being drawn to
-- callback param - define_name string - the name of the define that is being drawn
-- callback return - boolean - false will stop the element from being drawn
-- @treturn self the element define to allow chaining
function Gui._prototype:set_pre_authenticator(callback)
if type(callback) ~= 'function' then
return error('Pre authenticator callback must be a function')
end
self.pre_authenticator = callback
return self
end
--- Sets an authenticator that disables the element if check fails
-- @tparam callback function the function that will be ran to test if the element should be enabled or not
-- callback param - player LuaPlayer - the player that the element is being drawn to
-- callback param - define_name string - the name of the define that is being drawn
-- callback return - boolean - false will disable the element
-- @treturn self the element define to allow chaining
function Gui._prototype:set_post_authenticator(callback)
if type(callback) ~= 'function' then
return error('Authenicater callback must be a function')
end
self.post_authenticator = callback
return self
end
--- Draws the element using what is in the draw_data table, allows use of authenticator if present, registers new instances if store present
-- the data with in the draw_data is set up through the use of all the other functions
-- @tparam element LuaGuiElement the element that the define will draw a copy of its self onto
-- @treturn LuaGuiElement the new element that was drawn so styles can be applied
function Gui._prototype:draw_to(element,...)
if element[self.name] then return end
local player = Game.get_player_by_index(element.player_index)
if self.pre_authenticator then
if not self.pre_authenticator(player,self.name) then return end
end
local new_element = element.add(self.draw_data)
if self.post_authenticator then
new_element.enabled = self.post_authenticator(player,self.name)
end
if Instances.is_registered(self.name) then
Instances.add_element(self.name,new_element)
end
if self.post_draw then self.post_draw(new_element,...) end
return new_element
end
--- Gets the value in this elements store, category needed if categorize function used
-- @tparam category[opt] string the category to get such as player name or force name
-- @treturn any the value that is stored for this define
function Gui._prototype:get_store(category)
if not self.store then return end
if self.categorize then
return Store.get_child(self.store,category)
else
return Store.get(self.store)
end
end
--- Sets the value in this elements store, category needed if categorize function used
-- @tparam category[opt] string the category to get such as player name or force name
-- @tparam value any the value to set for this define, must be valid for its type ie boolean for checkbox etc
-- @treturn boolean true if the value was set
function Gui._prototype:set_store(category,value)
if not self.store then return end
if self.categorize then
return Store.set_child(self.store,category,value)
else
return Store.set(self.store,category)
end
end
--- Gets an element define give the uid, debug name or a copy of the element define
-- @tparam name ?string|table the uid, debug name or define for the element define to get
-- @tparam[opt] internal boolean when true the error trace is one level higher (used internally)
-- @treturn table the element define that was found or an error
function Gui.get_define(name,internal)
if type(name) == 'table' then
if name.name and Gui.defines[name.name] then
return Gui.defines[name.name]
end
end
local define = Gui.defines[name]
if not define and Gui.names[name] then
return Gui.defines[Gui.names[name]]
elseif not define then
return error('Invalid name for element define, name not found.',internal and 3 or 2) or nil
end
return define
end
--- Gets the value that is stored for a given element define, category needed if categorize function used
-- @tparam name ?string|table the uid, debug name or define for the element define to get
-- @tparam[opt] category string the category to get the value for
-- @treturn any the value that is stored for this define
function Gui.get_store(name,category)
local define = Gui.get_define(name,true)
return define:get_store(category)
end
--- Sets the value stored for a given element define, category needed if categorize function used
-- @tparam name ?string|table the uid, debug name or define for the element define to set
-- @tparam[opt] category string the category to set the value for
-- @tparam value any the value to set for the define, must be valid for its type ie boolean for a checkbox
-- @treturn boolean true if the value was set
function Gui.set_store(name,category,value)
local define = Gui.get_define(name,true)
return define:get_store(category,value)
end
--- A categorize function to be used with add_store, each player has their own value
-- @tparam element LuaGuiElement the element that will be converted to a string
-- @treturn string the player's name who owns this element
function Gui.player_store(element)
local player = Game.get_player_by_index(element.player_index)
return player.name
end
--- A categorize function to be used with add_store, each force has its own value
-- @tparam element LuaGuiElement the element that will be converted to a string
-- @treturn string the player's force name who owns this element
function Gui.force_store(element)
local player = Game.get_player_by_index(element.player_index)
return player.force.name
end
--- A categorize function to be used with add_store, each surface has its own value
-- @tparam element LuaGuiElement the element that will be converted to a string
-- @treturn string the player's surface name who owns this element
function Gui.surface_store(element)
local player = Game.get_player_by_index(element.player_index)
return player.surface.name
end
--- Draws a copy of the element define to the parent element, see draw_to
-- @tparam name ?string|table the uid, debug name or define for the element define to draw
-- @tparam element LuaGuiEelement the parent element that it the define will be drawn to
-- @treturn LuaGuiElement the new element that was created
function Gui.draw(name,element,...)
local define = Gui.get_define(name,true)
return define:draw_to(element,...)
end
--- Will toggle the enabled state of an element
-- @tparam element LuaGuiElement the gui element to toggle
function Gui.toggle_enable(element)
if not element or not element.valid then return end
if not element.enabled then
element.enabled = true
else
element.enabled = false
end
end
--- Will toggle the visiblity of an element
-- @tparam element LuaGuiElement the gui element to toggle
function Gui.toggle_visible(element)
if not element or not element.valid then return end
if not element.visible then
element.visible = true
else
element.visible = false
end
end
--- Sets the padding for a gui element
-- @tparam element LuaGuiElement the element to set the padding for
-- @tparam[opt=0] up number the amount of padding on the top
-- @tparam[opt=0] down number the amount of padding on the bottom
-- @tparam[opt=0] left number the amount of padding on the left
-- @tparam[opt=0] right number the amount of padding on the right
function Gui.set_padding(element,up,down,left,right)
local style = element.style
style.top_padding = up or 0
style.bottom_padding = down or 0
style.left_padding = left or 0
style.right_padding = right or 0
end
return Gui

189
expcore/Gui/dropdown.lua Normal file
View File

@@ -0,0 +1,189 @@
--- Gui class define for dropdowns and list boxs
--[[
>>>> Functions
Dropdown.new_dropdown(name) --- Creates a new dropdown element define
Dropdown.new_list_box(name) --- Creates a new list box element define
Dropdown._prototype:on_element_update(callback) --- Registers a handler for when an element instance updates
Dropdown._prototype:on_store_update(callback) --- Registers a handler for when the stored value updates
Dropdown._prototype:new_static_options(options,...) --- Adds new static options to the dropdown which will trigger the general callback
Dropdown._prototype:new_dynamic_options(callback) --- Adds a callback which should return a table of values to be added as options for the dropdown (appended after static options)
Dropdown._prototype:add_option_callback(option,callback) --- Adds a case specific callback which will only run when that option is selected (general case still triggered)
Dropdown.select_value(element,value) --- Selects the option from a dropdown or list box given the value rather than key
Dropdown.get_selected_value(element) --- Returns the currently selected value rather than index
Other functions present from expcore.gui.core
]]
local Gui = require 'expcore.gui.core'
local Game = require 'utils.game'
--- Event call for on_selection_state_changed and store update
-- @tparam define table the define that this is acting on
-- @tparam element LuaGuiElement the element that triggered the event
-- @tparam value string the new option for the dropdown
local function event_call(define,element,value)
local player = Game.get_player_by_index(element.player_index)
if define.events.on_element_update then
define.events.on_element_update(player,element,value)
end
if define.option_callbacks and define.option_callbacks[value] then
define.option_callbacks[value](player,element,value)
end
end
--- Store call for store update
-- @tparam define table the define that this is acting on
-- @tparam element LuaGuiElement the element that triggered the event
-- @tparam value string the new option for the dropdown
local _select_value
local function store_call(define,element,value)
_select_value(element,value)
event_call(define,element,value)
end
local Dropdown = {
_prototype=Gui._prototype_factory{
on_element_update = Gui._event_factory('on_element_update'),
on_store_update = Gui._event_factory('on_store_update'),
add_store = Gui._store_factory(store_call),
add_sync_store = Gui._sync_store_factory(store_call)
}
}
--- Creates a new dropdown element define
-- @tparam[opt] name string the optional debug name that can be added
-- @treturn table the new dropdown element define
function Dropdown.new_dropdown(name)
local self = Gui._define_factory(Dropdown._prototype)
self.draw_data.type = 'drop-down'
if name then
self:debug_name(name)
end
self.post_draw = function(element)
if self.dynamic_options then
local player = Game.get_player_by_index(element.player_index)
local dynamic_options = self.dynamic_options(player,element)
local items = element.items
for _,v in pairs(dynamic_options) do
table.insert(items,v)
end
element.items = items
end
if self.store then
local category = self.categorize and self.categorize(element) or nil
local value = self:get_store(category)
if value then Dropdown.select_value(element,value) end
end
end
Gui.on_selection_state_changed(self.name,function(event)
local element = event.element
local value = Dropdown.get_selected_value(element)
if self.store then
local category = self.categorize and self.categorize(element) or value
self:set_store(category,value)
else
event_call(self,element,value)
end
end)
return self
end
--- Creates a new list box element define
-- @tparam[opt] name string the optional debug name that can be added
-- @treturn table the new list box element define
function Dropdown.new_list_box(name)
local self = Dropdown.new_dropdown(name)
self.draw_data.type = 'list-box'
return self
end
--- Adds new static options to the dropdown which will trigger the general callback
-- @tparam options ?string|table either a table of option strings or the first option string, with a table values are the options
-- @tparam[opt] ... when options is not a table you can add the options one after each other
-- @tparam self the define to allow chaining
function Dropdown._prototype:new_static_options(options,...)
if type(options) == 'string' then
options = {options}
for _,v in pairs({...}) do
table.insert(options,v)
end
end
self.options = options
self.draw_data.items = options
return self
end
Dropdown._prototype.add_options = Dropdown._prototype.new_static_options
--- Adds a callback which should return a table of values to be added as options for the dropdown (appended after static options)
-- @tparam callback function the function that will run to get the options for the dropdown
-- callback param - player LuaPlayer - the player that the element is being drawn to
-- callback param - element LuaGuiElement - the element that is being drawn
-- callback return - table - the values of this table will be appended to the static options of the dropdown
-- @tparam self the define to allow chaining
function Dropdown._prototype:new_dynamic_options(callback)
if type(callback) ~= 'function' then
return error('Dynamic options callback must be a function',2)
end
self.dynamic_options = callback
return self
end
Dropdown._prototype.add_dynamic = Dropdown._prototype.new_dynamic_options
--- Adds a case specific callback which will only run when that option is selected (general case still triggered)
-- @tparam option string the name of the option to trigger the callback on; if not already added then will be added as an option
-- @tparam callback function the function that will be called when that option is selected
-- callback param - player LuaPlayer - the player who owns the gui element
-- callback param - element LuaGuiElement - the element which is being effected
-- callback param - value string - the new option that has been selected
-- @tparam self the define to allow chaining
function Dropdown._prototype:add_option_callback(option,callback)
if not self.option_callbacks then self.option_callbacks = {} end
if not self.options then self.options = {} end
self.option_callbacks[option] = callback
if not table.contains(self.options,option) then
table.insert(self.options,option)
end
return self
end
--- Selects the option from a dropdown or list box given the value rather than key
-- @tparam element LuaGuiElement the element that contains the option
-- @tparam value string the option to select from the dropdown
-- @treturn number the key where the value was
function Dropdown.select_value(element,value)
for k,item in pairs(element.items) do
if item == value then
element.selected_index = k
return k
end
end
end
_select_value = Dropdown.select_value
--- Returns the currently selected value rather than index
-- @tparam element LuaGuiElement the gui element that you want to get the value of
-- @treturn string the value that is currently selected
function Dropdown.get_selected_value(element)
local index = element.selected_index
return element.items[index]
end
return Dropdown

111
expcore/Gui/elem-button.lua Normal file
View File

@@ -0,0 +1,111 @@
--- Gui class defines for elem buttons
--[[
>>>> Functions
ElemButton.new_elem_button(name) --- Creates a new elem button element define
ElemButton._prototype:on_element_update(callback) --- Registers a handler for when an element instance updates
ElemButton._prototype:on_store_update(callback) --- Registers a handler for when the stored value updates
ElemButton._prototype:set_type(type) --- Sets the type of the elem button, the type is required so this must be called at least once
ElemButton._prototype:set_default(value) --- Sets the default value for the elem button, this may be a function or a string
Other functions present from expcore.gui.core
]]
local Gui = require 'expcore.gui.core'
local Game = require 'utils.game'
--- Event call for on_elem_changed and store update
-- @tparam define table the define that this is acting on
-- @tparam element LuaGuiElement the element that triggered the event
-- @tparam value string the new value for the elem button
local function event_call(define,element,value)
local player = Game.get_player_by_index(element.player_index)
if define.events.on_element_update then
define.events.on_element_update(player,element,value)
end
end
--- Store call for store update
-- @tparam define table the define that this is acting on
-- @tparam element LuaGuiElement the element that triggered the event
-- @tparam value string the new value for the elem button
local function store_call(self,element,value)
element.elem_value = value
event_call(self,element,value)
end
local ElemButton = {
_prototype=Gui._prototype_factory{
on_element_update = Gui._event_factory('on_element_update'),
on_store_update = Gui._event_factory('on_store_update'),
add_store = Gui._store_factory(store_call),
add_sync_store = Gui._sync_store_factory(store_call)
}
}
--- Creates a new elem button element define
-- @tparam[opt] name string the optional debug name that can be added
-- @treturn table the new elem button element define
function ElemButton.new_elem_button(name)
local self = Gui._define_factory(ElemButton._prototype)
self.draw_data.type = 'choose-elem-button'
if name then
self:debug_name(name)
end
self.post_draw = function(element)
local player = Game.get_player_by_index(element.player_index)
if type(self.default) == 'function' then
element.elem_value = self.default(player,element)
end
if self.store then
local category = self.categorize and self.categorize(element) or nil
local value = self:get_store(category)
if value then element.elem_value = value end
end
end
Gui.on_elem_changed(self.name,function(event)
local element = event.element
local value = element.elem_value
if self.store then
local category = self.categorize and self.categorize(element) or value
self:set_store(category,value)
else
event_call(self,element,value)
end
end)
return self
end
--- Sets the type of the elem button, the type is required so this must be called at least once
-- @tparam type string the type that this elem button is see factorio api
-- @treturn the element define to allow for chaining
function ElemButton._prototype:set_type(type)
self.draw_data.elem_type = type
return self
end
--- Sets the default value for the elem button, this may be a function or a string
-- @tparam value ?string|function a string will be a static default and a function will be called when drawn to get the default
-- @treturn the element define to allow for chaining
function ElemButton._prototype:set_default(value)
self.default = value
if type(value) ~= 'function' then
self.draw_data[self.draw_data.elem_type] = value
end
return self
end
return ElemButton

227
expcore/Gui/instances.lua Normal file
View File

@@ -0,0 +1,227 @@
--- This file is a breakout from core which forcues on instance management of defines
--[[
>>>> Using registered instance groups
The main use of this module is to register a group of elements refered here as "instances of an element define" in which
is meant that you define the name of a group of drawn elements that are really just multiple versions of a single element.
For example this might be that you have one label in multiple places (either for one player or many) and you want to update
the caption of all of them at once; this is where this module comes it.
First you must register the way that the intances are stored and under what name, using Instances.register you will give the
name of the collective group of instances followed by an optional categorise function which allows varients to be stored under one
name (like one for each force or player)
-- categorise works in the same way as store categorise
-- so the function will worl here but no value is stored only gui elements
Instances.register('score',Gui.force_store)
Then when you draw the new element to a gui you will want to add the element to the group:
Instances.add_element('score',new_element)
Then when you want to get the instances you have two options; Instances.get_elements or Instances.apply_to_elements when you want loop
over the elements it is more efficient to use apply_to_elements:
Instances.get_elements('score','player') -- returns all elements that were added with the 'player' category
Instances.apply_to_elements('score','player',function(element) -- runs the function on every valid element
element.caption = 0
end)
Note that if you dont give a categorise function then you dont need to give a category when getting the elements.
>>>> Using unregistered instance groups
When using a registered group and the functions that go with them it is much simpler to use and more importantly includes error checking
for valid instance group names; the down side is that the group must be registered which can only be done during startup and not during runtime.
To counter this there are two functions simlair to those above in order to add and get instances but may lead to errors not being noticed due to
the error interal error checking being skiped to allow it to work.
The main difference between the two groups of functions is that the category must always be present even if is nil; example below shows how a
instance group would work when registered vs unregistered:
-- Registered with category
Instances.register('score',Gui.force_store) -- force_store will return the force name of an element
Instances.add_element('score',new_element) -- the new element is added to the category based on in force
Instances.apply_to_elements('score','player',function(element)
element.caption = '0'
end) -- gets all instances from the player force and sets the caption to 0
-- Unregistered with category
Instances.unregistered_add_element('score','player',new_element) -- adds the new element to the player category
Instances.unregistered_apply_to_elements('score','player',function(element)
element.caption = '0'
end) -- gets all instances from the player force and sets the caption to 0
-- Registered without category; note that category can just be igroned
Instances.register('score') -- all instances will be under one group with no categories
Instances.add_element('score',new_element) -- adds the new element to the instance list
Instances.apply_to_elements('score',function(element)
element.caption = '0'
end) -- gets all instances and sets the element caption to 0
-- Unregistered without category; note that category must be given as nil
Instances.unregistered_add_element('score',nil,new_element) -- adds the new element to a single group with no categories
Instances.unregistered_apply_to_elements('score',nil,function(element)
element.caption = '0'
end) -- gets all instances and sets the element caption to 0
>>>> Functions
Instances.has_categories(name) --- Returns if a instnace group has a categorise function; must be registerd
Instances.is_registered(name) --- Returns if the given name is a registered instance group
Instances.register(name,categorise) --- Registers the name of an instance group to allow for storing element instances
Instances.add_element(name,element) --- Adds an element to the instance group under the correct category; must be registered
Instances.get_elements_raw(name,category) --- Gets all element instances without first removing any invalid ones; used internally and must be registered
Instances.get_valid_elements(name,category,callback) --- Gets all valid element instances and has the option of running a callback on those that are valid
Instances.unregistered_add_element(name,category,element) --- A version of add_element that does not require the group to be registered
Instances.unregistered_get_elements(name,category,callback) --- A version of get_elements that does not require the group to be registered
]]
local Global = require 'utils.global'
local Instances = {
categorise={},
data={}
}
Global.register(Instances.data,function(tbl)
Instances.data = tbl
end)
--- Returns if a instnace group has a categorise function; must be registerd
-- @tparam name string the name of the instance group
-- @treturn boolean true if there is a categorise function
function Instances.has_categories(name)
return type(Instances.categorise[name]) == 'function'
end
--- Returns if the given name is a registered instance group
-- @tparam name string the name of the instance group you are testing
-- @treturn boolean true if the name is registered
function Instances.is_registered(name)
return Instances.categorise[name] ~= nil
end
--- Registers the name of an instance group to allow for storing element instances
-- @tparam name string the name of the instance group; must to unique
-- @tparam[opt] categorise function function used to turn the element into a string
-- categorise param - element LuaGuiElement - the gui element to be turned into a string
-- categorise return - string - the category that the element will be added to like the player's name or force's name
-- @treturn string the name that was added so it can be used as a varible
function Instances.register(name,categorise)
if _LIFECYCLE ~= _STAGE.control then
return error('Can only be called during the control stage', 2)
end
if Instances.categorise[name] then
return error('Instances for '..name..' already exist.',2)
end
categorise = type(categorise) == 'function' and categorise or true
Instances.data[name] = {}
Instances.categorise[name] = categorise
return name
end
--- Adds an element to the instance group under the correct category; must be registered
-- @tparam name string the name of the instance group to add the element to
-- @tparam element LuaGuiElement the element to add the the instance group
function Instances.add_element(name,element)
if not Instances.categorise[name] then
return error('Inavlid name for instance group: '..name,2)
end
if Instances.has_categories(name) then
local category = Instances.categorise[name](element)
if not Instances.data[name][category] then Instances.data[name][category] = {} end
table.insert(Instances.data[name][category],element)
else
table.insert(Instances.data[name],element)
end
end
--- Gets all element instances without first removing any invalid ones; used internally and must be registered
-- @tparam name string the name of the instance group to get the instances of
-- @tparam[opt] category string the category to get the instance from, not needed when no categorise function
-- @treturn table the table of element instances of which some may be invalid
function Instances.get_elements_raw(name,category)
if not Instances.categorise[name] then
return error('Inavlid name for instance group: '..name,2)
end
if Instances.has_categories(name) then
return Instances.data[name][category] or {}
else
return Instances.data[name]
end
end
--- Gets all valid element instances and has the option of running a callback on those that are valid
-- @tparam name string the name of the instance group to get the instances of
-- @tparam[opt] category string the category to get the instances of, not needed when no categorise function
-- @tparan[opt] callback function when given the callback will be ran on all valid elements
-- callback param - element LuaGuiElement - the current valid element
-- @treturn table the table of element instances with all invalid ones removed
function Instances.get_valid_elements(name,category,callback)
if not Instances.categorise[name] then
return error('Inavlid name for instance group: '..name,2)
end
category = category or callback
local elements = Instances.get_elements_raw(name,category)
local categorise = Instances.has_categories(name)
for key,element in pairs(elements) do
if not element or not element.valid then
elements[key] = nil
else
if categorise and callback then callback(element)
elseif category then category(element) end
end
end
return elements
end
Instances.get_elements = Instances.get_valid_elements
Instances.apply_to_elements = Instances.get_valid_elements
--- A version of add_element that does not require the group to be registered
-- @tparam name string the name of the instance group to add the element to
-- @tparam category ?string|nil the category to add the element to, can be nil but must still be given
-- @tparam element LuaGuiElement the element to add to the instance group
function Instances.unregistered_add_element(name,category,element)
if not Instances.data[name] then Instances.data[name] = {} end
if category then
if not Instances.data[name][category] then Instances.data[name][category] = {} end
table.insert(Instances.data[name][category],element)
else
table.insert(Instances.data[name],element)
end
end
--- A version of get_elements that does not require the group to be registered
-- @tparam name string the name of the instance group to get the instances of
-- @tparam category ?string|nil the category to get the instances of, can be nil but must still be given
-- @tparam[opt] callback function when given will be called on all valid instances
-- callback param - element LuaGuiElement - the current valid element
-- @treturn table the table of element instances with all invalid ones removed
function Instances.unregistered_get_elements(name,category,callback)
local elements = Instances.data[name]
if elements and category then
elements = elements[category]
end
if not elements then return {} end
for key,element in pairs(elements) do
if not element or not element.valid then
elements[key] = nil
else
if callback then callback(element) end
end
end
return elements
end
Instances.unregistered_apply_to_elements = Instances.runtime_get_elements
return Instances

290
expcore/Gui/left.lua Normal file
View File

@@ -0,0 +1,290 @@
--- Gui structure define for left frames
--[[
>>>> Example formating
-- first we add config that relates to the button on the toolbar, all normal button functions are present
local left_frame =
Gui.new_left_frame('test-left-frame')
:set_caption('Test Left Gui')
:set_post_authenticator(function(player,button_name)
return global.show_test_gui
end)
-- then we add the config for the left frame, on_draw should draw the gui from an empty frame, on_update should take a frame from on_draw on edit it
:set_open_by_default()
:on_draw(function(_player,frame)
for _,player in pairs(game.connected_players) do
frame.add{
type='label',
caption=player.name
}
end
end)
-- now we can use the action factory to call events on the gui, actions are: 'update', 'update_all', 'redraw', 'redraw_all'
Event.add(defines.events.on_player_joined_game,left_frame 'update_all')
Event.add(defines.events.on_player_left_game,left_frame 'update_all')
>>>> Functions
LeftFrames.get_flow(player) --- Gets the left frame flow for a player
LeftFrames.get_frame(name,player) --- Gets one frame from the left flow by its name
LeftFrames.get_open(player) --- Gets all open frames for a player, if non are open it will remove the close all button
LeftFrames.toggle_frame(name,player,state) --- Toggles the visiblty of a left frame, or sets its visiblty state
LeftFrames.new_frame(permision_name) --- Creates a new left frame define
LeftFrames._prototype:set_open_by_default(state) --- Sets if the frame is visible when a player joins, can also be a function to return a boolean
LeftFrames._prototype:get_frame(player) --- Gets the frame for this define from the left frame flow
LeftFrames._prototype:is_open(player) --- Returns if the player currently has this define visible
LeftFrames._prototype:toggle(player) --- Toggles the visiblty of the left frame
LeftFrames._prototype:update(player) --- Updates the contents of the left frame, first tries update callback, oter wise will clear and redraw
LeftFrames._prototype:update_all(update_offline) --- Updates the frame for all players, see update
LeftFrames._prototype:redraw(player) --- Redraws the frame by calling on_draw, will always clear the frame
LeftFrames._prototype:redraw_all(update_offline) --- Redraws the frame for all players, see redraw
LeftFrames._prototype:on_draw(player,frame) --- Use to draw your elements to the new frame
LeftFrames._prototype:on_update(player,frame) --- Use to edit your frame when there is no need to redraw it
LeftFrames._prototype:event_handler(action) --- Creates an event handler that will trigger one of its functions, use with Event.add
]]
local Gui = require 'expcore.gui.core'
local Toolbar = require 'expcore.gui.toolbar'
local Buttons = require 'expcore.gui.buttons'
local mod_gui = require 'mod-gui'
local Game = require 'utils.game'
local Event = require 'utils.event'
local LeftFrames = {
frames={},
_prototype=Gui._prototype_factory{
on_draw = Gui._event_factory('on_draw'),
on_update = Gui._event_factory('on_update')
}
}
setmetatable(LeftFrames._prototype, {
__index = Buttons._prototype
})
--- Gets the left frame flow for a player
-- @tparam player LuaPlayer the player to get the flow of
-- @treturn LuaGuiElement the left frame flow for the player
function LeftFrames.get_flow(player)
player = Game.get_player_from_any(player)
return mod_gui.get_frame_flow(player)
end
--- Gets one frame from the left flow by its name
-- @tparam name string the name of the gui frame to get
-- @tparam player LuaPlayer the player to get the frame of
-- @treturn LuaGuiElement the frame in the left frame flow with that name
function LeftFrames.get_frame(name,player)
local define = LeftFrames.frames[name]
if not define then
return error('Left Frame '..name..' is not defined.',2)
end
return define:get_frame(player)
end
--- Gets all open frames for a player, if non are open it will remove the close all button
-- @tparam player LuaPlayer the player to get the flow of
-- @treturn table contains all the open (and registered) frames for the player
function LeftFrames.get_open(player)
local open = {}
local flow = LeftFrames.get_flow(player)
for _,define in pairs(LeftFrames.frames) do
if define:is_open(player) then
table.insert(open,define)
end
end
flow[LeftFrames.toggle_button.name].visible = #open ~= 0
return open
end
--- Toggles the visiblty of a left frame, or sets its visiblty state
-- @tparam name string the name of the gui frame to toggle
-- @tparam player LuaPlayer the player to get the frame of
-- @tparam[opt] state boolean when given will be the state that the visiblty is set to
-- @treturn boolean the new state of the visiblity
function LeftFrames.toggle_frame(name,player,state)
local define = LeftFrames.frames[name]
if not define then
return error('Left Frame '..name..' is not defined.',2)
end
local frame = LeftFrames.get_frame(name,player)
if state ~= nil then
frame.visible = state
else
Gui.toggle_visible(frame)
end
LeftFrames.get_open(player)
return frame.visible
end
--- Creates a new left frame define
-- @tparam permision_name string the name that can be used with the permision system
-- @treturn table the new left frame define
function LeftFrames.new_frame(permision_name)
local self = Toolbar.new_button(permision_name)
local mt = getmetatable(self)
mt.__index = LeftFrames._prototype
mt.__call = self.event_handler
self:on_click(function(player,_element)
self:toggle(player)
end)
LeftFrames.frames[self.name] = self
return self
end
--- Sets if the frame is visible when a player joins, can also be a function to return a boolean
-- @tparam[opt=true] state ?boolean|function the default state of the visiblty, can be a function
-- state param - player LuaPlayer - the player that has joined the game
-- state param - define_name string - the define name for the frame
-- state return - boolean - false will hide the frame
function LeftFrames._prototype:set_open_by_default(state)
if state == false then
self.open_by_default = false
else
self.open_by_default = state
end
return self
end
--- Gets the frame for this define from the left frame flow
-- @tparam player LuaPlayer the player to get the frame of
-- @treturn LuaGuiElement the frame in the left frame flow for this define
function LeftFrames._prototype:get_frame(player)
local flow = LeftFrames.get_flow(player)
if flow[self.name] and flow[self.name].valid then
return flow[self.name]
end
end
--- Returns if the player currently has this define visible
-- @tparam player LuaPlayer the player to get the frame of
-- @treturn boolean true if it is open/visible
function LeftFrames._prototype:is_open(player)
local frame = self:get_frame(player)
return frame and frame.visible or false
end
--- Toggles the visiblty of the left frame
-- @tparam player LuaPlayer the player to toggle the frame of
-- @treturn boolean the new state of the visiblity
function LeftFrames._prototype:toggle(player)
local frame = self:get_frame(player)
Gui.toggle_visible(frame)
LeftFrames.get_open(player)
return frame.visible
end
--- Updates the contents of the left frame, first tries update callback, oter wise will clear and redraw
-- @tparam player LuaPlayer the player to update the frame of
function LeftFrames._prototype:update(player)
local frame = self:get_frame(player)
if self.events.on_update then
self.events.on_update(player,frame)
elseif self.events.on_draw then
frame.clear()
self.events.on_draw(player,frame)
end
end
--- Updates the frame for all players, see update
-- @tparam[opt=false] update_offline boolean when true will update the frame for offline players
function LeftFrames._prototype:update_all(update_offline)
local players = update_offline == true and game.players or game.connected_players
for _,player in pairs(players) do
self:update(player)
end
end
--- Redraws the frame by calling on_draw, will always clear the frame
-- @tparam player LuaPlayer the player to update the frame of
function LeftFrames._prototype:redraw(player)
local frame = self:get_frame(player)
frame.claer()
if self.events.on_draw then
self.events.on_draw(player,frame)
end
end
--- Redraws the frame for all players, see redraw
-- @tparam[opt=false] update_offline boolean when true will update the frame for offline players
function LeftFrames._prototype:redraw_all(update_offline)
local players = update_offline == true and game.players or game.connected_players
for _,player in pairs(players) do
self:redraw(player)
end
end
--- Creates an event handler that will trigger one of its functions, use with Event.add
-- @tparam[opt=update] action string the action to take on this event
function LeftFrames._prototype:event_handler(action)
action = action or 'update'
return function(event)
local player = Game.get_player_by_index(event.player_index)
self[action](self,player)
end
end
LeftFrames.toggle_button =
Buttons.new_button()
:set_tooltip('Close Windows')
:set_caption('<')
:on_click(function(player,element)
for _,define in pairs(LeftFrames.frames) do
local frame = LeftFrames.get_frame(define.name,player)
frame.visible = false
end
element.visible = false
end)
Event.add(defines.events.on_player_created,function(event)
local player = Game.get_player_by_index(event.player_index)
local flow = LeftFrames.get_flow(player)
local close_button = LeftFrames.toggle_button(flow)
Gui.set_padding(close_button)
local style = close_button.style
style.width = 18
style.height = 36
style.font = 'default-small-bold'
for _,define in pairs(LeftFrames.frames) do
local frame = flow.add{
type='frame',
name=define.name
}
if define.events.on_draw then
define.events.on_draw(player,frame)
end
if define.open_by_default == false then
frame.visible = false
elseif type(define.open_by_default) == 'function' then
if not define.open_by_default(player,define.name) then
frame.visible = false
end
end
if not Toolbar.allowed(player,define.name) then
frame.visible = false
end
end
LeftFrames.get_open(player)
end)
return LeftFrames

228
expcore/Gui/popups.lua Normal file
View File

@@ -0,0 +1,228 @@
--- Gui structure define for popup gui
--[[
>>>> Functions
PopupFrames.get_flow(player) --- Gets the left flow that contains the popup frames
PopupFrames.open(define_name,player,open_time,...) --- Opens a popup for the player, can give the amount of time it is open as well as params for the draw function
PopupFrames.close_progress --- Progress bar which when depleaded will close the popup frame
PopupFrames.close_button --- A button which can be used to close the gui before the timer runs out
PopupFrames.new_popup(name) --- Creates a new popup frame define
PopupFrames._prototype:set_default_open_time(amount) --- Sets the default open time for the popup, will be used if non is provided with open
PopupFrames._prototype:open(player,open_time,...) --- Opens this define for a player, can be given open time and any other params for the draw function
]]
local Gui = require 'expcore.gui.core'
local Game = require 'utils.game'
local Event = require 'utils.event'
local ProgressBar = require 'expcore.gui.progress-bar'
local Button = require 'expcore.gui.buttons'
local mod_gui = require 'mod-gui'
local Color = require 'resources.color_presets'
local Global = require 'utils.global'
local PopupFrames = {
paused_popups={},
popup_flow_name = Gui.uid_name(),
main_frame_name = Gui.uid_name(),
close_frame_name = Gui.uid_name(),
_prototype = Gui._prototype_factory{
on_draw = Gui._event_factory('on_draw')
}
}
Global.register(PopupFrames.paused_popups,function(tbl)
PopupFrames.paused_popups = tbl
end)
--- Sets the state of the element in the pasued list, nil or true
-- @tparam element LuaGuiElement the element to set the state of
-- @tparam[opt] state boolean the state to set it to, true will pause the the progress bar
local function set_pasued_state(element,state)
local name = element.player_index..':'..element.index
PopupFrames.paused_popups[name] = state
end
--- Gets the state of the element in the pasued list, nil or true
-- @tparam element LuaGuiElement the element to get the state of
local function get_pasued_state(element)
local name = element.player_index..':'..element.index
return PopupFrames.paused_popups[name]
end
--- Gets the left flow that contains the popup frames
-- @tparam player LuaPlayer the player to get the flow for
-- @treturn LuaGuiElement the left flow that contains the popup frames
function PopupFrames.get_flow(player)
player = Game.get_player_from_any(player)
local flow = mod_gui.get_frame_flow(player)
return flow[PopupFrames.popup_flow_name]
end
--- Opens a popup for the player, can give the amount of time it is open as well as params for the draw function
-- @tparam define_name string the name of the define that you want to open for the player
-- @tparam player LuaPlayer the player to open the popup for
-- @tparam[opt] open_time number the minimum number of ticks you want the popup open for, 0 means no limit, nil will take default
-- @tparam ... any the other params that you want to pass to your on_draw event
-- @treturn LuaGuiElement the frame that was drawn, the inner gui flow which contains the content
function PopupFrames.open(define_name,player,open_time,...)
local define = Gui.get_define(define_name,true)
player = Game.get_player_from_any(player)
return define:open(player,open_time,...)
end
--- Closes the popup, is called by progress bar and close button
-- @tparam element LuaGuiElement either the progress bar or the close button
local function close_popup(element)
local frame = element.parent.parent.parent
if not frame or not frame.valid then return end
set_pasued_state(element.parent[PopupFrames.close_progress:uid()])
frame.destroy()
end
--- Progress bar which when depleaded will close the popup frame
PopupFrames.close_progress =
ProgressBar.new_progressbar()
:use_count_down()
:set_tooltip('Pause/Resume Auto-close')
:on_complete(function(player,element)
close_popup(element)
end)
--- A button which can be used to close the gui before the timer runs out
PopupFrames.close_button =
Button.new_button()
:set_sprites('utility/close_white')
:set_tooltip('Close Popup')
:on_click(function(player,element)
close_popup(element)
end)
--- When the progress bar is clicked it will pause its progress, or resume if previously paused
Gui.on_click(PopupFrames.close_progress:uid(),function(event)
local element = event.element
if get_pasued_state(element) then
set_pasued_state(element)
else
set_pasued_state(element,true)
end
end)
--- When the parent flow of the progress bar is clicked it will pause its progress, or resume if previously paused
Gui.on_click(PopupFrames.close_frame_name,function(event)
local element = event.element[PopupFrames.close_progress:uid()]
if get_pasued_state(element) then
set_pasued_state(element)
else
set_pasued_state(element,true)
end
end)
--- Creates a new popup frame define
-- @tparam[opt] name string the optional debug name that can be added
-- @treturn table the new popup frame define
function PopupFrames.new_popup(name)
local self = Gui._define_factory(PopupFrames._prototype)
self.draw_data.type = 'flow'
self.draw_data.direction = 'vertical'
if name then
self:debug_name(name)
end
local mt = getmetatable(self)
mt.__call = function(tbl,player,open_time,...)
return tbl:open(player,open_time,...)
end
self.post_draw = function(element,maximum,...)
-- main content frame
local frame = element.add{
type='flow',
name=PopupFrames.main_frame_name
}
frame.style.horizontally_stretchable = true
-- flow for progress bar and close button
local close_flow = element.add{
type='flow',
name=PopupFrames.close_frame_name
}
close_flow.style.horizontally_stretchable = true
-- progress bar, when 0 then a static full one is drawn
local progress_style
if maximum == 0 then
progress_style = close_flow.add{
type='progressbar',
tooltip='No Auto-close',
value=1
}.style
else
progress_style = PopupFrames.close_progress(close_flow,maximum).style
end
progress_style.top_padding = 6
progress_style.bottom_padding = 3
progress_style.height = 11
progress_style.color = Color.grey
-- close button, will close the popup when clicked
local close_button = PopupFrames.close_button(close_flow)
Gui.set_padding(close_button)
local close_button_style = close_button.style
close_button_style.width = 20
close_button_style.height = 20
-- event trigger to draw the gui content
if self.events.on_draw then
local player = Game.get_player_by_index(element.player_index)
self.events.on_draw(player,frame,...)
end
end
return self
end
--- Sets the default open time for the popup, will be used if non is provided with open
-- @tparam amount number the number of ticks, by default, the popup will be open for
-- @treturn table the define to allow for chaining
function PopupFrames._prototype:set_default_open_time(amount)
self.default_open_time = amount
return self
end
--- Opens this define for a player, can be given open time and any other params for the draw function
-- @tparam player LuaPlayer the player to open the popup for
-- @tparam[opt] open_time number the minimum number of ticks you want the popup open for, 0 means no limit, nil will take default
-- @tparam ... any the other params that you want to pass to your on_draw event
-- @treturn LuaGuiElement the frame that was drawn, the inner gui flow which contains the content
function PopupFrames._prototype:open(player,open_time,...)
open_time = open_time or self.default_open_time or 0
player = Game.get_player_from_any(player)
local flow = PopupFrames.get_flow(player)
local frame = flow.add{
type='frame',
style='blurry_frame'
}
Gui.set_padding(frame,3,3,4,4)
return self:draw_to(frame,open_time,...)[PopupFrames.main_frame_name]
end
--- When player is first created the popup flow is added to they left flow
Event.add(defines.events.on_player_created,function(event)
local player = Game.get_player_by_index(event.player_index)
local flow = mod_gui.get_frame_flow(player)
flow.add{
type='flow',
direction='vertical',
name=PopupFrames.popup_flow_name
}
end)
--- Every tick any, not pasued, progress bars will go down by one tick
Event.add(defines.events.on_tick,PopupFrames.close_progress:event_countdown(function(element)
return not get_pasued_state(element)
end))
return PopupFrames

View File

@@ -0,0 +1,390 @@
--- Gui element define for progess bars
--[[
>>>> Functions
ProgressBar.set_maximum(element,amount,count_down) --- Sets the maximum value that represents the end value of the progress bar
ProgressBar.increment(element,amount) --- Increases the value of the progressbar, if a define is given all of its instances are incremented
ProgressBar.decrement(element,amount) --- Decreases the value of the progressbar, if a define is given all of its instances are decresed
ProgressBar.new_progressbar(name) --- Creates a new progressbar element define
ProgressBar._prototype:set_maximum(amount,count_down) --- Sets the maximum value that represents the end value of the progress bar
ProgressBar._prototype:use_count_down(state) --- Will set the progress bar to start at 1 and trigger when it hits 0
ProgressBar._prototype:increment(amount,category) --- Increases the value of the progressbar
ProgressBar._prototype:increment_filtered(amount,filter) --- Increases the value of the progressbar, if the filter condition is met, does not work with store
ProgressBar._prototype:decrement(amount,category) --- Decreases the value of the progressbar
ProgressBar._prototype:decrement_filtered(amount,filter) --- Decreases the value of the progressbar, if the filter condition is met, does not work with store
ProgressBar._prototype:add_element(element,maximum) --- Adds an element into the list of instances that will are waiting to complete, does not work with store
ProgressBar._prototype:reset_element(element) --- Resets an element, or its store, to be back at the start, either 1 or 0
ProgressBar._prototype:on_complete(callback) --- Triggers when a progress bar element compeltes (hits 0 or 1)
ProgressBar._prototype:on_complete(callback) --- Triggers when a store value completes (hits 0 or 1)
ProgressBar._prototype:event_counter(filter) --- Event handler factory that counts up by 1 every time the event triggeres, can filter which elements are incremented
ProgressBar._prototype:event_countdown(filter) --- Event handler factory that counts down by 1 every time the event triggeres, can filter which elements are decremented
]]
local Gui = require 'expcore.gui.core'
local Global = require 'utils.global'
local Game = require 'utils.game'
--- Event call for when the value is outside the range 0-1
-- @tparam define table the define that this is acting on
-- @tparam element LuaGuiElement the element that triggered the event
local function event_call(define,element)
local player = Game.get_player_by_index(element.player_index)
if define.events.on_complete then
define.events.on_complete(player,element,function()
define:add_element(element)
define:reset_element(element)
end)
end
end
--- Store call for store update
-- @tparam define table the define that this is acting on
-- @tparam element LuaGuiElement the element that triggered the event
local function store_call(define,element,value)
if value then
element.value = value
if define.count_down and value <= 0
or not define.count_down and value >= 1 then
event_call(define,element)
end
end
end
local ProgressBar = {
unregistered={}, -- elements with no callbacks
independent={}, -- elements with a link to a deinfe
_prototype=Gui._prototype_factory{
-- note both events will recive a reset function that can be used to reset the progress of the element/store
on_complete = Gui._event_factory('on_complete'),
on_store_complete = Gui._event_factory('on_store_complete'),
add_store = Gui._store_factory(store_call),
add_sync_store = Gui._sync_store_factory(store_call)
}
}
Global.register({
ProgressBar.unregistered,
ProgressBar.independent
},function(tbl)
ProgressBar.unregistered = tbl[1]
ProgressBar.independent = tbl[2]
end)
--- Gets the define data, cant use Gui.get_define as it would error
-- @tparam define ?table|string the define to get
-- @treturn table the define or nil
local function get_define(define)
if type(define) == 'table' then
if define.name and Gui.defines[define.name] then
return Gui.defines[define.name]
end
end
return Gui.defines[define]
end
--- Gets the element data, used when there is no define
-- @tparam element LuaGuiElement
-- @treturn table the element data simialr to define
local function get_element(element)
if not element.valid then return end
local name = element.player_index..':'..element.index
if ProgressBar.unregistered[name] then
return ProgressBar.unregistered[name]
end
end
--- Sets the maximum value that represents the end value of the progress bar
-- @tparam element ?LuaGuiElement|string either a gui element or a registered define
-- @tparam amount number the amount to have set as the maximum
function ProgressBar.set_maximum(element,amount)
amount = amount > 0 and amount or error('amount must be greater than 0')
local define = get_define(element)
if define then
define:set_deafult_maximum(amount)
else
local element_data = get_element(element)
if element_data then
element_data.maximum = amount
else
local name = element.player_index..':'..element.index
ProgressBar.unregistered[name] = {
element=element,
maximum=amount or 1
}
end
end
end
--- Increases the value of the progressbar, if a define is given all of its instances are incremented
-- @tapram element ?LuaGuiElement|string either a gui element or a registered define
-- @tparam[opt=1] amount number the amount to increase the progressbar by
function ProgressBar.increment(element,amount)
amount = type(amount) == 'number' and amount or 1
local define = get_define(element)
if define then
define:increment(amount)
else
local element_data = get_element(element)
if element_data then
local real_amount = amount/element_data.maximum
element.value = element.value + real_amount
if element.value >= 1 then
local name = element.player_index..':'..element.index
ProgressBar.unregistered[name] = nil
return true
end
end
end
end
--- Decreases the value of the progressbar, if a define is given all of its instances are decresed
-- @tapram element ?LuaGuiElement|string either a gui element or a registered define
-- @tparam[opt=1] amount number the amount to decrease the progressbar by
function ProgressBar.decrement(element,amount)
amount = type(amount) == 'number' and amount or 1
local define = get_define(element)
if define then
define:decrement(amount)
else
local element_data = get_element(element)
if element_data then
local real_amount = amount/element_data.maximum
element.value = element.value - real_amount
if element.value <= 0 then
local name = element.player_index..':'..element.index
ProgressBar.unregistered[name] = nil
return true
end
end
end
end
--- Creates a new progressbar element define
-- @tparam[opt] name string the optional debug name that can be added
-- @treturn table the new progressbar elemente define
function ProgressBar.new_progressbar(name)
local self = Gui._define_factory(ProgressBar._prototype)
self.draw_data.type = 'progressbar'
if name then
self:debug_name(name)
end
self.post_draw = function(element,maximum)
if self.store then
local category = self.categorize and self.categorize(element) or nil
local value = self:get_store(category)
if not value then
value = self.count_down and 1 or 0
self:set_store(category,value)
end
element.value = value
else
if self.count_down then
element.value = 1
end
if not ProgressBar.independent[self.name] then
ProgressBar.independent[self.name] = {}
end
table.insert(ProgressBar.independent[self.name],{
element = element,
maximum = maximum
})
end
end
return self
end
--- Sets the maximum value that represents the end value of the progress bar
-- @tparam amount number the amount to have set as the maximum
-- @treturn table the define to allow chaining
function ProgressBar._prototype:set_default_maximum(amount)
amount = amount > 0 and amount or error('amount must be greater than 0')
self.default_maximum = amount
return self
end
--- Will set the progress bar to start at 1 and trigger when it hits 0
-- @tparam[opt=true] state boolean when true the bar will start filled, to be used with decrease
-- @treturn table the define to allow chaining
function ProgressBar._prototype:use_count_down(state)
if state == false then
self.count_down = false
else
self.count_down = true
end
return self
end
--- Main logic for changing the value of a progress bar, this only applies when its a registered define
-- @tparam self table the define that is being changed
-- @tparam amount number the amount which it is being changed by, may be negative
-- @tparam[opt] category string the category to use with store
local function change_value_prototype(self,amount,category,filter)
local function reset_store()
local value = self.count_down and 1 or 0
local _category = category or value
self:set_store(_category,value)
end
if self.store then
local value = self:get_store(category) or self.count_down and 1 or 0
local maximum = self.default_maximum or 1
local new_value = value + (amount/maximum)
if self.count_down and new_value <= 0
or not self.count_down and new_value >= 1 then
self:set_store(category)
if self.events.on_store_complete then
category = category or reset_store
self.events.on_store_complete(category,reset_store)
end
end
category = category or new_value
self:set_store(category,new_value)
return
end
if ProgressBar.independent[self.name] then
for key,element_data in pairs(ProgressBar.independent[self.name]) do
local element = element_data.element
if not element or not element.valid then
ProgressBar.independent[self.name][key] = nil
else
if not filter or filter(element) then
local maximum = element_data.maximum or self.default_maximum or 1
element.value = element.value + (amount/maximum)
if self.count_down and element.value <= 0
or not self.count_down and element.value >= 1 then
ProgressBar.independent[self.name][key] = nil
event_call(self,element)
end
end
end
end
end
end
--- Increases the value of the progressbar
-- @tparam[opt=1] amount number the amount to increase the progressbar by
-- @tparam[opt] category string the category that is used with a store
function ProgressBar._prototype:increment(amount,category)
amount = type(amount) == 'number' and amount or 1
change_value_prototype(self,amount,category)
end
--- Increases the value of the progressbar, if the filter condition is met, does not work with store
-- @tparam[opt=1] amount number the amount to increase the progressbar by
-- @tparam[opt] category string the category that is used with a store
function ProgressBar._prototype:increment_filtered(amount,filter)
amount = type(amount) == 'number' and amount or 1
change_value_prototype(self,amount,nil,filter)
end
--- Decreases the value of the progressbar
-- @tparam[opt=1] amount number the amount to decrease the progressbar by
-- @tparam[opt] category string the category that is used with a store
function ProgressBar._prototype:decrement(amount,category)
amount = type(amount) == 'number' and amount or 1
change_value_prototype(self,-amount,category)
end
--- Decreases the value of the progressbar, if the filter condition is met, does not work with store
-- @tparam[opt=1] amount number the amount to decrease the progressbar by
-- @tparam[opt] category string the category that is used with a store
function ProgressBar._prototype:decrement_filtered(amount,filter)
amount = type(amount) == 'number' and amount or 1
change_value_prototype(self,-amount,nil,filter)
end
--- Adds an element into the list of instances that will are waiting to complete, does not work with store
-- note use store if you want persistent data, this only stores the elements not the values which they have
-- @tparam element LuaGuiElement the element that you want to add into the waiting to complete list
-- @tparam[opt] maximum number the maximum for this element if not given the default for this define is used
function ProgressBar._prototype:add_element(element,maximum)
if self.store then return end
if not ProgressBar.independent[self.name] then
ProgressBar.independent[self.name] = {}
end
table.insert(ProgressBar.independent[self.name],{
element = element,
maximum = maximum
})
end
--- Resets an element, or its store, to be back at the start, either 1 or 0
-- @tparam element LuaGuiElement the element that you want to reset the progress of
function ProgressBar._prototype:reset_element(element)
if not element or not element.valid then return end
local value = self.count_down and 1 or 0
if self.store then
local category = self.categorize and self.categorize(element) or value
self:set_store(category,value)
else
element.value = value
end
end
--- Event handler factory that counts up by 1 every time the event triggeres, can filter which elements are incremented
-- @tparam[opt] filter function when given will use filtered incerement
-- @treturn function the event handler
function ProgressBar._prototype:event_counter(filter)
if type(filter) == 'function' then
return function()
self:increment_filtered(1,filter)
end
else
return function()
self:increment()
end
end
end
--- Event handler factory that counts down by 1 every time the event triggeres, can filter which elements are decremented
-- @tparam[opt] filter function when given will use filtered decerement
-- @treturn function the event handler
function ProgressBar._prototype:event_countdown(filter)
if type(filter) == 'function' then
return function()
self:decrement_filtered(1,filter)
end
else
return function()
self:decrement()
end
end
end
return ProgressBar

187
expcore/Gui/slider.lua Normal file
View File

@@ -0,0 +1,187 @@
--- Gui class define for silders
--[[
>>>> Functions
Slider.new_slider(name) --- Creates a new slider element define
Slider._prototype:on_element_update(callback) --- Registers a handler for when an element instance updates
Slider._prototype:on_store_update(callback) --- Registers a handler for when the stored value updates
Slider._prototype:use_notches(state) --- Adds notches to the slider
Slider._prototype:set_range(min,max) --- Sets the range of a slider, if not used will use default values for a slider
Slider._prototype:draw_label(element) --- Draws a new label and links its value to the value of this slider, if no store then it will only show one value per player
Slider._prototype:enable_auto_draw_label(state) --- Enables auto draw of the label, the label will share the same parent element as the slider
Other functions present from expcore.gui.core
]]
local Gui = require 'expcore.gui.core'
local Instances = require 'expcore.gui.instances'
local Game = require 'utils.game'
--- Event call for on_value_changed and store update
-- @tparam define table the define that this is acting on
-- @tparam element LuaGuiElement the element that triggered the event
-- @tparam value number the new value for the slider
local function event_call(define,element,value)
local player = Game.get_player_by_index(element.player_index)
local min,max = element.get_slider_minimum(),element.get_slider_maximum()
local delta = max-min
local percent = delta == 0 and 0 or (value-min)/delta
if define.events.on_element_update then
define.events.on_element_update(player,element,value,percent)
end
local category = player.name
if define.categorize then
category = define.categorize(element)
end
Instances.unregistered_get_elements(define.name..'-label',category,function(label)
label.caption = tostring(math.round(value,2))
end)
end
--- Store call for store update
-- @tparam define table the define that this is acting on
-- @tparam element LuaGuiElement the element that triggered the event
-- @tparam value number the new value for the slider
local function store_call(define,element,value)
element.slider_value = value
event_call(define,element,value)
end
local Slider = {
_prototype=Gui._prototype_factory{
on_element_update = Gui._event_factory('on_element_update'),
on_store_update = Gui._event_factory('on_store_update'),
add_store = Gui._store_factory(store_call),
add_sync_store = Gui._sync_store_factory(store_call)
}
}
--- Creates a new slider element define
-- @tparam[opt] name string the optional debug name that can be added
-- @treturn table the new slider element define
function Slider.new_slider(name)
local self = Gui._define_factory(Slider._prototype)
self.draw_data.type = 'slider'
if name then
self:debug_name(name)
end
self.post_draw = function(element)
local player = Game.get_player_by_index(element.player_index)
local min,max = element.get_slider_minimum(),element.get_slider_maximum()
if type(self.min) == 'function' then
min = self.min(player,element)
end
if type(self.max) == 'function' then
max = self.max(player,element)
end
element.set_slider_minimum_maximum(min,max)
if self.store then
local category = self.categorize and self.categorize(element) or nil
local value = self:get_store(category)
if value then element.slider_value = value end
end
if self.auto_label then
self:draw_label(element.parent)
end
end
Gui.on_value_changed(self.name,function(event)
local element = event.element
local value = element.slider_value
if self.store then
local category = self.categorize and self.categorize(element) or value
self:set_store(category,value)
else
event_call(self,element,value)
end
end)
return self
end
--- Adds notches to the slider
-- @tparam[opt] state boolean when true will draw notches onto the slider
function Slider._prototype:use_notches(state)
if state == false then
self.draw_data.style = nil
else
self.draw_data.style = 'notched_slider'
end
return self
end
--- Sets the range of a slider, if not used will use default values for a slider
-- @tparam[opt] min number the minimum value that the slider can take
-- @tparam[opt] max number the maximum value that the slider can take
-- @treturn self the define to allow chaining
function Slider._prototype:set_range(min,max)
self.min = min
self.max = max
if type(min) == 'number' then
self.draw_data.minimum_value = min
end
if type(max) == 'number' then
self.draw_data.maximum_value = max
end
return self
end
--- Draws a new label and links its value to the value of this slider, if no store then it will only show one value per player
-- @tparam element LuaGuiElement the parent element that the lable will be drawn to
-- @treturn LuaGuiElement the new label element so that styles can be applied
function Slider._prototype:draw_label(element)
local name = self.name..'-label'
if element[name] then return end
local value = 0
if self.store then
local category = self.categorize and self.categorize(element) or value
value = self:get_store(category) or 0
end
local new_element = element.add{
name=name,
type='label',
caption=tostring(math.round(value,2))
}
local categorise = self.categorise or Gui.player_store
local category = categorise(new_element)
Instances.unregistered_add_element(name,category,new_element)
return new_element
end
--- Enables auto draw of the label, the label will share the same parent element as the slider
-- @tparam[opt=true] state boolean when false will disable the auto draw of the label
-- @treturn self the define to allow chaining
function Slider._prototype:enable_auto_draw_label(state)
if state == false then
self.auto_label = false
else
self.auto_label = true
end
return self
end
return Slider

664
expcore/Gui/test.lua Normal file
View File

@@ -0,0 +1,664 @@
--- This file creates a teste gui that is used to test every input method
-- note that this does not cover every permutation only features in indepentance
-- for example store in most cases is just by player name, but other store methods are tested with checkbox
local Gui = require 'expcore.gui'
local format_chat_colour,table_keys = ext_require('expcore.common','format_chat_colour','table_keys')
local Colors = require 'resources.color_presets'
local Event = require 'utils.event'
local Store = require 'expcore.store'
local tests = {}
--[[
Toolbar Tests
> No display - Toolbar button with no display
> With caption - Toolbar button with a caption display
> With icons - Toolbar button with an icon
]]
Gui.new_toolbar_button('click-1')
:set_post_authenticator(function(player,button_name)
return global.click_one
end)
:on_click(function(player,element)
player.print('CLICK 1')
end)
Gui.new_toolbar_button('click-2')
:set_caption('Click Two')
:set_post_authenticator(function(player,button_name)
return global.click_two
end)
:on_click(function(player,element)
player.print('CLICK 2')
end)
Gui.new_toolbar_button('click-3')
:set_sprites('utility/questionmark')
:set_post_authenticator(function(player,button_name)
return global.click_three
end)
:on_click(function(player,element)
player.print('CLICK 3')
end)
--[[
Center Frame Tests
> Main test gui - Main test gui triggers all other tests
]]
local test_gui =
Gui.new_center_frame('gui-test-open')
:set_caption('Open Test Gui')
:set_tooltip('Main test gui triggers all other tests')
:set_post_authenticator(function(player,button_name)
return global.show_test_gui
end)
:on_draw(function(player,frame)
for test_group_name,test_group in pairs(tests) do
player.print('Starting tests for: '..format_chat_colour(test_group_name,Colors.cyan))
local pass_count = 0
local test_count = 0
local flow = frame.add{
type='flow',
name=test_group_name,
direction='vertical'
}
for test_name,test in pairs(test_group) do
local test_function = type(test) == 'function' and test or test.draw_to
test_count = test_count+1
local success,err = pcall(test_function,test,flow)
if success then
pass_count = pass_count+1
else
player.print('Failed Test: '..format_chat_colour(test_name,Colors.red))
log('Gui Test Failed: '..test_name..' stacktrace:\n'..err)
end
end
if pass_count == test_count then
player.print('All tests '..format_chat_colour('passed',Colors.green)..' ('..test_group_name..')')
else
player.print('Passed '..format_chat_colour(pass_count..'/'..test_count,Colors.cyan)..' ('..test_group_name..')')
end
end
end)
--[[
Left Frame Test
> Left frame which holds all online player names, updates when player leaves or joins
]]
local left_frame =
Gui.new_left_frame('test-left-frame')
:set_caption('Test Left Gui')
:set_tooltip('Left frame which holds all online player names, updates when player leaves or joins')
:set_post_authenticator(function(player,button_name)
return global.show_test_gui
end)
:set_open_by_default()
:on_draw(function(_player,frame)
for _,player in pairs(game.connected_players) do
frame.add{
type='label',
caption=player.name
}
end
end)
Event.add(defines.events.on_player_joined_game,left_frame 'update_all')
Event.add(defines.events.on_player_left_game,left_frame 'update_all')
--[[
Popup Test
> Allows opening a popup which contains the players name and tick it was opened
]]
local test_popup =
Gui.new_popup('test-popup')
:on_draw(function(player,frame)
frame.add{
type='label',
caption=player.name
}
frame.add{
type='label',
caption=game.tick
}
end)
Gui.new_toolbar_button('test-popup-open')
:set_caption('Test Popup')
:set_tooltip('Allows opening a popup which contains the players name and tick it was opened')
:set_post_authenticator(function(player,button_name)
return global.show_test_gui
end)
:on_click(function(player,element)
test_popup(player,300)
end)
--[[
Button Tests
> No display - Simple button which has no display
> Caption - Simple button but has a caption on it
> Icons - Button with an icon display plus two icons for hover and select
> Auth - Button which can only be passed when auth is true (press no display to toggle; needs reopen)
]]
local button_no_display =
Gui.new_button('test-button-no-display')
:set_tooltip('Button no display')
:on_click(function(player,element)
player.print('Button no display')
global.test_auth_button = not global.test_auth_button
player.print('Auth Button auth state: '..tostring(global.test_auth_button))
end)
local button_with_caption =
Gui.new_button('test-button-with-caption')
:set_tooltip('Button with caption')
:set_caption('Button Caption')
:on_click(function(player,element)
player.print('Button with caption')
end)
local button_with_icon =
Gui.new_button('test-button-with-icon')
:set_tooltip('Button with icons')
:set_sprites('utility/warning_icon','utility/warning','utility/warning_white')
:on_click(function(player,element)
player.print('Button with icons')
end)
local button_with_auth =
Gui.new_button('test-button-with-auth')
:set_tooltip('Button with auth')
:set_post_authenticator(function(player,button_name)
return global.test_auth_button
end)
:on_click(function(player,element)
player.print('Button with auth')
end)
tests.Buttons = {
['No display']=button_no_display,
['Caption']=button_with_caption,
['Icons']=button_with_icon,
['Auth']=button_with_auth
}
--[[
Checkbox Test
> Local -- Simple checkbox that can toggle
> Game store -- Checkbox which syncs its state between all players
> Force store -- Checkbox which syncs its state with all players on the same force
> Player store -- Checkbox that stores its state between re-draws
]]
local checkbox_local =
Gui.new_checkbox('test-checkbox-local')
:set_tooltip('Checkbox local')
:set_caption('Checkbox Local')
:on_element_update(function(player,element,state)
player.print('Checkbox local: '..tostring(state))
end)
local checkbox_game =
Gui.new_checkbox('test-checkbox-store-game')
:set_tooltip('Checkbox store game')
:set_caption('Checkbox Store Game')
:add_store()
:on_element_update(function(player,element,state)
player.print('Checkbox store game: '..tostring(state))
end)
local checkbox_force =
Gui.new_checkbox('test-checkbox-store-force')
:set_tooltip('Checkboc store force')
:set_caption('Checkbox Store Force')
:add_store(Gui.force_store)
:on_element_update(function(player,element,state)
player.print('Checkbox store force: '..tostring(state))
end)
local checkbox_player =
Gui.new_checkbox('test-checkbox-store-player')
:set_tooltip('Checkbox store player')
:set_caption('Checkbox Store Player')
:add_store(Gui.player_store)
:on_element_update(function(player,element,state)
player.print('Checkbox store player: '..tostring(state))
end)
tests.Checkboxs = {
['Local']=checkbox_local,
['Game store']=checkbox_game,
['Force store']=checkbox_force,
['Player store']=checkbox_player
}
--[[
Radiobutton Tests
> Local -- Simple radiobutton that can only be toggled true
> Player store -- Radio button that saves its state between re-draws
> Option set -- A set of radio buttons where only one can be true at a time
]]
local radiobutton_local =
Gui.new_radiobutton('test-radiobutton-local')
:set_tooltip('Radiobutton local')
:set_caption('Radiobutton Local')
:on_element_update(function(player,element,state)
player.print('Radiobutton local: '..tostring(state))
end)
local radiobutton_player =
Gui.new_radiobutton('test-radiobutton-store')
:set_tooltip('Radiobutton store')
:set_caption('Radiobutton Store')
:add_store(Gui.player_store)
:on_element_update(function(player,element,state)
player.print('Radiobutton store: '..tostring(state))
end)
local radiobutton_option_set =
Gui.new_radiobutton_option_set('gui.test.share',function(value,category)
game.print('Radiobutton option set for: '..category..' is now: '..tostring(value))
end,Gui.player_store)
local radiobutton_option_one =
Gui.new_radiobutton('test-radiobutton-option-one')
:set_tooltip('Radiobutton option set')
:set_caption('Radiobutton Option One')
:add_as_option(radiobutton_option_set,'One')
:on_element_update(function(player,element,state)
player.print('Radiobutton option one: '..tostring(state))
end)
local radiobutton_option_two =
Gui.new_radiobutton('test-radiobutton-option-two')
:set_tooltip('Radiobutton option set')
:set_caption('Radiobutton Option Two')
:add_as_option(radiobutton_option_set,'Two')
:on_element_update(function(player,element,state)
player.print('Radiobutton option two: '..tostring(state))
end)
local radiobutton_option_three =
Gui.new_radiobutton('test-radiobutton-option-three')
:set_tooltip('Radiobutton option set')
:set_caption('Radiobutton Option Three')
:add_as_option(radiobutton_option_set,'Three')
:on_element_update(function(player,element,state)
player.print('Radiobutton option three: '..tostring(state))
end)
tests.Radiobuttons = {
['Local']=radiobutton_local,
['Player store']=radiobutton_player,
['Option set']=function(self,frame)
Gui.draw_option_set(radiobutton_option_set,frame)
end
}
--[[
Dropdown Test
> Local static general -- Simple dropdown with all static options and general handler
> Player startic general -- Dropdown with all static options and general handler and stores option between re-draws
> Local static case -- Dropdown with all static options but case handlers and a general handler
> Player static case -- Dropdown with all static options but case handlers and a general handler and stores option between re-draws
> Local dynamic -- Dropdown with one static option with the reset generated by a function
> Player dynamic -- Dropdown with one static option with the reset generated by a function and stores option between re-draws
]]
local dropdown_local_static_general =
Gui.new_dropdown('test-dropdown-local-static-general')
:set_tooltip('Dropdown local static general')
:add_options('One','Two','Three','Four')
:on_element_update(function(player,element,value)
player.print('Dropdown local static general: '..tostring(value))
end)
local dropdown_player_static_general =
Gui.new_dropdown('test-dropdown-store-static-general')
:set_tooltip('Dropdown store static general')
:add_options('One','Two','Three','Four')
:add_store(Gui.player_store)
:on_element_update(function(player,element,value)
player.print('Dropdown store static general: '..tostring(value))
end)
local function print_option_selected_1(player,element,value)
player.print('Dropdown local static case (case): '..tostring(value))
end
local dropdown_local_static_case =
Gui.new_dropdown('test-dropdown-local-static-case')
:set_tooltip('Dropdown local static case')
:add_options('One','Two')
:add_option_callback('One',print_option_selected_1)
:add_option_callback('Two',print_option_selected_1)
:add_option_callback('Three',print_option_selected_1)
:add_option_callback('Four',print_option_selected_1)
:on_element_update(function(player,element,value)
player.print('Dropdown local static case (general): '..tostring(value))
end)
local function print_option_selected_2(player,element,value)
player.print('Dropdown store static case (case): '..tostring(value))
end
local dropdown_player_static_case =
Gui.new_dropdown('test-dropdown-store-static-case')
:set_tooltip('Dropdown store static case')
:add_store(Gui.player_store)
:add_options('One','Two')
:add_option_callback('One',print_option_selected_2)
:add_option_callback('Two',print_option_selected_2)
:add_option_callback('Three',print_option_selected_2)
:add_option_callback('Four',print_option_selected_2)
:on_element_update(function(player,element,value)
player.print('Dropdown store static case (general): '..tostring(value))
end)
local dropdown_local_dynamic =
Gui.new_dropdown('test-dropdown-local-dynamic')
:set_tooltip('Dropdown local dynamic')
:add_options('Static')
:add_dynamic(function(player,element)
return table_keys(Colors)
end)
:on_element_update(function(player,element,value)
player.print('Dropdown local dynamic: '..tostring(value))
end)
local dropdown_player_dynamic =
Gui.new_dropdown('test-dropdown-store-dynamic')
:set_tooltip('Dropdown store dynamic')
:add_options('Static')
:add_dynamic(function(player,element)
return table_keys(Colors)
end)
:add_store(Gui.player_store)
:on_element_update(function(player,element,value)
player.print('Dropdown store dynamic: '..tostring(value))
end)
tests.Dropdowns = {
['Local static general']=dropdown_local_static_general,
['Player startic general']=dropdown_player_static_general,
['Local static case']=dropdown_local_static_case,
['Player static case']=dropdown_player_static_case,
['Local dynamic general']=dropdown_local_dynamic,
['Player dynamic general']=dropdown_player_dynamic
}
--[[
List Box Tests
> Local -- A list box with all static options and general handler
> Store -- A list box with all static options and general handler and stores options between re-draws
]]
local list_box_local =
Gui.new_list_box('test-list-box-local')
:set_tooltip('List box local')
:add_options('One','Two','Three','Four')
:on_element_update(function(player,element,value)
player.print('Dropdown local: '..tostring(value))
end)
local list_box_player =
Gui.new_list_box('test-list-box-store')
:set_tooltip('List box store')
:add_options('One','Two','Three','Four')
:add_store(Gui.player_store)
:on_element_update(function(player,element,value)
player.print('Dropdown store: '..tostring(value))
end)
tests["List Boxs"] = {
['Local']=list_box_local,
['Player']=list_box_player
}
--[[
Slider Tests
> Local default -- Simple slider with default range
> Local notched -- Simple slider with notches
> Store default -- Slider with default range that stores value between re-draws
> Static range -- Simple slider with a static range
> Dynamic range -- Slider with a dynamic range
> Local label -- Simple slider with default range which has a label
> Store label -- Slider with default range which has a label and stores value between re-draws
]]
local slider_local_default =
Gui.new_slider('test-slider-local-default')
:set_tooltip('Silder local default')
:on_element_update(function(player,element,value,percent)
player.print('Slider local default: '..tostring(math.round(value))..' '..tostring(math.round(percent,1)))
end)
local slider_notched_default =
Gui.new_slider('test-slider-notched-default')
:set_tooltip('Silder notched default')
:use_notches()
:on_element_update(function(player,element,value,percent)
player.print('Slider notched default: '..tostring(math.round(value))..' '..tostring(math.round(percent,1)))
end)
local slider_player_default =
Gui.new_slider('test-slider-store-default')
:set_tooltip('Silder store default')
:add_store(Gui.player_store)
:on_element_update(function(player,element,value,percent)
player.print('Slider store default: '..tostring(math.round(value))..' '..tostring(math.round(percent,1)))
end)
local slider_static =
Gui.new_slider('test-slider-static-range')
:set_tooltip('Silder static range')
:set_range(5,50)
:on_element_update(function(player,element,value,percent)
player.print('Slider static range: '..tostring(math.round(value))..' '..tostring(math.round(percent,1)))
end)
local slider_dynamic =
Gui.new_slider('test-slider-dynamic-range')
:set_tooltip('Silder dynamic range')
:set_range(function(player,element)
return player.index - 5
end,function(player,element)
return player.index + 4
end)
:on_element_update(function(player,element,value,percent)
player.print('Slider dynamic range: '..tostring(math.round(value))..' '..tostring(math.round(percent,1)))
end)
local label_slider_local =
Gui.new_slider('test-slider-local-label')
:set_tooltip('Silder local label')
:enable_auto_draw_label()
:on_element_update(function(player,element,value,percent)
player.print('Slider local label: '..tostring(math.round(value))..' '..tostring(math.round(percent,1)))
end)
local label_slider_player =
Gui.new_slider('test-slider-store-label')
:set_tooltip('Silder store label')
:enable_auto_draw_label()
:add_store(Gui.player_store)
:on_element_update(function(player,element,value,percent)
player.print('Slider store label: '..tostring(math.round(value))..' '..tostring(math.round(percent,1)))
end)
tests.Sliders = {
['Local default']=slider_local_default,
['Local notched']=slider_notched_default,
['Player default']=slider_player_default,
['Static range']=slider_static,
['Dynamic range']=slider_dynamic,
['Local label']=function(self,frame)
local flow = frame.add{type='flow'}
label_slider_local:draw_to(flow)
end,
['Player label']=function(self,frame)
local flow = frame.add{type='flow'}
label_slider_player:draw_to(flow)
end
}
--[[
Text Tests
> Local field -- Simple text field
> Store field -- Test field that stores text between re-draws
> Local box -- Simple text box
> Wrap box -- Text box which has word wrap and selection disabled
]]
local text_filed_local =
Gui.new_text_filed('test-text-field-local')
:set_tooltip('Text field local')
:on_element_update(function(player,element,value)
player.print('Text field local: '..value)
end)
local text_filed_store =
Gui.new_text_filed('test-text-field-store')
:set_tooltip('Text field store')
:add_store(Gui.player_store)
:on_element_update(function(player,element,value)
player.print('Text field store: '..value)
end)
local text_box_local =
Gui.new_text_box('test-text-box-local')
:set_tooltip('Text box local')
:on_element_update(function(player,element,value)
player.print('Text box local: '..value)
end)
local text_box_wrap =
Gui.new_text_box('test-text-box-wrap')
:set_tooltip('Text box wrap')
:set_selectable(false)
:set_word_wrap()
:on_element_update(function(player,element,value)
player.print('Text box wrap: '..value)
end)
tests.Texts = {
['Local field']=text_filed_local,
['Store field']=text_filed_store,
['Local box']=text_box_local,
['Wrap box']=text_box_wrap
}
--[[
Elem Button Tests
> Local -- Simple elem button
> Default -- Simple elem button which has a default value
> Function -- Elem button which has a dynamic default
> Store -- Elem button which stores its value between re-draws
]]
local elem_local =
Gui.new_elem_button('test-elem-local')
:set_tooltip('Elem')
:set_type('item')
:on_element_update(function(player,element,value)
player.print('Elem: '..value)
end)
local elem_default =
Gui.new_elem_button('test-elem-default')
:set_tooltip('Elem default')
:set_type('item')
:set_default('iron-plate')
:on_element_update(function(player,element,value)
player.print('Elem default: '..value)
end)
local elem_function =
Gui.new_elem_button('test-elem-function')
:set_tooltip('Elem function')
:set_type('item')
:set_default(function(player,element)
return 'iron-plate'
end)
:on_element_update(function(player,element,value)
player.print('Elem function: '..value)
end)
local elem_store =
Gui.new_elem_button('test-elem-store')
:set_tooltip('Elem store')
:set_type('item')
:add_store(Gui.player_store)
:on_element_update(function(player,element,value)
player.print('Elem store: '..value)
end)
tests["Elem Buttons"] = {
['Local']=elem_local,
['Default']=elem_default,
['Function']=elem_function,
['Store']=elem_store
}
--[[
Progress bar tests
> Simple -- Progress bar that fills every 2 seconds
> Store -- Progress bar that fills every 5 seconds with synced value
> Reverce -- Progress bar that decreases every 2 seconds
]]
local progressbar_one =
Gui.new_progressbar('test-prog-one')
:set_default_maximum(120)
:on_complete(function(player,element,reset_element)
reset_element()
end)
local progressbar_two =
Gui.new_progressbar('test-prog-one')
:set_default_maximum(300)
:add_store(Gui.force_store)
:on_complete(function(player,element,reset_element)
reset_element()
end)
:on_store_complete(function(category,reset_store)
reset_store()
end)
local progressbar_three =
Gui.new_progressbar('test-prog-one')
:set_default_maximum(120)
:use_count_down()
:on_complete(function(player,element,reset_element)
reset_element()
end)
Event.add(defines.events.on_tick,function()
progressbar_one:increment()
progressbar_three:decrement()
local categories = Store.get_children(progressbar_two.store)
for category,_ in pairs(categories) do
progressbar_two:increment(1,category)
end
end)
tests["Progress Bars"] = {
['Simple']=progressbar_one,
['Store']=progressbar_two,
['Reverce']=progressbar_three
}

156
expcore/Gui/text.lua Normal file
View File

@@ -0,0 +1,156 @@
--- Gui class define for text fields and text boxs
--[[
>>>> Functions
Text.new_text_field(name) --- Creates a new text field element define
Text._prototype_field:on_element_update(callback) --- Registers a handler for when an element instance updates
Text._prototype_field:on_store_update(callback) --- Registers a handler for when the stored value updates
Text.new_text_box(name) --- Creates a new text box element define
Text._prototype_field:on_element_update(callback) --- Registers a handler for when an element instance updates
Text._prototype_field:on_store_update(callback) --- Registers a handler for when the stored value updates
Text._prototype_box:set_selectable(state) --- Sets the text box to be selectable
Text._prototype_box:set_word_wrap(state) --- Sets the text box to have word wrap
Text._prototype_box:set_read_only(state) --- Sets the text box to be read only
Other functions present from expcore.gui.core
]]
local Gui = require 'expcore.gui.core'
local Game = require 'utils.game'
--- Event call for on_text_changed and store update
-- @tparam define table the define that this is acting on
-- @tparam element LuaGuiElement the element that triggered the event
-- @tparam value string the new text for the text field
local function event_call(define,element,value)
local player = Game.get_player_by_index(element.player_index)
if define.events.on_element_update then
define.events.on_element_update(player,element,value)
end
end
--- Store call for store update
-- @tparam define table the define that this is acting on
-- @tparam element LuaGuiElement the element that triggered the event
-- @tparam value string the new text for the text field
local function store_call(self,element,value)
element.text = value
event_call(self,element,value)
end
local Text = {
_prototype_field=Gui._prototype_factory{
on_element_update = Gui._event_factory('on_element_update'),
on_store_update = Gui._event_factory('on_store_update'),
add_store = Gui._store_factory(store_call),
add_sync_store = Gui._sync_store_factory(store_call)
},
_prototype_box=Gui._prototype_factory{
on_element_update = Gui._event_factory('on_element_update'),
on_store_update = Gui._event_factory('on_store_update'),
add_store = Gui._store_factory(store_call),
add_sync_store = Gui._sync_store_factory(store_call)
}
}
--- Creates a new text field element define
-- @tparam[opt] name string the optional debug name that can be added
-- @treturn table the new text field element define
function Text.new_text_field(name)
local self = Gui._define_factory(Text._prototype_field)
self.draw_data.type = 'textfield'
if name then
self:debug_name(name)
end
self.post_draw = function(element)
if self.selectable then
element.selectable = true
end
if self.word_wrap then
element.word_wrap = true
end
if self.read_only then
element.read_only = true
end
if self.store then
local category = self.categorize and self.categorize(element) or nil
local value = self:get_store(category)
if value then element.text = value end
end
end
Gui.on_text_changed(self.name,function(event)
local element = event.element
local value = element.text
if self.store then
local category = self.categorize and self.categorize(element) or value
self:set_store(category,value)
else
event_call(self,element,value)
end
end)
return self
end
--- Creates a new text box element define
-- @tparam[opt] name string the optional debug name that can be added
-- @treturn table the new text box element define
function Text.new_text_box(name)
local self = Text.new_text_field(name)
self.draw_data.type = 'text-box'
local mt = getmetatable(self)
mt.__index = Text._prototype_box
return self
end
--- Sets the text box to be selectable
-- @tparam[opt=true] state boolean when false will set the state to false
-- @treturn self table the define to allow for chaining
function Text._prototype_box:set_selectable(state)
if state == false then
self.selectable = false
else
self.selectable = true
end
return self
end
--- Sets the text box to have word wrap
-- @tparam[opt=true] state boolean when false will set the state to false
-- @treturn self table the define to allow for chaining
function Text._prototype_box:set_word_wrap(state)
if state == false then
self.word_wrap = false
else
self.word_wrap = true
end
return self
end
--- Sets the text box to be read only
-- @tparam[opt=true] state boolean when false will set the state to false
-- @treturn self table the define to allow for chaining
function Text._prototype_box:set_read_only(state)
if state == false then
self.read_only = false
else
self.read_only = true
end
return self
end
return Text

101
expcore/Gui/toolbar.lua Normal file
View File

@@ -0,0 +1,101 @@
--- Gui structure for the toolbar (top left)
--[[
>>>> Example format
-- this is the same as any other button define, this just automatically draws it
-- you can use add_button if you already defined the button
local toolbar_button =
Toolbar.new_button('print-click')
:on_click(function(player,_element)
player.print('You clicked a button!')
end)
>>>> Functions
Toolbar.new_button(name) --- Adds a new button to the toolbar
Toolbar.add_button(button) --- Adds an existing buttton to the toolbar
Toolbar.update(player) --- Updates the player's toolbar with an new buttons or expected change in auth return
]]
local Buttons = require 'expcore.gui.buttons'
local Gui = require 'expcore.gui.core'
local Roles = require 'expcore.roles'
local Event = require 'utils.event'
local Game = require 'utils.game'
local Toolbar = {
permisison_names = {},
buttons = {}
}
function Toolbar.allowed(player,define_name)
local permisison_name = Toolbar.permisison_names[define_name] or define_name
return Roles.player_allowed(player,permisison_name)
end
function Toolbar.permission_alias(define_name,permisison_name)
Toolbar.permisison_names[define_name] = permisison_name
end
--- Adds a new button to the toolbar
-- @tparam[opt] name string when given allows an alias to the button for the permission system
-- @treturn table the button define
function Toolbar.new_button(name)
local button = Buttons.new_button()
button:set_post_authenticator(Toolbar.allowed)
Toolbar.add_button(button)
Toolbar.permission_alias(button.name,name)
return button
end
--- Adds an existing buttton to the toolbar
-- @tparam button table the button define for the button to be added
function Toolbar.add_button(button)
table.insert(Toolbar.buttons,button)
Gui.allow_player_to_toggle_top_element_visibility(button.name)
Gui.on_player_show_top(button.name,function(event)
if not button.post_authenticator(event.player,button.name) then
event.element.visible = false
end
end)
if not button.post_authenticator then
button:set_post_authenticator(function() return true end)
end
end
--- Updates the player's toolbar with an new buttons or expected change in auth return
-- @tparam player LuaPlayer the player to update the toolbar for
function Toolbar.update(player)
local top = Gui.get_top_element_flow(player)
if not top then return end
local visible = top[Gui.top_toggle_button_name].caption == '<'
for _,button in pairs(Toolbar.buttons) do
local element
if top[button.name] then element = top[button.name]
else element = button:draw_to(top) end
if button.post_authenticator(player,button.name) then
element.visible = visible
element.enabled = true
else
element.visible = false
element.enabled = false
end
end
end
--- When there is a new player they will have the toolbar update
Event.add(defines.events.on_player_created,function(event)
local player = Game.get_player_by_index(event.player_index)
Toolbar.update(player)
end)
--- When a player gets a new role they will have the toolbar updated
Event.add(Roles.player_role_assigned,function(event)
local player = Game.get_player_by_index(event.player_index)
Toolbar.update(player)
end)
--- When a player loses a role they will have the toolbar updated
Event.add(Roles.player_role_unassigned,function(event)
local player = Game.get_player_by_index(event.player_index)
Toolbar.update(player)
end)
return Toolbar

View File

@@ -218,7 +218,7 @@
]]
local Game = require 'utils.game'
local player_return = ext_require('expcore.common','player_return')
local player_return,write_json = ext_require('expcore.common','player_return','write_json')
local Commands = {
defines={ -- common values are stored error like signals
@@ -580,14 +580,14 @@ end
-- logs command usage to file
local function command_log(player,command,comment,params,raw,details)
local player_name = player and player.name or '<Server>'
game.write_file('log/commands.log',game.table_to_json{
write_json('log/commands.log',{
player_name=player_name,
command_name=command.name,
comment=comment,
details=details,
params=params,
raw=raw
}..'\n',true,0)
})
end
--- Main event function that is ran for all commands, used internally please avoid direct use

View File

@@ -7,12 +7,34 @@
Public.type_check_error(value,test_type,error_message,level) --- Raises an error if the value is of the incorrect type
Public.param_check(value,test_type,param_name,param_number) --- Raises an error when the value is the incorrect type, uses a consistent error message format
Public.extract_keys(tbl,...) --- Extracts certain keys from a table
Public.player_return(value,colour,player) --- Will return a value of any type to the player/server console, allows colour for in-game players
Public.write_json(path,tbl) --- Writes a table object to a file in json format
Public.opt_require(path) --- Calls a require that will not error if the file is not found
Public.ext_require(path,...) --- Calls a require and returns only the keys given, file must return a table
Public.format_time(ticks,options) --- Formats tick into a clean format, denominations from highest to lowest
Public.move_items(items,surface,position,radius,chest_type) --- Moves items to the position and stores them in the closest entity of the type given
Public.print_grid_value(value, surface, position, scale, offset, immutable) --- Prints a colored value on a location.
Public.print_colored_grid_value(value, surface, position, offset, immutable,
color_value, base_color, delta_color, under_bound, over_bound) --- Prints a colored value on a location. with extra settings.
Public.clear_flying_text(surface) --- Clears all flying text entites on a surface
Public.string_contains(s, contains) --- Tests if a string contains a given substring.
Public.extract_keys(tbl,...) --- Extracts certain keys from a table
Public.enum(tbl) --- Converts a table to an enum
Public.auto_complete(options,input,use_key,rtn_key) --- Returns the closest match to the input
Public.table_keys(tbl) --- Returns all the keys of a table
Public.table_values(tbl) --- Returns all the values of a table
Public.table_alphanumsort(tbl) --- Returns the list is a sorted way that would be expected by people (this is by key)
Public.table_keysort(tbl) --- Returns the list is a sorted way that would be expected by people (this is by key) (faster alterative than above)
Public.format_chat_colour(message,color) --- Returns a message with valid chat tags to change its colour
Public.format_chat_colour_localized(message,color) --- Returns a message with valid chat tags to change its colour, using localization
Public.format_chat_player_name(player,raw_string) --- Returns the players name in the players color
]]
local Colours = require 'resources.color_presets'
@@ -61,19 +83,6 @@ function Public.param_check(value,test_type,param_name,param_number)
return true
end
--- Extracts certain keys from a table
-- @usage local key_three, key_one = extract({key_one='foo',key_two='bar',key_three=true},'key_three','key_one')
-- @tparam tbl table the table which contains the keys
-- @tparam ... string the names of the keys you want extracted
-- @return the keys in the order given
function Public.extract_keys(tbl,...)
local values = {}
for _,key in pairs({...}) do
table.insert(values,tbl[key])
end
return unpack(values)
end
--- Will return a value of any type to the player/server console, allows colour for in-game players
-- @usage player_return('Hello, World!') -- returns 'Hello, World!' to game.player or server console
-- @usage player_return('Hello, World!','green') -- returns 'Hello, World!' to game.player with colour green or server console
@@ -86,8 +95,8 @@ function Public.player_return(value,colour,player)
player = player or game.player
-- converts the value to a string
local returnAsString
if Public.type_check(value,'table') then
if Public.type_check(value.__self,'userdata') then
if Public.type_check(value,'table') or type(value) == 'userdata' then
if Public.type_check(value.__self,'userdata') or type(value) == 'userdata' then
-- value is userdata
returnAsString = 'Cant Display Userdata'
elseif Public.type_check(value[1],'string') and string.find(value[1],'.+[.].+') and not string.find(value[1],'%s') then
@@ -115,6 +124,13 @@ function Public.player_return(value,colour,player)
else rcon.print(returnAsString) end
end
--- Writes a table object to a file in json format
-- @tparam path string the path of the file to write include / to use dir
-- @tpatam tbl table the table that will be converted to a json string and wrote to file
function Public.write_json(path,tbl)
game.write_file(path,game.table_to_json(tbl)..'\n',true,0)
end
--- Calls a require that will not error if the file is not found
-- @usage local file = opt_require('file.not.present') -- will not cause any error
-- @tparam path string the path that you want to require
@@ -413,6 +429,8 @@ function Public.print_colored_grid_value(value, surface, position, offset, immut
}.active = false
end
--- Clears all flying text entites on a surface
-- @tparam surface LuaSurface the surface to clear
function Public.clear_flying_text(surface)
local entities = surface.find_entities_filtered{name ='flying-text'}
for _,entity in pairs(entities) do
@@ -430,7 +448,41 @@ function Public.string_contains(s, contains)
return s and string.find(s, contains) ~= nil
end
--- Returns the closest match to a key
--- Extracts certain keys from a table
-- @usage local key_three, key_one = extract({key_one='foo',key_two='bar',key_three=true},'key_three','key_one')
-- @tparam tbl table the table which contains the keys
-- @tparam ... string the names of the keys you want extracted
-- @return the keys in the order given
function Public.extract_keys(tbl,...)
local values = {}
for _,key in pairs({...}) do
table.insert(values,tbl[key])
end
return unpack(values)
end
--- Converts a table to an enum
-- @tparam tbl table the table that will be converted
-- @treturn table the new table that acts like an enum
function Public.enum(tbl)
local rtn = {}
for k,v in pairs(tbl) do
if type(k) ~= 'number' then
rtn[v]=k
end
end
for k,v in pairs(tbl) do
if type(k) == 'number' then
table.insert(rtn,v)
end
end
for k,v in pairs(rtn) do
rtn[v]=k
end
return rtn
end
--- Returns the closest match to the input
-- @tparam options table a table of options for the auto complete
-- @tparam input string the input string that will be completed
-- @tparam[opt=false] use_key boolean when true the keys of options will be used as the options

252
expcore/gui.lua Normal file
View File

@@ -0,0 +1,252 @@
--- This file is used to require all the different elements of the gui module
-- each module has an outline here but for more details see their seperate files in ./gui
local Gui = require 'expcore.gui.core'
--[[
Gui._prototype_factory(tbl) --- Used internally to create new prototypes for element defines
Gui._event_factory(name) --- Used internally to create event handler adders for element defines
Gui._store_factory(callback) --- Used internally to create store adders for element defines
Gui._sync_store_factory(callback) --- Used internally to create synced store adders for element defines
Gui._define_factory(prototype) --- Used internally to create new element defines from a class prototype
Gui._prototype:uid() --- Gets the uid for the element define
Gui._prototype:debug_name(name) --- Sets a debug alias for the define
Gui._prototype:set_caption(caption) --- Sets the caption for the element define
Gui._prototype:set_tooltip(tooltip) --- Sets the tooltip for the element define
Gui._prototype:on_element_update(callback) --- Add a hander to run on the general value update event, different classes will handle this event differently
Gui._prototype:set_pre_authenticator(callback) --- Sets an authenticator that blocks the draw function if check fails
Gui._prototype:set_post_authenticator(callback) --- Sets an authenticator that disables the element if check fails
Gui._prototype:draw_to(element) --- Draws the element using what is in the draw_data table, allows use of authenticator if present, registers new instances if store present
Gui.draw(name,element) --- Draws a copy of the element define to the parent element, see draw_to
Gui._prototype:add_store(categorize) --- Adds a store location for the define that will save the state of the element, categorize is a function that returns a string
Gui._prototype:add_sync_store(location,categorize) --- Adds a store location for the define that will sync between games, categorize is a function that returns a string
Gui._prototype:on_store_update(callback) --- Adds a event callback for when the store changes are other events are not gauenteted to be raised
Gui.player_store(element) --- A categorize function to be used with add_store, each player has their own value
Gui.force_store(element) --- A categorize function to be used with add_store, each force has its own value
Gui.surface_store(element) --- A categorize function to be used with add_store, each surface has its own value
Gui._prototype:get_store(category) --- Gets the value in this elements store, category needed if categorize function used
Gui._prototype:set_store(category,value) --- Sets the value in this elements store, category needed if categorize function used
Gui.get_store(name,category) --- Gets the value that is stored for a given element define, category needed if categorize function used
Gui.set_store(name,category,value) --- Sets the value stored for a given element define, category needed if categorize function used
Gui.toggle_enable(element) --- Will toggle the enabled state of an element
Gui.toggle_visible(element) --- Will toggle the visiblity of an element
]]
local Instances = require 'expcore.gui.instances'
Gui.new_instance_group = Instances.registers
Gui.get_instances = Instances.get_elements
Gui.add_instance = Instances.get_elements
Gui.update_instances = Instances.apply_to_elements
Gui.classes.instances = Instances
local Button = require 'expcore.gui.buttons'
Gui.new_button = Button.new_button
Gui.classes.button = Button
--[[
Button.new_button(name) --- Creates a new button element define
Button._prototype:on_click(player,element) --- Registers a handler for when the button is clicked
Button._prototype:on_left_click(player,element) --- Registers a handler for when the button is clicked with the left mouse button
Button._prototype:on_right_click(player,element) --- Registers a handler for when the button is clicked with the right mouse button
Button._prototype:set_sprites(sprite,hovered_sprite,clicked_sprite) --- Adds sprites to a button making it a spirte button
Button._prototype:set_click_filter(filter,...) --- Adds a click / mouse button filter to the button
Button._prototype:set_key_filter(filter,...) --- Adds a control key filter to the button
]]
local Checkbox = require 'expcore.gui.checkboxs'
Gui.new_checkbox = Checkbox.new_checkbox
Gui.new_radiobutton = Checkbox.new_radiobutton
Gui.new_radiobutton_option_set = Checkbox.new_option_set
Gui.draw_option_set = Checkbox.draw_option_set
Gui.classes.checkbox = Checkbox
--[[
Checkbox.new_checkbox(name) --- Creates a new checkbox element define
Checkbox._prototype_checkbox:on_element_update(callback) --- Registers a handler for when an element instance updates
Checkbox._prototype_checkbox:on_store_update(callback) --- Registers a handler for when the stored value updates
Checkbox.new_radiobutton(name) --- Creates a new radiobutton element define
Checkbox._prototype_radiobutton:on_element_update(callback) --- Registers a handler for when an element instance updates
Checkbox._prototype_radiobutton:on_store_update(callback) --- Registers a handler for when the stored value updates
Checkbox._prototype_radiobutton:add_as_option(option_set,option_name) --- Adds this radiobutton to be an option in the given option set (only one can be true at a time)
Checkbox.new_option_set(name,callback,categorize) --- Registers a new option set that can be linked to radiobutotns (only one can be true at a time)
Checkbox.draw_option_set(name,element) --- Draws all radiobuttons that are part of an option set at once (Gui.draw will not work)
Checkbox.reset_radiobutton(element,exclude,recursive) --- Sets all radiobutotn in a element to false (unless excluded) and can act recursivly
]]
local Dropdown = require 'expcore.gui.dropdown'
Gui.new_dropdown = Dropdown.new_dropdown
Gui.new_list_box = Dropdown.new_list_box
Gui.classes.dropdown = Dropdown
--[[
Dropdown.new_dropdown(name) --- Creates a new dropdown element define
Dropdown.new_list_box(name) --- Creates a new list box element define
Dropdown._prototype:on_element_update(callback) --- Registers a handler for when an element instance updates
Dropdown._prototype:on_store_update(callback) --- Registers a handler for when the stored value updates
Dropdown._prototype:new_static_options(options,...) --- Adds new static options to the dropdown which will trigger the general callback
Dropdown._prototype:new_dynamic_options(callback) --- Adds a callback which should return a table of values to be added as options for the dropdown (appended after static options)
Dropdown._prototype:add_option_callback(option,callback) --- Adds a case specific callback which will only run when that option is selected (general case still triggered)
Dropdown.select_value(element,value) --- Selects the option from a dropdown or list box given the value rather than key
Dropdown.get_selected_value(element) --- Returns the currently selected value rather than index
]]
local Slider = require 'expcore.gui.slider'
Gui.new_slider = Slider.new_slider
Gui.classes.slider = Slider
--[[
Slider.new_slider(name) --- Creates a new slider element define
Slider._prototype:on_element_update(callback) --- Registers a handler for when an element instance updates
Slider._prototype:on_store_update(callback) --- Registers a handler for when the stored value updates
Slider._prototype:use_notches(state) --- Adds notches to the slider
Slider._prototype:set_range(min,max) --- Sets the range of a slider, if not used will use default values for a slider
Slider._prototype:draw_label(element) --- Draws a new label and links its value to the value of this slider, if no store then it will only show one value per player
Slider._prototype:enable_auto_draw_label(state) --- Enables auto draw of the label, the label will share the same parent element as the slider
]]
local Text = require 'expcore.gui.text'
Gui.new_text_filed = Text.new_text_field
Gui.new_text_box = Text.new_text_box
Gui.classes.text = Text
--[[
Text.new_text_field(name) --- Creates a new text field element define
Text._prototype_field:on_element_update(callback) --- Registers a handler for when an element instance updates
Text._prototype_field:on_store_update(callback) --- Registers a handler for when the stored value updates
Text.new_text_box(name) --- Creates a new text box element define
Text._prototype_field:on_element_update(callback) --- Registers a handler for when an element instance updates
Text._prototype_field:on_store_update(callback) --- Registers a handler for when the stored value updates
Text._prototype_box:set_selectable(state) --- Sets the text box to be selectable
Text._prototype_box:set_word_wrap(state) --- Sets the text box to have word wrap
Text._prototype_box:set_read_only(state) --- Sets the text box to be read only
]]
local ElemButton = require 'expcore.gui.elem-button'
Gui.new_elem_button = ElemButton.new_elem_button
Gui.classes.elem_button = ElemButton
--[[
ElemButton.new_elem_button(name) --- Creates a new elem button element define
ElemButton._prototype:on_element_update(callback) --- Registers a handler for when an element instance updates
ElemButton._prototype:on_store_update(callback) --- Registers a handler for when the stored value updates
ElemButton._prototype:set_type(type) --- Sets the type of the elem button, the type is required so this must be called at least once
ElemButton._prototype:set_default(value) --- Sets the default value for the elem button, this may be a function or a string
]]
local ProgressBar = require 'expcore.gui.progress-bar'
Gui.new_progressbar = ProgressBar.new_progressbar
Gui.set_progressbar_maximum = ProgressBar.set_maximum
Gui.increment_progressbar = ProgressBar.increment
Gui.decrement_progressbar = ProgressBar.decrement
Gui.classes.progressbar = ProgressBar
--[[
ProgressBar.set_maximum(element,amount,count_down) --- Sets the maximum value that represents the end value of the progress bar
ProgressBar.increment(element,amount) --- Increases the value of the progressbar, if a define is given all of its instances are incremented
ProgressBar.decrement(element,amount) --- Decreases the value of the progressbar, if a define is given all of its instances are decresed
ProgressBar.new_progressbar(name) --- Creates a new progressbar element define
ProgressBar._prototype:set_maximum(amount,count_down) --- Sets the maximum value that represents the end value of the progress bar
ProgressBar._prototype:use_count_down(state) --- Will set the progress bar to start at 1 and trigger when it hits 0
ProgressBar._prototype:increment(amount,category) --- Increases the value of the progressbar
ProgressBar._prototype:increment_filtered(amount,filter) --- Increases the value of the progressbar, if the filter condition is met, does not work with store
ProgressBar._prototype:decrement(amount,category) --- Decreases the value of the progressbar
ProgressBar._prototype:decrement_filtered(amount,filter) --- Decreases the value of the progressbar, if the filter condition is met, does not work with store
ProgressBar._prototype:add_element(element,maximum) --- Adds an element into the list of instances that will are waiting to complete, does not work with store
ProgressBar._prototype:reset_element(element) --- Resets an element, or its store, to be back at the start, either 1 or 0
ProgressBar._prototype:on_complete(callback) --- Triggers when a progress bar element compeltes (hits 0 or 1)
ProgressBar._prototype:on_complete(callback) --- Triggers when a store value completes (hits 0 or 1)
ProgressBar._prototype:event_counter(filter) --- Event handler factory that counts up by 1 every time the event triggeres, can filter which elements are incremented
ProgressBar._prototype:event_countdown(filter) --- Event handler factory that counts down by 1 every time the event triggeres, can filter which elements are decremented
]]
local Toolbar = require 'expcore.gui.toolbar'
Gui.new_toolbar_button = Toolbar.new_button
Gui.add_button_to_toolbar = Toolbar.add_button
Gui.update_toolbar = Toolbar.update
Gui.classes.toolbar = Toolbar
--[[
Toolbar.new_button(name) --- Adds a new button to the toolbar
Toolbar.add_button(button) --- Adds an existing buttton to the toolbar
Toolbar.update(player) --- Updates the player's toolbar with an new buttons or expected change in auth return
]]
local LeftFrames = require 'expcore.gui.left'
Gui.get_left_frame_flow = LeftFrames.get_flow
Gui.toggle_left_frame = LeftFrames.toggle_frame
Gui.new_left_frame = LeftFrames.new_frame
Gui.classes.left_frames = LeftFrames
--[[
LeftFrames.get_flow(player) --- Gets the left frame flow for a player
LeftFrames.get_frame(name,player) --- Gets one frame from the left flow by its name
LeftFrames.get_open(player) --- Gets all open frames for a player, if non are open it will remove the close all button
LeftFrames.toggle_frame(name,player,state) --- Toggles the visiblty of a left frame, or sets its visiblty state
LeftFrames.new_frame(permision_name) --- Creates a new left frame define
LeftFrames._prototype:set_open_by_default(state) --- Sets if the frame is visible when a player joins, can also be a function to return a boolean
LeftFrames._prototype:get_frame(player) --- Gets the frame for this define from the left frame flow
LeftFrames._prototype:is_open(player) --- Returns if the player currently has this define visible
LeftFrames._prototype:toggle(player) --- Toggles the visiblty of the left frame
LeftFrames._prototype:update(player) --- Updates the contents of the left frame, first tries update callback, oter wise will clear and redraw
LeftFrames._prototype:update_all(update_offline) --- Updates the frame for all players, see update
LeftFrames._prototype:redraw(player) --- Redraws the frame by calling on_draw, will always clear the frame
LeftFrames._prototype:redraw_all(update_offline) --- Redraws the frame for all players, see redraw
LeftFrames._prototype:on_draw(player,frame) --- Use to draw your elements to the new frame
LeftFrames._prototype:on_update(player,frame) --- Use to edit your frame when there is no need to redraw it
LeftFrames._prototype:event_handler(action) --- Creates an event handler that will trigger one of its functions, use with Event.add
]]
local CenterFrames = require 'expcore.gui.center'
Gui.get_center_flow = CenterFrames.get_flow
Gui.toggle_left_frame = CenterFrames.toggle_frame
Gui.draw_center_frame = CenterFrames.draw_frame
Gui.redraw_center_frame = CenterFrames.redraw_frames
Gui.new_center_frame = CenterFrames.new_frame
Gui.classes.center_frames = CenterFrames
--[[
CenterFrames.get_flow(player) --- Gets the center flow for a player
CenterFrames.clear_flow(player) --- Clears the center flow for a player
CenterFrames.draw_frame(player,name) --- Draws the center frame for a player, if already open then will do nothing
CenterFrames.redraw_frame(player,name) --- Draws the center frame for a player, if already open then will destory it and redraw
CenterFrames.toggle_frame(player,name,state) --- Toggles if the frame is currently open or not, will open if closed and close if open
CenterFrames.new_frame(permision_name) --- Sets the frame to be the current active gui when opened and closes all other frames
CenterFrames._prototype:on_draw(player,frame) --- Use to draw your elements onto the new frame
CenterFrames._prototype:set_auto_focus(state) --- Sets the frame to be the current active gui when opened and closes all other frames
CenterFrames._prototype:draw_frame(player) --- Draws this frame to the player, if already open does nothing (will call on_draw to draw to the frame)
CenterFrames._prototype:redraw_frame(player) --- Draws this frame to the player, if already open it will remove it and redraw it (will call on_draw to draw to the frame)
CenterFrames._prototype:toggle_frame(player) --- Toggles if the frame is open, if open it will close it and if closed it will open it
CenterFrames._prototype:event_handler(action) --- Creates an event handler that will trigger one of its functions, use with Event.add
]]
local PopupFrames = require 'expcore.gui.popups'
Gui.get_popup_flow = PopupFrames.get_flow
Gui.open_popup = PopupFrames.open
Gui.new_popup = PopupFrames.new_popup
Gui.classes.popup_frames = PopupFrames
--[[
PopupFrames.get_flow(player) --- Gets the left flow that contains the popup frames
PopupFrames.open(define_name,player,open_time,...) --- Opens a popup for the player, can give the amount of time it is open as well as params for the draw function
PopupFrames.close_progress --- Progress bar which when depleaded will close the popup frame
PopupFrames.close_button --- A button which can be used to close the gui before the timer runs out
PopupFrames.new_popup(name) --- Creates a new popup frame define
PopupFrames._prototype:set_default_open_time(amount) --- Sets the default open time for the popup, will be used if non is provided with open
PopupFrames._prototype:open(player,open_time,...) --- Opens this define for a player, can be given open time and any other params for the draw function
]]
return Gui

View File

@@ -24,4 +24,9 @@ error-log-format-promote=[ERROR] rolePromote/__1__ :: __2__
game-message-assign=__1__ has been assigned to __2__ by __3__
game-message-unassign=__1__ has been unassigned from __2__ by __3__
reject-role=Invalid Role Name.
reject-player-role=Player has a higher role.
reject-player-role=Player has a higher role.
[gui_util]
button_tooltip=Shows / hides the Toolbar Gui buttons.
[expcore-gui]

View File

@@ -160,6 +160,7 @@ local Global = require 'utils.global'
local Event = require 'utils.event'
local Groups = require 'expcore.permission_groups'
local Colours = require 'resources.color_presets'
local write_json = ext_require('expcore.common','write_json')
local Roles = {
config={
@@ -223,12 +224,12 @@ local function emit_player_roles_updated(player,type,roles,by_player_name,skip_g
by_player_index=by_player_index,
roles=roles
})
game.write_file('log/roles.log',game.table_to_json{
write_json('log/roles.log',{
player_name=player.name,
by_player_name=by_player_name,
type=type,
roles_changed=role_names
}..'\n',true,0)
})
end
--- Returns a string which contains all roles in index order displaying all data for them

306
expcore/store.lua Normal file
View File

@@ -0,0 +1,306 @@
--- Adds an easy way to store and watch for updates to a value
--[[
>>>> Basic Use
At the most basic level this allows for the naming of locations to store in the global table, the second feature is that you are
able to listen for updates of this value, which means that when ever the set function is called it will trigger the update callback.
This may be useful when storing config values and when they get set you want to make sure it is taken care of, or maybe you want
to have a value that you can trigger an update of from different places.
-- this will register a new location called 'scenario.dificutly' and the start value is 'normal'
-- note that setting a start value is optional and we could take nil to mean normal
Store.register('scenario.dificutly',function(value)
game.print('The scenario dificulty has be set to: '..value)
end,'normal')
-- this will return 'normal' as we have not set the value anywhere else
Store.get('scenario.dificutly')
-- this will set the value in the store to 'hard' and will trigger the update callback which will print a message to the game
Store.set('scenario.dificutly','hard')
>>>> Using Children
One limitation of store is that all lcoations must be registered to avoid desyncs, to get round this issue "children" can be used.
When you set the value of a child it does not have its own update callback so rather the "partent" location which has been registerd
will have its update value called with a second param of the name of that child.
This may be useful when you want a value of each player or force and since you cant regisier every player at the start you must use
the players name as the child name.
-- this will register the lcoation 'scenario.score' where we plan to use force names as the child
-- here we have not set a start value since it will be an empty location
Store.register('scenario.score',function(value,child)
game.print(child..' now has a score of '..value)
end)
-- this will return nil, but will not error as children dont need to be registerd
Store.get_child('scenario.score','player')
-- this will set 'player' to have a value of 10 for 'scenario.score' and trigger the game message print
Store.set_child('scenario.score','player',10)
-- this would be the same as Store.get however this will return an empty table rather than nil
Store.get_children('scenario.score')
>>>> Using Sync
There is the option to use Store.register_synced which is the same as Store.register however you can combine this with an external script
which can read the output from 'script-output/log/store.log' and have it send rcon commands back to the game allowing for cross instance
syncing of values.
This may be useful when you want to have a value change effect multiple instances or even if you just want a database to store values so
you can sync data between map resets.
-- this example will register the location 'stastics.total-play-time' where we plan to use plan names as the child
-- note that the location must be the same across instances
Store.register_synced('stastics.total-play-time',function(value,child)
game.print(child..' now has now played for '..value)
end)
-- use of set,get,set_child and get_chlid are all the same as non synced
>>>> Using a watch function
Some times the value that you want is not some made up value that you have but rather a factorio value or something similar, in order to recive
updates on these values (if the factorio api does not provide an event for it) you will need to add a watch function to update the store when the
values changes. You will want to keep these watch functions small since they run every tick.
-- this will register a location 'game.speed', note that the lcoation can be anything but we chose 'game.speed' to match what we are watching
-- also note that we do not need a start value here since it will be set on the first tick, but you may want a start value to avoid a trigger of the callback
Store.register('game.speed',function(value)
game.print('The game speed has been set to: '..value)
end)
-- this will add the watch function to the lcoation, every tick the function is ran and the value returned in compeared to the stored value
-- if the two values are different then the store is overriden and the update function is called
Store.add_watch('game.speed',function()
return game.speed
end)
>>>> Alternative method
Some people may prefer to use a varible rather than a string for formating reasons here is an example. Also for any times when
there will be little external input Store.uid_location() can be used to generate non conflicting locations, use of register_synced will
still require a name other wise there may be mirgration issuses.
local store_game_speed = Store.uid_location()
Store.register(store_game_speed,function(value)
game.print('The game speed has been set to: '..value)
end)
Store.add_watch(store_game_speed,function()
return game.speed
end)
]]
local Global = require 'utils.global'
local Event = require 'utils.event'
local write_json = ext_require('expcore.common','write_json','table_keys')
local Token = require 'utils.token'
local Store = {
data={},
callbacks={},
synced={},
watchers={}
}
Global.register(Store.data,function(tbl)
Store.data = tbl
end)
--- Check for if a lcoation is registered
-- @tparam location string the location to test for
-- @treturn boolean true if registered
function Store.is_registered(location)
return not not Store.callbacks[location]
end
--- Returns a unqiue name that can be used for a store
-- @treturn string a unqiue name
function Store.uid_location()
return tostring(Token.uid())
end
--- Registers a new location with an update callback which is triggered when the value updates
-- @tparam location string a unique string that points to the data, string used rather than token to allow migration
-- @tparam callback function this callback will be called when the stored value is set to a new value
-- @tparam[opt] start_value any this value will be the inital value that is stored at this location
function Store.register(location,callback,start_value)
if _LIFECYCLE ~= _STAGE.control then
return error('Can only be called during the control stage', 2)
end
if Store.callbacks[location] then
return error('Location is already registered', 2)
end
if type(callback) ~= 'function' then
return error('Callback must be a function', 2)
end
Store.data[location] = start_value
Store.callbacks[location] = callback
return location
end
--- Registers a new cross server synced location with an update callback, and external script is required for cross server
-- @tparam location string a unique string that points to the data, string used rather than token to allow migration
-- @tparam callback function this callback will be called when the stored value is set to a new value
-- @tparam[opt] start_value any this value will be the inital value that is stored at this location
function Store.register_synced(location,callback,start_value)
if _LIFECYCLE ~= _STAGE.control then
return error('Can only be called during the control stage', 2)
end
if Store.callbacks[location] then
return error('Location is already registered', 2)
end
if type(callback) ~= 'function' then
return error('Callback must be a function', 2)
end
Store.data[location] = start_value
Store.callbacks[location] = callback
Store.synced[location] = true
end
--- Adds a function that will be checked every tick for a change in the returned value, when the value changes it will be saved in the store
-- @tparam location string the location where the data will be saved and compeared to, must already be a registered location
-- @tparam callback function this function will be called every tick to check for a change in value
function Store.add_watch(location,callback)
if _LIFECYCLE ~= _STAGE.control then
return error('Can only be called during the control stage', 2)
end
if Store.callbacks[location] then
return error('Location is already being watched', 2)
end
if type(callback) ~= 'function' then
return error('Callback must be a function', 2)
end
Store.watchers[location] = callback
end
--- Gets the value stored at a location, this location must be registered
-- @tparam location string the location to get the data from
-- @tparam[opt=false] no_error boolean when true no error is returned if the location is not registered
-- @treturn any the data which was stored at the location
function Store.get(location,no_error)
if not Store.callbacks[location] and not no_error then
return error('Location is not registered', 2)
end
return Store.data[location]
end
--- Sets the value at a location, this location must be registered, if server synced it will emit the change to file
-- @tparam location string the location to set the data to
-- @tparam value any the new value to set at the location, value may be reverted if there is a watch callback
-- @treturn boolean true if it was successful
function Store.set(location,value)
if not Store.callbacks[location] then
return error('Location is not registered', 2)
end
Store.data[location] = value
Store.callbacks[location](value)
if Store.synced[location] then
write_json('log/store.log',{
location=location,
value=value
})
end
return true
end
--- Gets all non nil children at a location, children can be added and removed during runtime
-- this is similar to Store.get but will always return a table even if it is empty
-- @tparam location string the location to get the children of
-- @treturn table a table containg all the children and they values
function Store.get_children(location)
local store = Store.get(location)
if type(store) ~= 'table' and store ~= nil then
return error('Location has a non table value', 2)
end
return store or {}
end
--- Gets the value of the child to a location, children can be added and removed during runtime
-- @tparam location string the location of which the child is located
-- @tparam child string the child element to get the value of
-- @treturn any the value which was stored at that location
function Store.get_child(location,child)
local store = Store.get(location)
if type(store) ~= 'table' and store ~= nil then
return error('Location has a non table value', 2)
end
return store and store[child]
end
--- Sets the value of the chlid to a location, children can be added and removed during runtime
-- when a child is set it will call the update handler of the parent allowing children be to added at runtime
-- this may be used when a player joins the game and the child is the players name
-- @tparam location string the location of which the child is located
-- @tparam child string the child element to set the value of
-- @tparam value any the value to set at this location
-- @treturn boolean true if it was successful
function Store.set_child(location,child,value)
local store = Store.get(location)
if type(store) ~= 'table' and store ~= nil then
return error('Location has a non table value', 2)
end
if not store then
Store.data[location] = {}
end
Store.data[location][child] = value
Store.callbacks[location](value,child)
if Store.synced[location] then
write_json('log/store.log',{
location=location,
child=child,
value=value
})
end
return true
end
-- Event handler for the watcher callbacks
Event.add(defines.events.on_tick,function()
local errors = {}
for location,callback in pairs(Store.watchers) do
local store_old = Store.data[location]
local success,store_new = pcall(callback)
if not success then
table.insert(errors,store_new)
else
if type(store_old) ~= type(store_new)
or type(store_old) == 'table' and not table.compare(store_new,store_new)
or store_old ~= store_new then
Store.data[location] = store_new
Store.callbacks[location](store_new)
end
end
end
if #errors > 0 then
error(table.concat(errors,'; '))
end
end)
return Store

View File

@@ -1,5 +1,4 @@
time-symbol-days-short=__1__d
color-tag=[color=__1__]__2__[/color]
[expcore-commands]
unauthorized=Unauthorized, Access is denied due to invalid credentials
@@ -25,4 +24,9 @@ error-log-format-promote=[ERROR] rolePromote/__1__ :: __2__
game-message-assign=__1__ has been assigned to __2__ by __3__
game-message-unassign=__1__ has been unassigned from __2__ by __3__
reject-role=Invalid Role Name.
reject-player-role=Player has a higher role.
reject-player-role=Player has a higher role.
[gui_util]
button_tooltip=Shows / hides the Toolbar Gui buttons.
[expcore-gui]

View File

@@ -0,0 +1,7 @@
local DebugView = require 'modules.gui.debug.main_view'
local Commands = require 'expcore.commands'
Commands.new_command('debug','Opens the debug pannel for viewing tables.')
:register(function(player,raw)
DebugView.open_dubug(player)
end)

View File

@@ -9,13 +9,15 @@ local interface_modules = {
['Commands']=Commands,
['output']=Common.player_return,
['Group']='expcore.permission_groups',
['Roles']='expcore.roles'
['Roles']='expcore.roles',
['Store']='expcore.store',
['Gui']='expcore.gui'
}
-- loads all the modules given in the above table
for key,value in pairs(interface_modules) do
if type(value) == 'string' then
interface_modules[key] = require(value)
interface_modules[key] = Common.opt_require(value)
end
end
@@ -94,5 +96,6 @@ add_interface_callback('tile',function(player) return player.surface.get_tile(pl
return {
add_interface_callback=add_interface_callback,
interface_env=interface_env,
interface_callbacks=interface_callbacks
interface_callbacks=interface_callbacks,
clean_stack_trace=function(str) return str:gsub('%.%.%..-/temp/currently%-playing','') end
}

View File

@@ -0,0 +1,114 @@
local Gui = require 'utils.gui'
local Model = require 'modules.gui.debug.model'
local Color = require 'resources.color_presets'
local dump = Model.dump
local Public = {}
local ignore = {
_G = true,
assert = true,
collectgarbage = true,
error = true,
getmetatable = true,
ipairs = true,
load = true,
loadstring = true,
next = true,
pairs = true,
pcall = true,
print = true,
rawequal = true,
rawlen = true,
rawget = true,
rawset = true,
select = true,
setmetatable = true,
tonumber = true,
tostring = true,
type = true,
xpcall = true,
_VERSION = true,
module = true,
require = true,
package = true,
unpack = true,
table = true,
string = true,
bit32 = true,
math = true,
debug = true,
serpent = true,
log = true,
table_size = true,
global = true,
remote = true,
commands = true,
settings = true,
rcon = true,
script = true,
util = true,
mod_gui = true,
game = true,
rendering = true
}
local header_name = Gui.uid_name()
local left_panel_name = Gui.uid_name()
local right_panel_name = Gui.uid_name()
Public.name = '_G'
function Public.show(container)
local main_flow = container.add {type = 'flow', direction = 'horizontal'}
local left_panel = main_flow.add {type = 'scroll-pane', name = left_panel_name}
local left_panel_style = left_panel.style
left_panel_style.width = 300
for key, value in pairs(_G) do
if not ignore[key] then
local header =
left_panel.add({type = 'flow'}).add {type = 'label', name = header_name, caption = tostring(key)}
Gui.set_data(header, value)
end
end
local right_panel = main_flow.add {type = 'text-box', name = right_panel_name}
right_panel.read_only = true
right_panel.selectable = true
local right_panel_style = right_panel.style
right_panel_style.vertically_stretchable = true
right_panel_style.horizontally_stretchable = true
right_panel_style.maximal_width = 1000
right_panel_style.maximal_height = 1000
Gui.set_data(left_panel, {right_panel = right_panel, selected_header = nil})
end
Gui.on_click(
header_name,
function(event)
local element = event.element
local value = Gui.get_data(element)
local left_panel = element.parent.parent
local left_panel_data = Gui.get_data(left_panel)
local right_panel = left_panel_data.right_panel
local selected_header = left_panel_data.selected_header
if selected_header then
selected_header.style.font_color = Color.white
end
element.style.font_color = Color.orange
left_panel_data.selected_header = element
local content = dump(value)
right_panel.text = content
end
)
return Public

View File

@@ -0,0 +1,117 @@
local Event = require 'utils.event'
local table = require 'utils.table'
local Gui = require 'utils.gui'
local Model = require 'modules.gui.debug.model'
local format = string.format
local insert = table.insert
local events = defines.events
-- Constants
local events_to_keep = 10
-- Local vars
local Public = {
name = 'Events'
}
local name_lookup = {}
-- GUI names
local checkbox_name = Gui.uid_name()
-- global tables
local enabled = {}
local last_events = {}
global.debug_event_view = {
enabled = enabled,
last_events = last_events
}
function Public.on_open_debug()
local tbl = global.debug_event_view
if tbl then
enabled = tbl.enabled
last_events = tbl.last_events
else
enabled = {}
last_events = {}
global.debug_event_view = {
enabled = enabled,
last_events = last_events
}
end
Public.on_open_debug = nil
end
-- Local functions
local function event_callback(event)
local id = event.name
if not enabled[id] then
return
end
local name = name_lookup[id]
if not last_events[name] then
last_events[name] = {}
end
insert(last_events[name], 1, event)
last_events[name][events_to_keep + 1] = nil
event.name = nil
local str = format('%s (id = %s): %s', name, id, Model.dump(event))
game.print(str)
log(str)
end
local function on_gui_checked_state_changed(event)
local element = event.element
local name = element.caption
local id = events[name]
local state = element.state and true or false
element.state = state
if state then
enabled[id] = true
else
enabled[id] = false
end
end
-- GUI
-- Create a table with events sorted by their names
local grid_builder = {}
for name, _ in pairs(events) do
grid_builder[#grid_builder + 1] = name
end
table.sort(grid_builder)
function Public.show(container)
local main_frame_flow = container.add({type = 'flow', direction = 'vertical'})
local scroll_pane = main_frame_flow.add({type = 'scroll-pane'})
local gui_table = scroll_pane.add({type = 'table', column_count = 3, draw_horizontal_lines = true})
for _, event_name in pairs(grid_builder) do
local index = events[event_name]
gui_table.add({type = 'flow'}).add {
name = checkbox_name,
type = 'checkbox',
state = enabled[index] or false,
caption = event_name
}
end
end
Gui.on_checked_state_changed(checkbox_name, on_gui_checked_state_changed)
-- Event registers (TODO: turn to removable hooks.. maybe)
for name, id in pairs(events) do
name_lookup[id] = name
Event.add(id, event_callback)
end
return Public

View File

@@ -0,0 +1,133 @@
local Gui = require 'utils.gui'
local Model = require 'modules.gui.debug.model'
local Color = require 'resources.color_presets'
local dump = Model.dump
local dump_text = Model.dump_text
local concat = table.concat
local Public = {}
local ignore = {tokens = true}
local header_name = Gui.uid_name()
local left_panel_name = Gui.uid_name()
local right_panel_name = Gui.uid_name()
local input_text_box_name = Gui.uid_name()
local refresh_name = Gui.uid_name()
Public.name = 'global'
function Public.show(container)
local main_flow = container.add {type = 'flow', direction = 'horizontal'}
local left_panel = main_flow.add {type = 'scroll-pane', name = left_panel_name}
local left_panel_style = left_panel.style
left_panel_style.width = 300
for key, _ in pairs(global) do
if not ignore[key] then
local header =
left_panel.add({type = 'flow'}).add {type = 'label', name = header_name, caption = tostring(key)}
Gui.set_data(header, key)
end
end
local right_flow = main_flow.add {type = 'flow', direction = 'vertical'}
local right_top_flow = right_flow.add {type = 'flow', direction = 'horizontal'}
local input_text_box = right_top_flow.add {type = 'text-box', name = input_text_box_name}
local input_text_box_style = input_text_box.style
input_text_box_style.horizontally_stretchable = true
input_text_box_style.height = 32
input_text_box_style.maximal_width = 1000
local refresh_button =
right_top_flow.add {type = 'sprite-button', name = refresh_name, sprite = 'utility/reset', tooltip = 'refresh'}
local refresh_button_style = refresh_button.style
refresh_button_style.width = 32
refresh_button_style.height = 32
local right_panel = right_flow.add {type = 'text-box', name = right_panel_name}
right_panel.read_only = true
right_panel.selectable = true
local right_panel_style = right_panel.style
right_panel_style.vertically_stretchable = true
right_panel_style.horizontally_stretchable = true
right_panel_style.maximal_width = 1000
right_panel_style.maximal_height = 1000
local data = {
right_panel = right_panel,
input_text_box = input_text_box,
selected_header = nil,
selected_token_id = nil
}
Gui.set_data(input_text_box, data)
Gui.set_data(left_panel, data)
Gui.set_data(refresh_button, data)
end
Gui.on_click(
header_name,
function(event)
local element = event.element
local key = Gui.get_data(element)
local left_panel = element.parent.parent
local data = Gui.get_data(left_panel)
local right_panel = data.right_panel
local selected_header = data.selected_header
local input_text_box = data.input_text_box
if selected_header then
selected_header.style.font_color = Color.white
end
element.style.font_color = Color.orange
data.selected_header = element
input_text_box.text = concat {"global['", key, "']"}
input_text_box.style.font_color = Color.black
local content = dump(global[key]) or 'nil'
right_panel.text = content
end
)
local function update_dump(text_input, data, player)
local suc, ouput = dump_text(text_input.text, player)
if not suc then
text_input.style.font_color = Color.red
else
text_input.style.font_color = Color.black
data.right_panel.text = ouput
end
end
Gui.on_text_changed(
input_text_box_name,
function(event)
local element = event.element
local data = Gui.get_data(element)
update_dump(element, data, event.player)
end
)
Gui.on_click(
refresh_name,
function(event)
local element = event.element
local data = Gui.get_data(element)
local input_text_box = data.input_text_box
update_dump(input_text_box, data, event.player)
end
)
return Public

View File

@@ -0,0 +1,103 @@
local Gui = require 'utils.gui'
local Color = require 'resources.color_presets'
local Public = {}
local pages = {
require 'modules.gui.debug.redmew_global_view',
require 'modules.gui.debug.global_view',
require 'modules.gui.debug.package_view',
require 'modules.gui.debug._g_view',
require 'modules.gui.debug.event_view'
}
local main_frame_name = Gui.uid_name()
local close_name = Gui.uid_name()
local tab_name = Gui.uid_name()
function Public.open_dubug(player)
for i = 1, #pages do
local page = pages[i]
local callback = page.on_open_debug
if callback then
callback()
end
end
local center = player.gui.center
local frame = center[main_frame_name]
if frame then
return
end
frame = center.add {type = 'frame', name = main_frame_name, caption = 'Debuggertron 3001', direction = 'vertical'}
local frame_style = frame.style
frame_style.height = 600
frame_style.width = 900
local tab_flow = frame.add {type = 'flow', direction = 'horizontal'}
local container = frame.add {type = 'flow'}
container.style.vertically_stretchable = true
local data = {}
for i = 1, #pages do
local page = pages[i]
local tab_button = tab_flow.add({type = 'flow'}).add {type = 'button', name = tab_name, caption = page.name}
local tab_button_style = tab_button.style
Gui.set_data(tab_button, {index = i, frame_data = data})
if i == 1 then
tab_button_style.font_color = Color.orange
data.selected_index = i
data.selected_tab_button = tab_button
data.container = container
Gui.set_data(frame, data)
page.show(container)
end
end
frame.add {type = 'button', name = close_name, caption = 'Close'}
end
Gui.on_click(
tab_name,
function(event)
local element = event.element
local data = Gui.get_data(element)
local index = data.index
local frame_data = data.frame_data
local selected_index = frame_data.selected_index
if selected_index == index then
return
end
local selected_tab_button = frame_data.selected_tab_button
selected_tab_button.style.font_color = Color.black
frame_data.selected_tab_button = element
frame_data.selected_index = index
element.style.font_color = Color.orange
local container = frame_data.container
Gui.clear(container)
pages[index].show(container)
end
)
Gui.on_click(
close_name,
function(event)
local frame = event.player.gui.center[main_frame_name]
if frame then
Gui.destroy(frame)
end
end
)
return Public

147
modules/gui/debug/model.lua Normal file
View File

@@ -0,0 +1,147 @@
local Gui = require 'utils.gui'
local table = require 'utils.table'
local gui_names = Gui.names
local type = type
local concat = table.concat
local inspect = table.inspect
local pcall = pcall
local loadstring = loadstring
local rawset = rawset
local Public = {}
local luaObject = {'{', nil, ", name = '", nil, "'}"}
local luaPlayer = {"{LuaPlayer, name = '", nil, "', index = ", nil, '}'}
local luaEntity = {"{LuaEntity, name = '", nil, "', unit_number = ", nil, '}'}
local luaGuiElement = {"{LuaGuiElement, name = '", nil, "'}"}
local function get(obj, prop)
return obj[prop]
end
local function get_name_safe(obj)
local s, r = pcall(get, obj, 'name')
if not s then
return 'nil'
else
return r or 'nil'
end
end
local function get_lua_object_type_safe(obj)
local s, r = pcall(get, obj, 'help')
if not s then
return
end
return r():match('Lua%a+')
end
local function inspect_process(item)
if type(item) ~= 'table' or type(item.__self) ~= 'userdata' then
return item
end
local suc, valid = pcall(get, item, 'valid')
if not suc then
-- no 'valid' property
return get_lua_object_type_safe(item) or '{NoHelp LuaObject}'
end
if not valid then
return '{Invalid LuaObject}'
end
local obj_type = get_lua_object_type_safe(item)
if not obj_type then
return '{NoHelp LuaObject}'
end
if obj_type == 'LuaPlayer' then
luaPlayer[2] = item.name or 'nil'
luaPlayer[4] = item.index or 'nil'
return concat(luaPlayer)
elseif obj_type == 'LuaEntity' then
luaEntity[2] = item.name or 'nil'
luaEntity[4] = item.unit_number or 'nil'
return concat(luaEntity)
elseif obj_type == 'LuaGuiElement' then
local name = item.name
luaGuiElement[2] = gui_names and gui_names[name] or name or 'nil'
return concat(luaGuiElement)
else
luaObject[2] = obj_type
luaObject[4] = get_name_safe(item)
return concat(luaObject)
end
end
local inspect_options = {process = inspect_process}
function Public.dump(data)
return inspect(data, inspect_options)
end
local dump = Public.dump
function Public.dump_ignore_builder(ignore)
local function process(item)
if ignore[item] then
return nil
end
return inspect_process(item)
end
local options = {process = process}
return function(data)
return inspect(data, options)
end
end
function Public.dump_function(func)
local res = {'upvalues:\n'}
local i = 1
while true do
local n, v = debug.getupvalue(func, i)
if n == nil then
break
elseif n ~= '_ENV' then
res[#res + 1] = n
res[#res + 1] = ' = '
res[#res + 1] = dump(v)
res[#res + 1] = '\n'
end
i = i + 1
end
return concat(res)
end
function Public.dump_text(text, player)
local func = loadstring('return ' .. text)
if not func then
return false
end
rawset(game, 'player', player)
local suc, var = pcall(func)
rawset(game, 'player', nil)
if not suc then
return false
end
return true, dump(var)
end
return Public

View File

@@ -0,0 +1,161 @@
local Gui = require 'utils.gui'
local Color = require 'resources.color_presets'
local Model = require 'modules.gui.debug.model'
local dump_function = Model.dump_function
local loaded = _G.package.loaded
local Public = {}
local ignore = {
_G = true,
package = true,
coroutine = true,
table = true,
string = true,
bit32 = true,
math = true,
debug = true,
serpent = true,
['utils.math'] = true,
util = true,
['utils.inspect'] = true,
['mod-gui'] = true
}
local file_label_name = Gui.uid_name()
local left_panel_name = Gui.uid_name()
local breadcrumbs_name = Gui.uid_name()
local top_panel_name = Gui.uid_name()
local variable_label_name = Gui.uid_name()
local text_box_name = Gui.uid_name()
Public.name = 'package'
function Public.show(container)
local main_flow = container.add {type = 'flow', direction = 'horizontal'}
local left_panel = main_flow.add {type = 'scroll-pane', name = left_panel_name}
local left_panel_style = left_panel.style
left_panel_style.width = 300
for name, file in pairs(loaded) do
if not ignore[name] then
local file_label =
left_panel.add({type = 'flow'}).add {type = 'label', name = file_label_name, caption = name}
Gui.set_data(file_label, file)
end
end
local right_flow = main_flow.add {type = 'flow', direction = 'vertical'}
local breadcrumbs = right_flow.add {type = 'label', name = breadcrumbs_name}
local top_panel = right_flow.add {type = 'scroll-pane', name = top_panel_name}
local top_panel_style = top_panel.style
top_panel_style.height = 200
top_panel_style.maximal_width = 1000
top_panel_style.horizontally_stretchable = true
local text_box = right_flow.add {type = 'text-box', name = text_box_name}
text_box.read_only = true
text_box.selectable = true
local text_box_style = text_box.style
text_box_style.vertically_stretchable = true
text_box_style.horizontally_stretchable = true
text_box_style.maximal_width = 1000
text_box_style.maximal_height = 1000
local data = {
left_panel = left_panel,
breadcrumbs = breadcrumbs,
top_panel = top_panel,
text_box = text_box,
selected_file_label = nil,
selected_variable_label = nil
}
Gui.set_data(left_panel, data)
Gui.set_data(top_panel, data)
end
Gui.on_click(
file_label_name,
function(event)
local element = event.element
local file = Gui.get_data(element)
local left_panel = element.parent.parent
local data = Gui.get_data(left_panel)
local selected_file_label = data.selected_file_label
if selected_file_label then
selected_file_label.style.font_color = Color.white
end
element.style.font_color = Color.orange
data.selected_file_label = element
local top_panel = data.top_panel
local text_box = data.text_box
Gui.clear(top_panel)
local file_type = type(file)
if file_type == 'table' then
for k, v in pairs(file) do
local label =
top_panel.add({type = 'flow'}).add {type = 'label', name = variable_label_name, caption = k}
Gui.set_data(label, v)
end
elseif file_type == 'function' then
text_box.text = dump_function(file)
else
text_box.text = tostring(file)
end
end
)
Gui.on_click(
variable_label_name,
function(event)
local element = event.element
local variable = Gui.get_data(element)
local top_panel = element.parent.parent
local data = Gui.get_data(top_panel)
local text_box = data.text_box
local variable_type = type(variable)
if variable_type == 'table' then
Gui.clear(top_panel)
for k, v in pairs(variable) do
local label =
top_panel.add({type = 'flow'}).add {type = 'label', name = variable_label_name, caption = k}
Gui.set_data(label, v)
end
return
end
local selected_label = data.selected_variable_label
if selected_label and selected_label.valid then
selected_label.style.font_color = Color.white
end
element.style.font_color = Color.orange
data.selected_variable_label = element
if variable_type == 'function' then
text_box.text = dump_function(variable)
else
text_box.text = tostring(variable)
end
end
)
return Public

View File

@@ -0,0 +1,129 @@
local Gui = require 'utils.gui'
local Global = require 'utils.global'
local Token = require 'utils.token'
local Color = require 'resources.color_presets'
local Model = require 'modules.gui.debug.model'
local dump = Model.dump
local dump_text = Model.dump_text
local concat = table.concat
local Public = {}
local header_name = Gui.uid_name()
local left_panel_name = Gui.uid_name()
local right_panel_name = Gui.uid_name()
local input_text_box_name = Gui.uid_name()
local refresh_name = Gui.uid_name()
Public.name = 'Global'
function Public.show(container)
local main_flow = container.add {type = 'flow', direction = 'horizontal'}
local left_panel = main_flow.add {type = 'scroll-pane', name = left_panel_name}
local left_panel_style = left_panel.style
left_panel_style.width = 300
for token_id, token_name in pairs(Global.names) do
local header = left_panel.add({type = 'flow'}).add {type = 'label', name = header_name, caption = token_name}
Gui.set_data(header, token_id)
end
local right_flow = main_flow.add {type = 'flow', direction = 'vertical'}
local right_top_flow = right_flow.add {type = 'flow', direction = 'horizontal'}
local input_text_box = right_top_flow.add {type = 'text-box', name = input_text_box_name}
local input_text_box_style = input_text_box.style
input_text_box_style.horizontally_stretchable = true
input_text_box_style.height = 32
input_text_box_style.maximal_width = 1000
local refresh_button =
right_top_flow.add {type = 'sprite-button', name = refresh_name, sprite = 'utility/reset', tooltip = 'refresh'}
local refresh_button_style = refresh_button.style
refresh_button_style.width = 32
refresh_button_style.height = 32
local right_panel = right_flow.add {type = 'text-box', name = right_panel_name}
right_panel.read_only = true
right_panel.selectable = true
local right_panel_style = right_panel.style
right_panel_style.vertically_stretchable = true
right_panel_style.horizontally_stretchable = true
right_panel_style.maximal_width = 1000
right_panel_style.maximal_height = 1000
local data = {
right_panel = right_panel,
input_text_box = input_text_box,
selected_header = nil
}
Gui.set_data(input_text_box, data)
Gui.set_data(left_panel, data)
Gui.set_data(refresh_button, data)
end
Gui.on_click(
header_name,
function(event)
local element = event.element
local token_id = Gui.get_data(element)
local left_panel = element.parent.parent
local data = Gui.get_data(left_panel)
local right_panel = data.right_panel
local selected_header = data.selected_header
local input_text_box = data.input_text_box
if selected_header then
selected_header.style.font_color = Color.white
end
element.style.font_color = Color.orange
data.selected_header = element
input_text_box.text = concat {'global.tokens[', token_id, ']'}
input_text_box.style.font_color = Color.black
local content = dump(Token.get_global(token_id)) or 'nil'
right_panel.text = content
end
)
local function update_dump(text_input, data, player)
local suc, ouput = dump_text(text_input.text, player)
if not suc then
text_input.style.font_color = Color.red
else
text_input.style.font_color = Color.black
data.right_panel.text = ouput
end
end
Gui.on_text_changed(
input_text_box_name,
function(event)
local element = event.element
local data = Gui.get_data(element)
update_dump(element, data, event.player)
end
)
Gui.on_click(
refresh_name,
function(event)
local element = event.element
local data = Gui.get_data(element)
local input_text_box = data.input_text_box
update_dump(input_text_box, data, event.player)
end
)
return Public

View File

@@ -3,13 +3,11 @@
-- Dependencies
local Game = require 'utils.game'
local Color = require 'resources.color_presets'
local Server = require 'features.server'
-- localized functions
local random = math.random
local sqrt = math.sqrt
local floor = math.floor
local format = string.format
local match = string.match
local insert = table.insert
local concat = table.concat
@@ -201,27 +199,6 @@ function Module.set_and_return(tbl, key, value)
return value
end
--- Takes msg and prints it to all players. Also prints to the log and discord
-- @param msg <string> The message to print
-- @param warning_prefix <string> The name of the module/warning
function Module.action_warning(warning_prefix, msg)
game.print(prefix .. msg, Color.yellow)
msg = format('%s %s', warning_prefix, msg)
log(msg)
Server.to_discord_bold(msg)
end
--- Takes msg and prints it to all players except provided player. Also prints to the log and discord
-- @param msg <string> The message to print
-- @param warning_prefix <string> The name of the module/warning
-- @param player <LuaPlayer> the player not to send the message to
function Module.silent_action_warning(warning_prefix, msg, player)
Module.print_except(prefix .. msg, player, Color.yellow)
msg = format('%s %s', warning_prefix, msg)
log(msg)
Server.to_discord_bold(msg)
end
-- add utility functions that exist in base factorio/util
require 'util'

View File

@@ -40,6 +40,7 @@ end
local function on_init()
_LIFECYCLE = 5 -- on_init
log('[INFO] Entering on_init')
local handlers = event_handlers[init_event_name]
call_handlers(handlers)
@@ -47,10 +48,12 @@ local function on_init()
event_handlers[load_event_name] = nil
_LIFECYCLE = 8 -- Runtime
log('[INFO] Entering runtime')
end
local function on_load()
_LIFECYCLE = 6 -- on_load
log('[INFO] Entering on_load')
local handlers = event_handlers[load_event_name]
call_handlers(handlers)
@@ -58,6 +61,7 @@ local function on_load()
event_handlers[load_event_name] = nil
_LIFECYCLE = 8 -- Runtime
log('[INFO] Entering runtime')
end
local function on_nth_tick_event(event)

View File

@@ -36,7 +36,7 @@ function Global.register_init(tbl, init_handler, callback)
)
end
if _DEBUG then
if _DEBUG or true then
local concat = table.concat
local names = {}

View File

@@ -2,6 +2,7 @@ local Token = require 'utils.token'
local Event = require 'utils.event'
local Game = require 'utils.game'
local Global = require 'utils.global'
local mod_gui = require 'mod-gui'
local Gui = {}
@@ -179,7 +180,7 @@ Gui.on_player_show_top = custom_handler_factory(on_visible_handlers)
Gui.on_pre_player_hide_top = custom_handler_factory(on_pre_hidden_handlers)
--- Allows the player to show / hide this element.
-- The element must be part in gui.top.
-- The element must be in Gui.get_top_element_flow(player)
-- This function must be called in the control stage, i.e not inside an event.
-- @param element_name<string> This name must be globally unique.
function Gui.allow_player_to_toggle_top_element_visibility(element_name)
@@ -189,7 +190,17 @@ function Gui.allow_player_to_toggle_top_element_visibility(element_name)
top_elements[#top_elements + 1] = element_name
end
--- Returns the flow where top elements can be added and will be effected by google visibility
-- For the toggle to work it must be registed with Gui.allow_player_to_toggle_top_element_visibility(element_name)
-- @tparam player LuaPlayer pointer to the player who has the gui
-- @treturn LuaGuiEelement the top element flow
function Gui.get_top_element_flow(player)
player = Game.get_player_from_any(player)
return mod_gui.get_button_flow(player)
end
local toggle_button_name = Gui.uid_name()
Gui.top_toggle_button_name = toggle_button_name
Event.add(
defines.events.on_player_created,
@@ -200,16 +211,18 @@ Event.add(
return
end
local b =
player.gui.top.add {
local top = Gui.get_top_element_flow(player)
local b = top.add {
type = 'button',
name = toggle_button_name,
style = mod_gui.button_style,
caption = '<',
tooltip = 'Shows / hides the Redmew Gui buttons.'
tooltip = {'gui_util.button_tooltip'}
}
local style = b.style
style.width = 18
style.height = 38
style.height = 36
style.left_padding = 0
style.top_padding = 0
style.right_padding = 0
@@ -223,21 +236,16 @@ Gui.on_click(
function(event)
local button = event.element
local player = event.player
local top = player.gui.top
local top = Gui.get_top_element_flow(player)
if button.caption == '<' then
for i = 1, #top_elements do
local name = top_elements[i]
local ele = top[name]
if ele and ele.valid then
local style = ele.style
-- if visible is not set it has the value of nil.
-- Hence nil is treated as is visible.
local v = style.visible
if v or v == nil then
if ele.visible then
custom_raise(on_pre_hidden_handlers, ele, player)
style.visible = false
ele.visible = false
end
end
end
@@ -249,17 +257,15 @@ Gui.on_click(
local name = top_elements[i]
local ele = top[name]
if ele and ele.valid then
local style = ele.style
if not style.visible then
style.visible = true
if not ele.visible then
ele.visible = true
custom_raise(on_visible_handlers, ele, player)
end
end
end
button.caption = '<'
button.style.height = 38
button.style.height = 36
end
end
)
@@ -284,4 +290,4 @@ if _DEBUG then
end
end
return Gui
return Gui