Merge branch 'feature/gui-inputs' into dev

This commit is contained in:
Cooldude2606
2019-05-19 15:47:16 +01:00
29 changed files with 3392 additions and 62 deletions

View File

@@ -32,11 +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.store_test'
'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 (%s)',index,total_file_count,path))
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 |-----')

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

@@ -0,0 +1,122 @@
--- 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 './core'
local Button = {
config={},
_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

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 './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

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

@@ -0,0 +1,537 @@
--- 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 Global = require 'utils.global'
local Store = require 'expcore.store'
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
Gui.instances = {} -- Stores runtime data of all active instances of an element define
Global.register(Gui.instances,function(tbl)
Gui.instances = tbl
end)
--- 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
Gui.instances[self.name]={}
Store.register(self.store,function(value,category)
if self.events.on_store_update then
self.events.on_store_update(value,category)
end
local instances = Gui.get_instances(self,category)
if instances then
for k,element in pairs(instances) do
if element and element.valid then
callback(self,element,value)
else
instances[k] = nil
end
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
Gui.instances[self.name]={}
Store.register_synced(self.store,function(value,category)
if self.events.on_store_update then
self.events.on_store_update(value,category)
end
local instances = Gui.get_instances(self,category)
if instances then
for k,element in pairs(instances) do
if element and element.valid then
callback(self,element,value)
else
instances[k] = nil
end
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 self.store then
local category = self.categorize and self.categorize(element) or nil
local instances = Gui.get_instances(self,category)
if instances then
table.insert(instances,new_element)
end
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 checkbox, name not found.',internal and 3 or 2) or nil
end
return define
end
--- Gets all instances of the element define, mostly internal use and note invalid elements may be present in the return
-- @tparam name string the uid or debug name for the define to get the instances for
-- @tparam[opt] category string the category to get the instances for
-- @treturn table a table of LuaGuiElements that might be invalid which belong to this define
function Gui.get_instances(name,category)
local define = Gui.get_define(name,true)
if not Gui.instances[define.name] then return end
local instances = Gui.instances[define.name]
if define.categorize then
if not instances[category] then instances[category] = {} end
return instances[category]
end
return instances
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
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 './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 './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

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

@@ -0,0 +1,206 @@
--- 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: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 './core'
local Game = require 'utils.game'
--- Gets the active lables for a define
-- @tparam define table the define to get the labels for
-- @tparam element LuaGuiElement the element that will be used to get the category
-- @treturn table the table of active instances for the slider lables
local function get_labels(define,element)
local function cat(e)
return e.player_index
end
local name = define.name..'-label'
if not Gui.instances[name] then return end
local categorize = define.categorize or not define.store and cat
local category = categorize and categorize(element) or nil
local instances = Gui.get_instances({
name=name,
categorize=categorize
},category)
return instances
end
--- Gets and updates the label values
-- @tparam define table the define to get the labels for
-- @tparam element LuaGuiElement the element that will be used to get the category
local function update_lables(define,element)
local instances = get_labels(define,element)
local value = element.slider_value
if instances then
for k,instance in pairs(instances) do
if instance and instance.valid then
instance.caption = tostring(math.round(value,2))
else
instances[k]=nil
end
end
end
end
--- 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
update_lables(define,element)
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
--- 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))
}
if not Gui.instances[name] then Gui.instances[name] = {} end
local labels = get_labels(self,element)
table.insert(labels,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

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

@@ -0,0 +1,552 @@
--- 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 Game = require 'utils.game'
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
> Main test gui - Main test gui triggers all other tests
]]
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)
Gui.new_toolbar_button('gui-test-open')
:set_caption('Open Test Gui')
:set_post_authenticator(function(player,button_name)
return global.show_test_gui
end)
:on_click(function(player,_element)
if player.gui.center.TestGui then player.gui.center.TestGui.destroy() return end
local frame = player.gui.center.add{
type='frame',
caption='Gui Test',
name='TestGui'
}
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)
--[[
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
> 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_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,
['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
}

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 './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

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

@@ -0,0 +1,82 @@
--- Gui structure for the toolbar (top left)
--[[
>>>> 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 './buttons'
local Gui = require './core'
local Roles = require 'expcore.roles'
local Event = require 'utils.event'
local Game = require 'utils.game'
local Toolbar = {
buttons = {}
}
--- Adds a new button to the toolbar
-- @tparam[opt] name string the name of the button to be added
-- @treturn table the button define
function Toolbar.new_button(name)
name = name or #Toolbar.buttons+1
local button = Buttons.new_button('toolbar/'..name)
button:set_post_authenticator(Roles.player_allowed)
Toolbar.add_button(button)
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

150
expcore/gui.lua Normal file
View File

@@ -0,0 +1,150 @@
--- 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('./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 Button = require('./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 Toolbar = require('./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 Checkbox = require('./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('./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('./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: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('./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('./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
]]
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

@@ -74,11 +74,28 @@
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 'util.global'
local Event = require 'util.event'
local write_json,table_keys = ext_require('expcore.common','write_json','table_keys')
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={},
@@ -90,6 +107,19 @@ 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
@@ -109,6 +139,8 @@ function Store.register(location,callback,start_value)
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
@@ -167,8 +199,9 @@ 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] and not no_error then
if not Store.callbacks[location] then
return error('Location is not registered', 2)
end
@@ -181,6 +214,8 @@ function Store.set(location,value)
value=value
})
end
return true
end
--- Gets all non nil children at a location, children can be added and removed during runtime
@@ -190,7 +225,7 @@ end
function Store.get_children(location)
local store = Store.get(location)
if type(store) ~= 'table' and table ~= nil then
if type(store) ~= 'table' and store ~= nil then
return error('Location has a non table value', 2)
end
@@ -204,11 +239,11 @@ end
function Store.get_child(location,child)
local store = Store.get(location)
if type(store) ~= 'table' and table ~= nil then
if type(store) ~= 'table' and store ~= nil then
return error('Location has a non table value', 2)
end
return store[child]
return store and store[child]
end
--- Sets the value of the chlid to a location, children can be added and removed during runtime
@@ -217,10 +252,11 @@ end
-- @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 table ~= nil then
if type(store) ~= 'table' and store ~= nil then
return error('Location has a non table value', 2)
end
@@ -238,6 +274,8 @@ function Store.set_child(location,child,value)
value=value
})
end
return true
end
-- Event handler for the watcher callbacks
@@ -251,14 +289,18 @@ Event.add(defines.events.on_tick,function()
if not success then
table.insert(errors,store_new)
else
if store_old ~= store_new then
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
error(errors)
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

@@ -10,7 +10,8 @@ local interface_modules = {
['output']=Common.player_return,
['Group']='expcore.permission_groups',
['Roles']='expcore.roles',
['Store']='expcore.store'
['Store']='expcore.store',
['Gui']='expcore.gui'
}
-- loads all the modules given in the above table
@@ -95,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