The base element to be used with the toolbar, others can be used but this is recomented
+
+
+
+ Properties / Events:
+
+
+
+
+
+
+
+
+
+ permission_alias
+
+ :
+
+ (string)
+
+ the alias used with Toolbar.allowed
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Usage:
+
-- Adding a basic button to the toolbar, note no need to call Toolbar.add_button_concept
+Gui.clone_concept('toolbar-button','new-button')
+:set_caption('Click Me')
+:on_click(function(event)
+ event.player.print('You Clicked Me!!')
+end)
Adds a frame concept to the toolbar frame area, this will not add a button to the toolbar
+
+
+
+ Parameters:
+
+
+
+
+
+
+
+
+
+ concept
+
+ :
+
+ (table)
+
+ the gui concept that you want to add to the toolbar frame area
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Usage:
+
-- Adding a basic frame to the frame area
+local new_frame =
+Gui.clone_concept('frame','new_frame')
+:set_title('Test')
+
+Toolbar.add_frame_concept(new_frame)
@@ -419,7 +420,7 @@ fraction will decide a chance to spawn. 1 alien for 2 spawner's will have 50% on
generated by LDoc
diff --git a/docs/modules/utils.core.html b/docs/modules/utils.core.html
index 4f6a5936..9dda491f 100644
--- a/docs/modules/utils.core.html
+++ b/docs/modules/utils.core.html
@@ -78,6 +78,7 @@
@@ -332,7 +333,7 @@
generated by LDoc
diff --git a/expcore/gui/concepts/button.lua b/expcore/gui/concepts/button.lua
index ce79e6a6..196cc117 100644
--- a/expcore/gui/concepts/button.lua
+++ b/expcore/gui/concepts/button.lua
@@ -50,21 +50,21 @@ end)
properties.type = 'sprite-button'
end)
:define_draw(function(properties,parent,element)
- if properties.type == 'button' then
+ if properties.type == 'sprite-button' then
element = parent.add{
name = properties.name,
- type = properties.type,
- caption = properties.caption,
+ type = 'sprite-button',
+ sprite = properties.sprite,
+ hovered_sprite = properties.hovered_sprite,
+ clicked_sprite = properties.clicked_sprite,
tooltip = properties.tooltip
}
else
element = parent.add{
name = properties.name,
- type = properties.type,
- sprite = properties.sprite,
- hovered_sprite = properties.hovered_sprite,
- clicked_sprite = properties.clicked_sprite,
+ type = 'button',
+ caption = properties.caption,
tooltip = properties.tooltip
}
diff --git a/expcore/gui/core.lua b/expcore/gui/core.lua
index 08bec3d2..e45101f9 100644
--- a/expcore/gui/core.lua
+++ b/expcore/gui/core.lua
@@ -188,6 +188,7 @@ function Gui.set_padding(element,up,down,left,right)
end
--[[ Used to check a property exists and if it is a function then call the function
+@function Gui.resolve_property
@tparam any value the value that you are testing exists and call if its a function
@tparam LuaGuiElement element the element that is passed to the function if it is a function
@treturn any the value or what it returns if it is a function
diff --git a/expcore/gui/prototype.lua b/expcore/gui/prototype.lua
index 5fb2629e..cac0d37e 100644
--- a/expcore/gui/prototype.lua
+++ b/expcore/gui/prototype.lua
@@ -75,6 +75,7 @@ local Game = require 'utils.game' -- @dep utils.game
local Factorio_Events = {}
local Prototype = {
draw_callbacks = {},
+ clone_callbacks = {},
properties = {},
factorio_events = {},
events = {}
@@ -159,9 +160,40 @@ function Prototype:clone(concept_name)
concept.set_store_from_instance = nil
end
+ -- Loop over all the clone defines, element is updated when a value is returned
+ for _,clone_callback in pairs(concept.clone_callbacks) do
+ local success, rtn = pcall(clone_callback,concept)
+ if not success then
+ error('Gui clone handler error with '..concept.name..':\n\t'..rtn)
+ end
+ end
+
return concept
end
+--[[-- Use to add your own callbacks to the clone function, for example adding to a local table
+@tparam function clone_callback the function which is called with the concept to have something done to it
+@treturn table self to allow chaining
+@usage-- Adding concept to a local table
+local buttons = {}
+local button =
+Gui.get_concept('Button')
+:define_clone(function(concept)
+ buttons[concept.name] = concept
+end)
+]]
+function Prototype:define_clone(clone_callback)
+ -- Check that it is a function that is being added
+ if type(clone_callback) ~= 'function' then
+ error('Draw define must be a function',2)
+ end
+
+ -- Add the draw function
+ self.clone_callbacks[#self.clone_callbacks+1] = clone_callback
+
+ return self
+end
+
--[[-- Used internally to save concept names to the core gui module
@function Prototype:change_name
@tparam[opt=self.name] string new_name the new name of the concept
@@ -280,7 +312,7 @@ function Prototype:raise_event(event_name,event,from_factorio)
for _,handler in ipairs(handlers) do
local success, err = pcall(handler,event)
if not success then
- error('Gui event handler error with '..self.name..'/'..event_name..': '..err)
+ error('Gui event handler error with '..self.name..'/'..event_name..':\n\t'..err)
end
end
end
@@ -335,7 +367,7 @@ Gui.get_concept('CustomButton')
-- Call the setter method to update values if present
local success, err = pcall(setter_callback,concept.properties,value,...)
if not success then
- error('Gui property handler error with '..concept.name..'/'..property_name..': '..err)
+ error('Gui property handler error with '..concept.name..'/'..property_name..':\n\t'..err)
end
else
-- Otherwise just update the key
@@ -413,7 +445,7 @@ function Prototype:draw(parent_element,...)
if success and rtn then
element = rtn
elseif not success then
- error('Gui draw handler error with '..self.name..': '..rtn)
+ error('Gui draw handler error with '..self.name..':\n\t'..rtn)
end
end
diff --git a/expcore/gui/test.lua b/expcore/gui/test.lua
index afee4f8f..776bfa59 100644
--- a/expcore/gui/test.lua
+++ b/expcore/gui/test.lua
@@ -8,7 +8,9 @@
-- @section tests
local Gui = require 'expcore.gui'
-local Game = require 'utils.game' -- @dep utils.game
+local Game = require 'utils.game'
+local Event = require 'utils.event'
+require 'expcore.toolbar'
local test_prefix = '__GUI_TEST_'
local tests = {}
@@ -32,6 +34,59 @@ Gui.clone_concept('frame',TEST 'test_frame')
end
end)
+Gui.clone_concept('toolbar-button',TEST 'run_test_button')
+:set_permission_alias('gui-test')
+:set_caption('Element Tests')
+:on_click(function(event)
+ local player = event.player
+ if not Gui.destroy(player.gui.center[test_frame.name]) then
+ Gui.run_tests(event.player)
+ end
+end)
+
+local test_left_frame =
+Gui.clone_concept('toolbar-frame',TEST 'player_list')
+:set_permission_alias('gui-test')
+:set_caption('Frame Test Left')
+:define_draw(function(properties,parent,element)
+ local list_area =
+ element.add{
+ name = 'scroll',
+ type = 'scroll-pane',
+ direction = 'vertical',
+ horizontal_scroll_policy = 'never',
+ vertical_scroll_policy = 'auto-and-reserve-space'
+ }
+ Gui.set_padding(list_area,1,1,2,2)
+ list_area.style.horizontally_stretchable = true
+ list_area.style.maximal_height = 200
+
+ for _,player in pairs(game.connected_players) do
+ list_area.add{
+ type='label',
+ caption=player.name
+ }
+ end
+end)
+:on_update(function(event)
+ local list_area = event.element.scroll
+ list_area.clear()
+
+ for _,player in pairs(game.connected_players) do
+ list_area.add{
+ type='label',
+ caption=player.name
+ }
+ end
+end)
+
+Event.add(defines.events.on_player_joined_game,function(event)
+ test_left_frame:update_all(event)
+end)
+Event.add(defines.events.on_player_left_game,function(event)
+ test_left_frame:update_all(event)
+end)
+
--[[-- Runs a set of gui tests to ensure that the system is working
@tparam LuaPlayer player the player that the guis are made for and who recives the results
@tparam[opt] string category when given only tests in this category are ran
diff --git a/expcore/toolbar.lua b/expcore/toolbar.lua
new file mode 100644
index 00000000..a601a7ab
--- /dev/null
+++ b/expcore/toolbar.lua
@@ -0,0 +1,469 @@
+--[[-- Core Module - Toolbar
+ @core Toolbar
+ @alias Toolbar
+]]
+
+local Gui = require 'expcore.gui' --- @dep expcore.gui
+local Roles = require 'expcore.roles' --- @dep expcore.roles
+local Event = require 'utils.event' --- @dep utils.event
+local Game = require 'utils.game' --- @dep utils.game
+local mod_gui = require 'mod-gui' --- @dep mod-gui
+
+local toolbar_toggle_concept
+local toolbar_hide_concept
+local toolbar_concept
+local Toolbar = {
+ button_concepts = {},
+ frame_concepts = {},
+ permissions = {}
+}
+
+Gui.Toolbar = Toolbar
+
+--- Permissions.
+-- Functions to do with deciding which player can do what
+-- @section permissions
+
+--[[-- Used to test if a player is allowed to use a button on the toolbar, if you are not using expcore.roles then change this function
+@tparam LuaPlayer player the player you want ot test is allowed to use this button
+@tparam string concept_name the name of the button concept that you want to see if the player is allowed to use
+@treturn boolean true if the player is allowed to use it
+@usage-- Test if a player can use 'test-player-list'
+local allowed = Toolbar.allowed(game.player,'test-player-list')
+]]
+function Toolbar.allowed(player,concept_name)
+ local permission = Toolbar.permissions[concept_name] or concept_name
+ return Roles.player_allowed(player,permission)
+end
+
+--[[-- Use to add an alias for the allowed test, alias is what is tested for rather than the concept name
+@tparam string concept_name the name of the concept that will point to this alias
+@tparam string alias the permission string that will be tested when this concept is used with Toolbar.allowed
+@usage-- Adding an alias for the 'test-player-list' concept
+Toolbar.set_permission_alias('test-player-list','gui/player-list')
+]]
+function Toolbar.set_permission_alias(concept_name,alias)
+ Toolbar.permissions[concept_name] = alias
+end
+
+--- Buttons.
+-- All function to do with the toolbar buttons
+-- @section buttons
+
+--[[-- Adds a concept to be drawn to the button area and allows it to be toggled with the toggle toolbar button
+@tparam table concept the gui concept that you want to add to the button area
+@usage-- Adding a basic button to the toolbar
+local new_button =
+Gui.clone_concept('button','new-button')
+:set_caption('Click Me')
+:on_click(function(event)
+ event.player.print('You Clicked Me!!')
+end)
+
+Toolbar.add_button_concept(new_button)
+]]
+function Toolbar.add_button_concept(concept)
+ local concepts = Toolbar.button_concepts
+ concepts[#concepts+1] = concept
+end
+
+--[[-- Updates all the buttons for a player, this means hide and show buttons based on permissions
+@tparam LuaPlayer player the player to update the toolbar buttons for
+@usage-- Updating your toolbar
+Toolbar.update_buttons(player)
+]]
+function Toolbar.update_buttons(player)
+ toolbar_concept:raise_event('on_button_update',{
+ player_index = player.index
+ })
+end
+
+--[[-- Returns an array of buttons names that the given player is able to see, returns none if toolbar hidden
+@tparam LuaPlayer player the player you want to get the visible buttons of
+@treturn table an array of names of the visible buttons
+@usage-- Get a list of all your visible buttons
+Toolbar.get_visible_buttons(game.player)
+]]
+function Toolbar.get_visible_buttons(player)
+ local rtn = {}
+ local top_flow = mod_gui.get_button_flow(player)
+
+ for _,concept in pairs(Toolbar.button_concepts) do
+ local element = top_flow[concept.name]
+ if element.visible then
+ rtn[#rtn+1] = element.name
+ end
+ end
+
+ return rtn
+end
+
+--[[-- The base element to be used with the toolbar, others can be used but this is recomented
+@element toolbar-button
+@tparam string permission_alias the alias used with Toolbar.allowed
+@usage-- Adding a basic button to the toolbar, note no need to call Toolbar.add_button_concept
+Gui.clone_concept('toolbar-button','new-button')
+:set_caption('Click Me')
+:on_click(function(event)
+ event.player.print('You Clicked Me!!')
+end)
+]]
+Toolbar.button =
+Gui.clone_concept('button','toolbar-button')
+:new_property('permission_alias',nil,function(properties,value)
+ Toolbar.set_permission_alias(properties.name,value)
+end)
+:define_clone(Toolbar.add_button_concept)
+:define_draw(function(properties,parent,element)
+ element.style = mod_gui.button_style
+end)
+
+--- Frames.
+-- Functions to do with the toolbar frames
+-- @section frames
+
+--[[-- Adds a frame concept to the toolbar frame area, this will not add a button to the toolbar
+@tparam table concept the gui concept that you want to add to the toolbar frame area
+@usage-- Adding a basic frame to the frame area
+local new_frame =
+Gui.clone_concept('frame','new_frame')
+:set_title('Test')
+
+Toolbar.add_frame_concept(new_frame)
+]]
+function Toolbar.add_frame_concept(concept)
+ local concepts = Toolbar.frame_concepts
+ concepts[#concepts+1] = concept
+end
+
+--[[-- Hides all the frames for a player
+@tparam LuaPlayer player the player to hide the frames for
+@usage-- Hiding all your frames
+Toolbar.hide_frames(game.player)
+]]
+function Toolbar.hide_frames(player)
+ toolbar_concept:raise_event('on_hide_frames',{
+ player_index = player.index
+ })
+end
+
+--[[-- Gets an array of the names of all the visible frames for a player
+@tparam LuaPlayer player the player that you want to get the visible frames of
+@treturn table an array of names of the visible frames for the given player
+@usage-- Get all your visible frames
+Toolbar.get_visible_frames(game.player)
+]]
+function Toolbar.get_visible_frames(player)
+ local rtn = {}
+ local left_flow = mod_gui.get_frame_flow(player)
+
+ for _,concept in pairs(Toolbar.frame_concepts) do
+ local element = left_flow[concept.name..'-frame']
+ if element.visible then
+ rtn[#rtn+1] = element.name
+ end
+ end
+
+ left_flow[toolbar_hide_concept.name].visible = #rtn > 0
+
+ return rtn
+end
+
+--[[-- The base toolbar frame, others can be used but this is recomented
+@element toolbar-frame
+@param on_update fired when the frame is to have its content updated
+@tparam boolean open_by_default weather the frame should be open when a player first joins
+@tparam boolean use_container true by default and will place a container inside the frame for content
+@tparam string direction the direction that the items in the frame are added
+@usage-- Adding a basic player list
+local player_list =
+Gui.clone_concept('toolbar-frame','player_list')
+:set_permission_alias('player_list')
+:set_caption('Player List')
+:toggle_with_click()
+:define_draw(function(properties,parent,element)
+ local list_area =
+ element.add{
+ name = 'scroll',
+ type = 'scroll-pane',
+ direction = 'vertical',
+ horizontal_scroll_policy = 'never',
+ vertical_scroll_policy = 'auto-and-reserve-space'
+ }
+ Gui.set_padding(list_area,1,1,2,2)
+ list_area.style.horizontally_stretchable = true
+ list_area.style.maximal_height = 200
+
+ for _,player in pairs(game.connected_players) do
+ list_area.add{
+ type='label',
+ caption=player.name
+ }
+ end
+end)
+:on_update(function(event)
+ local list_area = event.element.scroll
+ list_area.clear()
+
+ for _,player in pairs(game.connected_players) do
+ list_area.add{
+ type='label',
+ caption=player.name
+ }
+ end
+end)
+]]
+Toolbar.frame =
+Gui.clone_concept('toolbar-button','toolbar-frame')
+:new_property('open_by_default',false)
+:new_property('use_container',true)
+:new_property('direction','horizontal')
+:new_event('on_update')
+:define_clone(function(concept)
+ Toolbar.add_frame_concept(concept)
+ concept:on_click(function(event)
+ event.concept:toggle_visible_state(event.player)
+ end)
+end)
+:define_draw(function(properties,parent,element)
+ -- Add the base frame element, the button is already drawn to parent
+ local player = Gui.get_player_from_element(element)
+ local left_flow = mod_gui.get_frame_flow(player)
+ local frame = left_flow.add{
+ name = properties.name..'-frame',
+ type = 'frame',
+ direction = properties.direction
+ }
+
+ frame.style.padding = 2
+
+ -- Add and return the container if a container is used
+ if properties.use_container then
+ local container =
+ frame.add{
+ name = 'container',
+ type = 'frame',
+ direction = properties.direction,
+ style = 'window_content_frame_packed'
+ }
+ Gui.set_padding(container)
+
+ return container
+
+ end
+
+ return frame
+end)
+
+--[[-- Gets the content area of the frame concept for this player, each player only has one area
+@tparam LuaPlayer player the player that you want to get the frame content for
+@treturn LuaGuiElement the content area of this concept for this player
+@usage-- Get the content area of a concept
+local frame = player_list:get_content(game.player)
+]]
+function Toolbar.frame:get_content(player)
+ local left_flow = mod_gui.get_frame_flow(player)
+ local frame = left_flow[self.name..'-frame']
+ return frame.container or frame
+end
+
+--[[-- Toggles the visibilty of this concept for the given player
+@tparam LuaPlayer player the player that you want to toggle the frame for
+@treturn boolean the new state of the visibilty of this concept for the player
+@usage-- Toggle the frame for your self
+player_list:toggle_visible_state(game.player)
+]]
+function Toolbar.frame:toggle_visible_state(player)
+ local left_flow = mod_gui.get_frame_flow(player)
+ local frame = left_flow[self.name..'-frame']
+ if frame.visible then
+ frame.visible = false
+ Toolbar.get_visible_frames(player)
+ return false
+ else
+ frame.visible = true
+ Toolbar.get_visible_frames(player)
+ return true
+ end
+end
+
+--[[-- Gets the current visibilty state of this conept for this player
+@tparam LuaPlayer player the player that you want the visibilty state for
+@treturn boolean the current visiblity state of this concept to the player
+@usage-- Getting the current visiblity state
+player_list:get_visible_state(player)]]
+function Toolbar.frame:get_visible_state(player)
+ local left_flow = mod_gui.get_frame_flow(player)
+ return left_flow[self.name..'-frame'].visible
+end
+
+--[[-- Triggers an update of the content within the concept for this player, uses on_update handlers
+@tparam LuaPlayer player the player to update the concept content for
+@tparam[opt] table event the event data that you want to pass to the update handlers
+@usage-- Updating the frame for your player
+player_list:update(game.player)
+]]
+function Toolbar.frame:update(player,event)
+ event = event or {}
+ event.player_index = player.index
+ event.element = self:get_content(player)
+ self:raise_event('on_update',event)
+end
+
+--[[-- Triggers an update of the content with in this frame for all players
+@tparam[opt] table event the event data that you want to pass to the update handlers
+@usage-- Update the grame for all players
+player_list:update_all()
+]]
+function Toolbar.frame:update_all(event)
+ local players = event.update_offline == true and game.players or game.connected_players
+ for _,player in pairs(players) do
+ self:update(player)
+ end
+end
+
+--- Other Elements.
+-- All the other elements that are used to make this work
+-- @section elements
+
+--[[-- The main toolbar element, draws, updates, and controls the other concepts
+@element toolbar
+@param on_button_update fired when the buttons are updated for a player
+@param on_hide_frames fired when the frames are hidden for a player
+]]
+toolbar_concept =
+Gui.new_concept('toolbar')
+:define_draw(function(properties,player)
+ -- Get the main flows
+ local top_flow = mod_gui.get_button_flow(player)
+ if not top_flow then return end
+ local left_flow = mod_gui.get_frame_flow(player)
+ if not left_flow then return end
+
+ -- Draw toggle buttons first
+ toolbar_toggle_concept:draw(top_flow)
+ toolbar_hide_concept:draw(left_flow)
+
+ -- Draw all the buttons and frames
+ local done = {}
+ for _,concept in pairs(Toolbar.button_concepts) do
+ done[concept.name] = true
+ concept:draw(top_flow)
+ top_flow[concept.name].visible = Toolbar.allowed(player,concept.name)
+
+ local frame = left_flow[concept.name..'-frame']
+ if frame then
+ frame.visible = Gui.resolve_property(concept.properties.open_by_default,frame)
+ end
+ end
+
+ -- Draws frames that did not have buttons
+ for _,concept in pairs(Toolbar.frame_concepts) do
+ if not done[concept.name] then
+ concept:draw(left_flow)
+ local frame = left_flow[concept.name..'-frame']
+ if frame then
+ frame.visible = Gui.resolve_property(concept.properties.open_by_default,frame)
+ end
+ end
+ end
+
+ -- Toggle the clear toobar if needed
+ Toolbar.get_visible_frames(player)
+
+end)
+:new_event('on_button_update')
+:on_button_update(function(event)
+ -- Get the top flow
+ local player = event.player
+ local top_flow = mod_gui.get_button_flow(player)
+ if not top_flow then return end
+
+ -- Set the visiblity of the elements
+ local visible = top_flow[toolbar_toggle_concept.name].caption == '<'
+ for _,concept in pairs(Toolbar.button_concepts) do
+ local element = top_flow[concept.name]
+ if Gui.valid(element) then
+ element.visible = visible and Toolbar.allowed(player,concept.name)
+ end
+ end
+
+end)
+:new_event('on_hide_frames')
+:on_hide_frames(function(event)
+ -- Get the left flow
+ local player = event.player
+ local left_flow = mod_gui.get_frame_flow(player)
+ if not left_flow then return end
+
+ -- Set the visiblity of the elements
+ left_flow[toolbar_hide_concept.name].visible = false
+ for _,concept in pairs(Toolbar.frame_concepts) do
+ local element = left_flow[concept.name..'-frame']
+ if Gui.valid(element) then element.visible = false end
+ end
+end)
+
+--- Used so toggle and hide can have the same look as each other
+local function toolbar_button_draw(properties,parent,element)
+ element.style = mod_gui.button_style
+ local style = element.style
+ style.width = 18
+ style.height = 36
+ style.padding = 0
+ style.left_padding = 1
+ style.font = 'default-small-bold'
+end
+
+--[[-- Button which toggles the the visible state of all toolbar buttons, triggers on_button_update
+@element toolbar-toggle
+]]
+toolbar_toggle_concept =
+Gui.clone_concept('button','toolbar-toggle')
+:set_caption('<')
+:set_tooltip{'gui_util.button_tooltip'}
+:define_draw(toolbar_button_draw)
+:on_click(function(event)
+ local element = event.element
+ element.caption = element.caption == '<' and '>' or '<'
+ toolbar_concept:raise_event('on_button_update',{
+ player_index = event.player_index
+ })
+end)
+
+--[[-- Button which hides all visible toolbar frames, triggers on_hide_frames
+@element toolbar-clear
+]]
+toolbar_hide_concept =
+Gui.clone_concept('button','toolbar-clear')
+:set_caption('<')
+:set_tooltip{'expcore-gui.left-button-tooltip'}
+:define_draw(toolbar_button_draw)
+:on_click(function(event)
+ event.element.visible = false
+ toolbar_concept:raise_event('on_hide_frames',{
+ player_index = event.player_index
+ })
+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_concept:draw(player)
+end)
+
+--- When a player gets a new role they will have the toolbar updated
+Event.add(Roles.events.on_role_assigned,function(event)
+ toolbar_concept:raise_event('on_button_update',{
+ player_index = event.player_index
+ })
+end)
+
+--- When a player loses a role they will have the toolbar updated
+Event.add(Roles.events.on_role_unassigned,function(event)
+ toolbar_concept:raise_event('on_button_update',{
+ player_index = event.player_index
+ })
+end)
+
+return Toolbar
\ No newline at end of file
diff --git a/utils/event_core.lua b/utils/event_core.lua
index 364e3134..b34715e1 100644
--- a/utils/event_core.lua
+++ b/utils/event_core.lua
@@ -27,7 +27,7 @@ local function call_handlers(handlers, event)
local handler = handlers[i]
local success, error = pcall(handler, event)
if not success then
- log(error)
+ log('\n\t'..error)
end
end
end