Merge pull request #145 from explosivegaming/feature/gui-v5

Gui v5
This commit is contained in:
Cooldude2606
2020-03-21 13:07:12 +00:00
committed by GitHub
135 changed files with 5761 additions and 15918 deletions

View File

@@ -1,284 +1 @@
--[[-- Core Module - Gui
- 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 separate files in ./gui
- please read the files for more documentation that cant be shown here
- please note there is a rework planned but not started
@core Gui
@alias Gui
]]
local Gui = require 'expcore.gui.core' --- @dep expcore.gui.core
--[[
Core
Gui.new_define(prototype) --- Used internally to create new element defines from a class prototype
Gui.draw(name,element) --- Draws a copy of the element define to the parent element, see draw_to
Gui.categorize_by_player(element) --- A categorize function to be used with add_store, each player has their own value
Gui.categorize_by_force(element) --- A categorize function to be used with add_store, each force has its own value
Gui.categorize_by_surface(element) --- A categorize function to be used with add_store, each surface has its own value
Gui.toggle_enabled(element) --- Will toggle the enabled state of an element
Gui.toggle_visible(element) --- Will toggle the visibility of an element
Gui.set_padding(element,up,down,left,right) --- Sets the padding for a gui element
Gui.set_padding_style(style,up,down,left,right) --- Sets the padding for a gui style
Gui.create_alignment(element,flow_name) --- Allows the creation of a right align flow to place elements into
Gui.destroy_if_valid(element) --- Destroys an element but tests for it being present and valid first
Gui.create_scroll_table(element,table_size,maximal_height,name) --- Creates a scroll area with a table inside, table can be any size
Gui.create_header(element,caption,tooltip,right_align,name) --- Creates a header section with a label and button area
Prototype Constructor
Constructor.event(event_name) --- Creates a new function to add functions to an event handler
Constructor.extend(new_prototype) --- Extents a prototype with the base functions of all gui prototypes, no metatables
Constructor.store(sync,callback) --- Creates a new function which adds a store to a gui define
Constructor.setter(value_type,key,second_key) --- Creates a setter function that checks the type when a value is set
Base Prototype
Prototype:uid() --- Gets the uid for the element define
Prototype:debug_name(value) --- Sets a debug alias for the define
Prototype:set_caption(value) --- Sets the caption for the element define
Prototype:set_tooltip(value) --- Sets the tooltip for the element define
Prototype:set_style(style,callback) --- Sets the style for the element define
Prototype:set_embedded_flow(state) --- Sets the element to be drawn inside a nameless flow, can be given a name using a function
Prototype:set_pre_authenticator --- Sets an authenticator that blocks the draw function if check fails
Prototype:set_post_authenticator --- Sets an authenticator that disables the element if check fails
Prototype:raise_event(event_name,...) --- Raises a custom event for this define, any number of params can be given
Prototype:draw_to(element,...) --- The main function for defines, when called will draw an instance of this define to the given element
Prototype:get_store(category) --- Gets the value in this elements store, category needed if categorize function used
Prototype:set_store(category,value) --- Sets the value in this elements store, category needed if categorize function used
Prototype:clear_store(category) --- Sets the value in this elements store to nil, category needed if categorize function used
]]
local Instances = require 'expcore.gui.instances' --- @dep expcore.gui.instances
Gui.new_instance_group = Instances.registers
Gui.get_instances = Instances.get_elements
Gui.add_instance = Instances.get_elements
Gui.update_instances = Instances.apply_to_elements
Gui.classes.instances = Instances
--[[
Instances.has_categories(name) --- Returns if a instance group has a categorise function; must be registered
Instances.is_registered(name) --- Returns if the given name is a registered instance group
Instances.register(name,categorise) --- Registers the name of an instance group to allow for storing element instances
Instances.add_element(name,element) --- Adds an element to the instance group under the correct category; must be registered
Instances.get_elements_raw(name,category) --- Gets all element instances without first removing any invalid ones; used internally and must be registered
Instances.get_valid_elements(name,category,callback) --- Gets all valid element instances and has the option of running a callback on those that are valid
Instances.unregistered_add_element(name,category,element) --- A version of add_element that does not require the group to be registered
Instances.unregistered_get_elements(name,category,callback) --- A version of get_elements that does not require the group to be registered
]]
local Button = require 'expcore.gui.elements.buttons' --- @dep expcore.gui.elements.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 sprite button
Button._prototype:set_click_filter(filter,...) --- Adds a click / mouse button filter to the button
Button._prototype:set_key_filter(filter,...) --- Adds a control key filter to the button
]]
local Checkbox = require 'expcore.gui.elements.checkbox' --- @dep expcore.gui.elements.checkbox
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(callback,categorize) --- Registers a new option set that can be linked to radiobuttons (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 radiobuttons in a element to false (unless excluded) and can act recursively
]]
local Dropdown = require 'expcore.gui.elements.dropdown' --- @dep expcore.gui.elements.dropdown
Gui.new_dropdown = Dropdown.new_dropdown
Gui.new_list_box = Dropdown.new_list_box
Gui.classes.dropdown = Dropdown
--[[
Dropdown.new_dropdown(name) --- Creates a new dropdown element define
Dropdown.new_list_box(name) --- Creates a new list box element define
Dropdown._prototype:on_element_update(callback) --- Registers a handler for when an element instance updates
Dropdown._prototype:on_store_update(callback) --- Registers a handler for when the stored value updates
Dropdown._prototype:new_static_options(options,...) --- Adds new static options to the dropdown which will trigger the general callback
Dropdown._prototype:new_dynamic_options(callback) --- Adds a callback which should return a table of values to be added as options for the dropdown (appended after static options)
Dropdown._prototype:add_option_callback(option,callback) --- Adds a case specific callback which will only run when that option is selected (general case still triggered)
Dropdown.select_value(element,value) --- Selects the option from a dropdown or list box given the value rather than key
Dropdown.get_selected_value(element) --- Returns the currently selected value rather than index
]]
local Slider = require 'expcore.gui.elements.slider' --- @dep expcore.gui.elements.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 'expcore.gui.elements.text' --- @dep expcore.gui.elements.text
Gui.new_text_filed = Text.new_text_field
Gui.new_text_box = Text.new_text_box
Gui.classes.text = Text
--[[
Text.new_text_field(name) --- Creates a new text field element define
Text._prototype_field:on_element_update(callback) --- Registers a handler for when an element instance updates
Text._prototype_field:on_store_update(callback) --- Registers a handler for when the stored value updates
Text.new_text_box(name) --- Creates a new text box element define
Text._prototype_field:on_element_update(callback) --- Registers a handler for when an element instance updates
Text._prototype_field:on_store_update(callback) --- Registers a handler for when the stored value updates
Text._prototype_box:set_selectable(state) --- Sets the text box to be selectable
Text._prototype_box:set_word_wrap(state) --- Sets the text box to have word wrap
Text._prototype_box:set_read_only(state) --- Sets the text box to be read only
]]
local ElemButton = require 'expcore.gui.elements.elem-button' --- @dep expcore.gui.elements.elem-button
Gui.new_elem_button = ElemButton.new_elem_button
Gui.classes.elem_button = ElemButton
--[[
ElemButton.new_elem_button(name) --- Creates a new elem button element define
ElemButton._prototype:on_element_update(callback) --- Registers a handler for when an element instance updates
ElemButton._prototype:on_store_update(callback) --- Registers a handler for when the stored value updates
ElemButton._prototype:set_type(type) --- Sets the type of the elem button, the type is required so this must be called at least once
ElemButton._prototype:set_default(value) --- Sets the default value for the elem button, this may be a function or a string
]]
local ProgressBar = require 'expcore.gui.elements.progress-bar' --- @dep expcore.gui.elements.progress-bar
Gui.new_progressbar = ProgressBar.new_progressbar
Gui.set_progressbar_maximum = ProgressBar.set_maximum
Gui.increment_progressbar = ProgressBar.increment
Gui.decrement_progressbar = ProgressBar.decrement
Gui.classes.progressbar = ProgressBar
--[[
ProgressBar.set_maximum(element,amount,count_down) --- Sets the maximum value that represents the end value of the progress bar
ProgressBar.increment(element,amount) --- Increases the value of the progressbar, if a define is given all of its instances are incremented
ProgressBar.decrement(element,amount) --- Decreases the value of the progressbar, if a define is given all of its instances are decreased
ProgressBar.new_progressbar(name) --- Creates a new progressbar element define
ProgressBar._prototype:set_maximum(amount,count_down) --- Sets the maximum value that represents the end value of the progress bar
ProgressBar._prototype:use_count_down(state) --- Will set the progress bar to start at 1 and trigger when it hits 0
ProgressBar._prototype:increment(amount,category) --- Increases the value of the progressbar
ProgressBar._prototype:increment_filtered(amount,filter) --- Increases the value of the progressbar, if the filter condition is met, does not work with store
ProgressBar._prototype:decrement(amount,category) --- Decreases the value of the progressbar
ProgressBar._prototype:decrement_filtered(amount,filter) --- Decreases the value of the progressbar, if the filter condition is met, does not work with store
ProgressBar._prototype:add_element(element,maximum) --- Adds an element into the list of instances that will are waiting to complete, does not work with store
ProgressBar._prototype:reset_element(element) --- Resets an element, or its store, to be back at the start, either 1 or 0
ProgressBar._prototype:on_complete(callback) --- Triggers when a progress bar element completes (hits 0 or 1)
ProgressBar._prototype:on_complete(callback) --- Triggers when a store value completes (hits 0 or 1)
ProgressBar._prototype:event_counter(filter) --- Event handler factory that counts up by 1 every time the event triggers, can filter which elements are incremented
ProgressBar._prototype:event_countdown(filter) --- Event handler factory that counts down by 1 every time the event triggers, can filter which elements are decremented
]]
local Toolbar = require 'expcore.gui.concepts.toolbar' --- @dep expcore.gui.concepts.toolbar
Gui.new_toolbar_button = Toolbar.new_button
Gui.add_button_to_toolbar = Toolbar.add_button
Gui.update_toolbar = Toolbar.update
Gui.classes.toolbar = Toolbar
--[[
Toolbar.new_button(name) --- Adds a new button to the toolbar
Toolbar.add_button(button) --- Adds an existing buttton to the toolbar
Toolbar.update(player) --- Updates the player's toolbar with an new buttons or expected change in auth return
]]
local LeftFrames = require 'expcore.gui.concepts.left' --- @dep expcore.gui.concepts.left
Gui.get_left_frame_flow = LeftFrames.get_flow
Gui.toggle_left_frame = LeftFrames.toggle_frame
Gui.new_left_frame = LeftFrames.new_frame
Gui.classes.left_frames = LeftFrames
--[[
LeftFrames.get_flow(player) --- Gets the left frame flow for a player
LeftFrames.get_frame(name,player) --- Gets one frame from the left flow by its name
LeftFrames.get_open(player) --- Gets all open frames for a player, if non are open it will remove the close all button
LeftFrames.toggle_frame(name,player,state) --- Toggles the visibility of a left frame, or sets its visibility state
LeftFrames.new_frame(permission_name) --- Creates a new left frame define
LeftFrames._prototype:set_open_by_default(state) --- Sets if the frame is visible when a player joins, can also be a function to return a boolean
LeftFrames._prototype:set_direction(direction) --- Sets the direction of the frame, either vertical or horizontal
LeftFrames._prototype:get_frame(player) --- Gets the frame for this define from the left frame flow
LeftFrames._prototype:is_open(player) --- Returns if the player currently has this define visible
LeftFrames._prototype:toggle(player) --- Toggles the visibility of the left frame
LeftFrames._prototype:update(player) --- Updates the contents of the left frame, first tries update callback, other wise will clear and redraw
LeftFrames._prototype:update_all(update_offline) --- Updates the frame for all players, see update
LeftFrames._prototype:redraw(player) --- Redraws the frame by calling on_draw, will always clear the frame
LeftFrames._prototype:redraw_all(update_offline) --- Redraws the frame for all players, see redraw
LeftFrames._prototype:on_draw(player,frame) --- Use to draw your elements to the new frame
LeftFrames._prototype:on_update(player,frame) --- Use to edit your frame when there is no need to redraw it
LeftFrames._prototype:on_player_toggle(player,frame) --- Triggered when the player toggle the left frame
LeftFrames._prototype:event_handler(action) --- Creates an event handler that will trigger one of its functions, use with Event.add
]]
local CenterFrames = require 'expcore.gui.concepts.center' --- @dep expcore.gui.concepts.center
Gui.get_center_flow = CenterFrames.get_flow
Gui.toggle_center_frame = CenterFrames.toggle_frame
Gui.draw_center_frame = CenterFrames.draw_frame
Gui.redraw_center_frame = CenterFrames.redraw_frames
Gui.new_center_frame = CenterFrames.new_frame
Gui.classes.center_frames = CenterFrames
--[[
CenterFrames.get_flow(player) --- Gets the center flow for a player
CenterFrames.clear_flow(player) --- Clears the center flow for a player
CenterFrames.draw_frame(player,name) --- Draws the center frame for a player, if already open then will do nothing
CenterFrames.redraw_frame(player,name) --- Draws the center frame for a player, if already open then will destroy it and redraw
CenterFrames.toggle_frame(player,name,state) --- Toggles if the frame is currently open or not, will open if closed and close if open
CenterFrames.new_frame(permission_name) --- Sets the frame to be the current active gui when opened and closes all other frames
CenterFrames._prototype:on_draw(player,frame) --- Use to draw your elements onto the new frame
CenterFrames._prototype:set_auto_focus(state) --- Sets the frame to be the current active gui when opened and closes all other frames
CenterFrames._prototype:draw_frame(player) --- Draws this frame to the player, if already open does nothing (will call on_draw to draw to the frame)
CenterFrames._prototype:redraw_frame(player) --- Draws this frame to the player, if already open it will remove it and redraw it (will call on_draw to draw to the frame)
CenterFrames._prototype:toggle_frame(player) --- Toggles if the frame is open, if open it will close it and if closed it will open it
CenterFrames._prototype:event_handler(action) --- Creates an event handler that will trigger one of its functions, use with Event.add
]]
local PopupFrames = require 'expcore.gui.concepts.popups' --- @dep expcore.gui.concepts.popups
Gui.get_popup_flow = PopupFrames.get_flow
Gui.open_popup = PopupFrames.open
Gui.new_popup = PopupFrames.new_popup
Gui.classes.popup_frames = PopupFrames
--[[
PopupFrames.get_flow(player) --- Gets the left flow that contains the popup frames
PopupFrames.open(define_name,player,open_time,...) --- Opens a popup for the player, can give the amount of time it is open as well as params for the draw function
PopupFrames.close_progress --- Progress bar which when depleted will close the popup frame
PopupFrames.close_button --- A button which can be used to close the gui before the timer runs out
PopupFrames.new_popup(name) --- Creates a new popup frame define
PopupFrames._prototype:set_default_open_time(amount) --- Sets the default open time for the popup, will be used if non is provided with open
PopupFrames._prototype:open(player,open_time,...) --- Opens this define for a player, can be given open time and any other params for the draw function
]]
return Gui
return require 'expcore.gui.require'

View File

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

View File

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

View File

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

View File

@@ -1,114 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Prototype
]]
--- Toolbar.
-- Gui structure for the toolbar (top left)
-- @section toolbar
--[[
>>>> Example format
-- this is the same as any other button define, this just automatically draws it
-- you can use add_button if you already defined the button
local toolbar_button =
Toolbar.new_button('print-click')
:on_click(function(player,_element)
player.print('You clicked a button!')
end)
>>>> Functions
Toolbar.new_button(name) --- Adds a new button to the toolbar
Toolbar.add_button(button) --- Adds an existing buttton to the toolbar
Toolbar.update(player) --- Updates the player's toolbar with an new buttons or expected change in auth return
]]
local Gui = require 'expcore.gui.core' --- @dep expcore.gui.core
local Buttons = require 'expcore.gui.elements.buttons' --- @dep expcore.gui.elements.buttons
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 = {
permission_names = {},
buttons = {}
}
function Toolbar.allowed(player,define_name)
local permission_name = Toolbar.permission_names[define_name] or define_name
return Roles.player_allowed(player,permission_name)
end
function Toolbar.permission_alias(define_name,permission_name)
Toolbar.permission_names[define_name] = permission_name
end
--- Adds a new button to the toolbar
-- @tparam[opt] string name when given allows an alias to the button for the permission system
-- @treturn table the button define
function Toolbar.new_button(name)
local button =
Buttons.new_button()
:set_post_authenticator(Toolbar.allowed)
:set_style(mod_gui.button_style,function(style)
Gui.set_padding_style(style,-2,-2,-2,-2)
end)
Toolbar.add_button(button)
Toolbar.permission_alias(button.name,name)
return button
end
--- Adds an existing buttton to the toolbar
-- @tparam table button 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 LuaPlayer player 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.events.on_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.events.on_role_unassigned,function(event)
local player = Game.get_player_by_index(event.player_index)
Toolbar.update(player)
end)
return Toolbar

View File

@@ -1,368 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Prototype
]]
--- Core.
-- 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
-- @section core
--[[
>>>> 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.
>>>> Basic prototype functions (see expcore.gui.prototype)
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 (see expcore.gui.prototype and expcore.gui.instances)
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.categorize_by_player)
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.categorize_by_player 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.categorize_by_player)
:on_element_update(function(player,element,value)
player.print('Example checkbox is now: '..tostring(value))
end)
>>>> Functions
Gui.new_define(prototype) --- Used internally to create new element defines from a class prototype
Gui.draw(name,element) --- Draws a copy of the element define to the parent element, see draw_to
Gui.categorize_by_player(element) --- A categorize function to be used with add_store, each player has their own value
Gui.categorize_by_force(element) --- A categorize function to be used with add_store, each force has its own value
Gui.categorize_by_surface(element) --- A categorize function to be used with add_store, each surface has its own value
Gui.toggle_enabled(element) --- Will toggle the enabled state of an element
Gui.toggle_visible(element) --- Will toggle the visiblity of an element
Gui.set_padding(element,up,down,left,right) --- Sets the padding for a gui element
Gui.set_padding_style(style,up,down,left,right) --- Sets the padding for a gui style
Gui.create_alignment(element,flow_name) --- Allows the creation of a right align flow to place elements into
Gui.destroy_if_valid(element) --- Destroies an element but tests for it being present and valid first
Gui.create_scroll_table(element,table_size,maximal_height,name) --- Creates a scroll area with a table inside, table can be any size
Gui.create_header(element,caption,tooltip,right_align,name) --- Creates a header section with a label and button area
]]
local Gui = require 'utils.gui' --- @dep utils.gui
local Game = require 'utils.game' --- @dep utils.game
Gui.classes = {} -- Stores the class definations used to create element defines
Gui.defines = {} -- Stores the indivdual element definations
Gui.names = {} -- Stores debug names to link to gui uids
--- Used to create new element defines from a class prototype, please use the own given by the class
-- @tparam table prototype the class prototype that will be used for the element define
-- @tparam[opt] string debug_name the name that you want to see while debuging
-- @treturn table the new element define with all functions accessed via __index metamethod
function Gui.new_define(prototype,debug_name)
local name = Gui.uid_name()
local define = setmetatable({
debug_name = debug_name,
name = name,
events = {},
draw_data = {
name = name
}
},{
__index = prototype,
__call = function(self,...)
return self:draw_to(...)
end
})
Gui.defines[define.name] = define
return define
end
--- Gets an element define give the uid, debug name or a copy of the element define
-- @tparam ?string|table name the uid, debug name or define for the element define to get
-- @tparam[opt] boolean internal when true the error trace is one level higher (used internally)
-- @treturn table the element define that was found or an error
function Gui.get_define(name,internal)
if type(name) == 'table' then
if name.name and Gui.defines[name.name] then
return Gui.defines[name.name]
end
end
local define = Gui.defines[name]
if not define and Gui.names[name] then
return Gui.defines[Gui.names[name]]
elseif not define then
return error('Invalid name for element define, name not found.',internal and 3 or 2) or nil
end
return define
end
--- A categorize function to be used with add_store, each player has their own value
-- @tparam LuaGuiElement element the element that will be converted to a string
-- @treturn string the player's name who owns this element
function Gui.categorize_by_player(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 LuaGuiElement element the element that will be converted to a string
-- @treturn string the player's force name who owns this element
function Gui.categorize_by_force(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 LuaGuiElement element the element that will be converted to a string
-- @treturn string the player's surface name who owns this element
function Gui.categorize_by_surface(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 ?string|table name the uid, debug name or define for the element define to draw
-- @tparam LuaGuiEelement element 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 LuaGuiElement element the gui element to toggle
-- @treturn boolean the new state that the element has
function Gui.toggle_enabled(element)
if not element or not element.valid then return end
if not element.enabled then
element.enabled = true
else
element.enabled = false
end
return element.enabled
end
--- Will toggle the visiblity of an element
-- @tparam LuaGuiElement element the gui element to toggle
-- @treturn boolean the new state that the element has
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
return element.visible
end
--- Sets the padding for a gui element
-- @tparam LuaGuiElement element the element to set the padding for
-- @tparam[opt=0] number up the amount of padding on the top
-- @tparam[opt=0] number down the amount of padding on the bottom
-- @tparam[opt=0] number left the amount of padding on the left
-- @tparam[opt=0] number right the amount of padding on the right
function Gui.set_padding(element,up,down,left,right)
local style = element.style
style.top_padding = up or 0
style.bottom_padding = down or 0
style.left_padding = left or 0
style.right_padding = right or 0
end
--- Sets the padding for a gui style
-- @tparam LuaStyle style the element to set the padding for
-- @tparam[opt=0] number up the amount of padding on the top
-- @tparam[opt=0] number down the amount of padding on the bottom
-- @tparam[opt=0] number left the amount of padding on the left
-- @tparam[opt=0] number right the amount of padding on the right
function Gui.set_padding_style(style,up,down,left,right)
style.top_padding = up or 0
style.bottom_padding = down or 0
style.left_padding = left or 0
style.right_padding = right or 0
end
--- Allows the creation of an alignment flow to place elements into
-- @tparam LuaGuiElement element the element to add this alignment into
-- @tparam[opt] string name the name to use for the alignment
-- @tparam[opt='right'] string horizontal_align the horizontal alignment of the elements in this flow
-- @tparam[opt='center'] string vertical_align the vertical alignment of the elements in this flow
-- @treturn LuaGuiElement the new flow that was created
function Gui.create_alignment(element,name,horizontal_align,vertical_align)
local flow = element.add{name=name,type='flow'}
local style = flow.style
Gui.set_padding(flow,1,1,2,2)
style.horizontal_align = horizontal_align or 'right'
style.vertical_align = vertical_align or 'center'
style.horizontally_stretchable =style.horizontal_align ~= 'center'
style.vertically_stretchable = style.vertical_align ~= 'center'
return flow
end
--- Destroies an element but tests for it being present and valid first
-- @tparam LuaGuiElement element the element to be destroied
-- @treturn boolean true if it was destoried
function Gui.destroy_if_valid(element)
if element and element.valid then
element.destroy()
return true
end
end
--- Creates a scroll area with a table inside, table can be any size
-- @tparam LuaGuiElement element the element to add this scroll into
-- @tparam number table_size the number of columns in the table
-- @tparam number maximal_height the max hieght of the scroll
-- @tparam[opt='scroll'] string name the name of the scoll element
-- @treturn LuaGuiElement the table that was made
function Gui.create_scroll_table(element,table_size,maximal_height,name)
local list_scroll =
element.add{
name=name or 'scroll',
type='scroll-pane',
direction='vertical',
horizontal_scroll_policy='never',
vertical_scroll_policy='auto-and-reserve-space'
}
Gui.set_padding(list_scroll,1,1,2,2)
list_scroll.style.horizontally_stretchable = true
list_scroll.style.maximal_height = maximal_height
local list_table =
list_scroll.add{
name='table',
type='table',
column_count=table_size
}
Gui.set_padding(list_table)
list_table.style.horizontally_stretchable = true
list_table.style.vertical_align = 'center'
list_table.style.cell_padding = 0
return list_table
end
--- Creates a header section with a label and button area
-- @tparam LuaGuiElement element the element to add this header into
-- @tparam localeString caption the caption that is used as the title
-- @tparam[opt] localeString tooltip the tooltip that is shown on the caption
-- @tparam[opt] boolean right_align when true will include the right align area
-- @tparam[opt='header'] string name the name of the header area
-- @treturn LuaGuiElement the header that was made, or the align area if that was created
function Gui.create_header(element,caption,tooltip,right_align,name)
local header =
element.add{
name=name or 'header',
type='frame',
style='subheader_frame'
}
Gui.set_padding(header,2,2,4,4)
header.style.horizontally_stretchable = true
header.style.use_header_filler = false
header.add{
type='label',
style='heading_1_label',
caption=caption,
tooltip=tooltip
}
return right_align and Gui.create_alignment(header,'header-align') or header
end
return Gui

View File

@@ -0,0 +1,87 @@
--[[-- Core Module - Gui
- Gui defines that are used internally by the gui system
@module Gui
]]
local Gui = require 'expcore.gui.prototype'
local Event = require 'utils.event' --- @dep utils.event
--- Core Defines.
-- @section coreDefines
--- Button which toggles the top flow elements, version which shows inside the top flow when top flow is visible
-- @element hide_top_flow
local hide_top_flow =
Gui.element{
type = 'sprite-button',
sprite = 'utility/preset',
style = 'tool_button',
tooltip = {'gui_util.button_tooltip'}
}
:style{
padding = -2,
width = 18,
height = 36
}
:on_click(function(player,_,_)
Gui.toggle_top_flow(player)
end)
Gui.core_defines.hide_top_flow = hide_top_flow
--- Button which toggles the top flow elements, version which shows inside the left flow when top flow is hidden
-- @element show_top_flow
local show_top_flow =
Gui.element{
type = 'sprite-button',
sprite = 'utility/preset',
style = 'tool_button',
tooltip = {'gui_util.button_tooltip'}
}
:style{
padding = -2,
width = 18,
height = 20
}
:on_click(function(player,_,_)
Gui.toggle_top_flow(player)
end)
Gui.core_defines.show_top_flow = show_top_flow
--- Button which hides the elements in the left flow, shows inside the left flow when frames are visible
-- @element hide_left_flow
local hide_left_flow =
Gui.element{
type = 'sprite-button',
sprite = 'utility/close_black',
style = 'tool_button',
tooltip = {'expcore-gui.left-button-tooltip'}
}
:style{
padding = -3,
width = 18,
height = 20
}
:on_click(function(player,_,_)
Gui.hide_left_flow(player)
end)
Gui.core_defines.hide_left_flow = hide_left_flow
--- Draw the core elements when a player joins the game
Event.add(defines.events.on_player_created,function(event)
local player = game.players[event.player_index]
-- Draw the top flow
local top_flow = Gui.get_top_flow(player)
hide_top_flow(top_flow)
Gui.update_top_flow(player)
-- Draw the left flow
local left_flow = Gui.get_left_flow(player)
local button_flow = left_flow.add{ type = 'flow', name = 'gui_core_buttons', direction = 'vertical' }
local show_top = show_top_flow(button_flow)
local hide_left = hide_left_flow(button_flow)
show_top.visible = false
hide_left.visible = false
Gui.draw_left_flow(player)
end)

218
expcore/gui/defines.lua Normal file
View File

@@ -0,0 +1,218 @@
--[[-- Core Module - Gui
- Common defines that are used by other modules, non of these are used internally
@module Gui
]]
local Gui = require 'expcore.gui.prototype'
--- Defines.
-- @section defines
--[[-- Draw a flow used to align its child elements, default is right align
@element Gui.alignment
@tparam LuaGuiElement parent the parent element to which the alignment will be added
@tparam[opt='alignment'] string name the name of the alignment flow which is added
@tparam[opt='right'] string horizontal_align the horizontal alignment of the elements in the flow
@tparam[opt='center'] string vertical_align the vertical alignment of the elements in the flow
@treturn LuaGuiElement the alignment flow that was created
@usage-- Adding a right align flow
local alignment = Gui.alignment(element,'example_right_alignment')
@usage-- Adding a horizontal center and top align flow
local alignment = Gui.alignment(element,'example_center_top_alignment','center','top')
]]
Gui.alignment =
Gui.element(function(_,parent,name,_,_)
return parent.add{
name = name or 'alignment',
type = 'flow',
}
end)
:style(function(style,_,_,horizontal_align,vertical_align)
style.padding = {1,2}
style.vertical_align = vertical_align or 'center'
style.horizontal_align = horizontal_align or 'right'
style.vertically_stretchable = style.vertical_align ~= 'center'
style.horizontally_stretchable = style.horizontal_align ~= 'center'
end)
--[[-- Draw a scroll pane that has a table inside of it
@element Gui.scroll_table
@tparam LuaGuiElement parent the parent element to which the scroll table will be added
@tparam number height the maximum height for the scroll pane
@tparam number column_count the number of columns that the table will have
@tparam[opt='scroll'] string name the name of the scroll pane that is added, the table is always called "table"
@treturn LuaGuiElement the table that was created
@usage-- Adding a scroll table with max height of 200 and column count of 3
local scroll_table = Gui.scroll_table(element,200,3)
]]
Gui.scroll_table =
Gui.element(function(_,parent,height,column_count,name)
-- Draw the scroll
local scroll_pane =
parent.add{
name = name or 'scroll',
type = 'scroll-pane',
direction = 'vertical',
horizontal_scroll_policy = 'never',
vertical_scroll_policy = 'auto',
style = 'scroll_pane_under_subheader'
}
-- Set the style of the scroll pane
local scroll_style = scroll_pane.style
scroll_style.padding = {1,3}
scroll_style.maximal_height = height
scroll_style.horizontally_stretchable = true
-- Draw the table
local scroll_table =
scroll_pane.add{
type = 'table',
name = 'table',
column_count = column_count
}
-- Return the scroll table
return scroll_table
end)
:style{
padding = 0,
cell_padding = 0,
vertical_align = 'center',
horizontally_stretchable = true
}
--[[-- Used to add a frame with the header style, has the option for a right alignment flow for buttons
@element Gui.header
@tparam LuaGuiElement parent the parent element to which the header will be added
@tparam ?string|Concepts.LocalizedString caption the caption that will be shown on the header
@tparam[opt] ?string|Concepts.LocalizedString tooltip the tooltip that will be shown on the header
@tparam[opt=false] boolean add_alignment when true an alignment flow will be added to the header
@tparam[opt='header'] string name the name of the header that is being added, the alignment is always called "alignment"
@treturn LuaGuiElement either the header or the header alignment if add_alignment is true
@usage-- Adding a custom header with a label
local header = Gui.header(
element,
'Example Caption',
'Example Tooltip'
)
]]
Gui.header =
Gui.element(function(_,parent,caption,tooltip,add_alignment,name)
-- Draw the header
local header =
parent.add{
name = name or 'header',
type = 'frame',
style = 'subheader_frame'
}
-- Change the style of the header
local style = header.style
style.padding = {2,4}
style.use_header_filler = false
style.horizontally_stretchable = true
-- Draw the caption label
if caption then
header.add{
name = 'header_label',
type = 'label',
style = 'heading_1_label',
caption = caption,
tooltip = tooltip
}
end
-- Return either the header or the added alignment
return add_alignment and Gui.alignment(header) or header
end)
--[[-- Used to add a frame with the footer style, has the option for a right alignment flow for buttons
@element Gui.footer
@tparam LuaGuiElement parent the parent element to which the footer will be added
@tparam ?string|Concepts.LocalizedString caption the caption that will be shown on the footer
@tparam[opt] ?string|Concepts.LocalizedString tooltip the tooltip that will be shown on the footer
@tparam[opt=false] boolean add_alignment when true an alignment flow will be added to the footer
@tparam[opt='footer'] string name the name of the footer that is being added, the alignment is always called "alignment"
@treturn LuaGuiElement either the footer or the footer alignment if add_alignment is true
@usage-- Adding a custom footer with a label
local footer = Gui.footer(
element,
'Example Caption',
'Example Tooltip'
)
]]
Gui.footer =
Gui.element(function(_,parent,caption,tooltip,add_alignment,name)
-- Draw the header
local footer =
parent.add{
name = name or 'footer',
type = 'frame',
style = 'subfooter_frame'
}
-- Change the style of the footer
local style = footer.style
style.padding = {2,4}
style.use_header_filler = false
style.horizontally_stretchable = true
-- Draw the caption label
if caption then
footer.add{
name = 'footer_label',
type = 'label',
style = 'heading_1_label',
caption = caption,
tooltip = tooltip
}
end
-- Return either the footer or the added alignment
return add_alignment and Gui.alignment(footer) or footer
end)
--[[-- Used for left frames to give them a nice boarder
@element Gui.container
@tparam LuaGuiElement parent the parent element to which the container will be added
@tparam string name the name that you want to give to the outer frame, often just event_trigger
@tparam number width the minimal width that the frame will have
@usage-- Adding a container as a base
local container = Gui.container(parent,'my_container',200)
]]
Gui.container =
Gui.element(function(_,parent,name,_)
-- Draw the external container
local frame =
parent.add{
name = name,
type = 'frame'
}
-- Return the container
return frame.add{
name = 'container',
type = 'frame',
direction = 'vertical',
style = 'window_content_frame_packed'
}
end)
:style(function(style,element,_,width)
style.vertically_stretchable = false
local frame_style = element.parent.style
frame_style.padding = 2
frame_style.minimal_width = width
end)

View File

@@ -1,128 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Prototype
]]
--- Buttons.
-- Gui class define for buttons and sprite buttons
-- @section 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 sprite 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' --- @dep mod-gui
local Gui = require 'expcore.gui.core' --- @dep expcore.gui.core
local Prototype = require 'expcore.gui.prototype' --- @dep expcore.gui.prototype
local Button = {
_prototype=Prototype.extend{
on_raw_click = Prototype.event,
on_click = Prototype.event,
on_left_click = Prototype.event,
on_right_click = Prototype.event,
}
}
--- Creates a new button element define
-- @tparam[opt] string name the optional debug name that can be added
-- @treturn table the new button element define
function Button.new_button(name)
local self = Gui.new_define(Button._prototype,name)
self.draw_data.type = 'button'
self.draw_data.style = mod_gui.button_style
Gui.on_click(self.name,function(event)
local mouse_button = event.button
local keys = {alt=event.alt,control=event.control,shift=event.shift}
local player,element = event.player,event.element
event.keys = keys
self:raise_event('on_raw_click',event)
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 then
self:raise_event('on_left_click',player,element)
elseif mouse_button == defines.mouse_button_type.right and self.events.on_right_click then
self:raise_event('on_right_click',player,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
self:raise_event('on_click',player,element)
end)
return self
end
--- Adds sprites to a button making it a sprite button
-- @tparam SpritePath sprite the sprite path for the default sprite for the button
-- @tparam[opt] SpritePath hovered_sprite the sprite path for the sprite when the player hovers over the button
-- @tparam[opt] SpritePath clicked_sprite 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 table filter ?string|table either a of mouse buttons or the first mouse button to filter, with a table true means allowed
-- @tparam[opt] table ... when filter is not a 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 table filter ?string|table either a of control keys or the first control keys to filter, with a table true means allowed
-- @tparam[opt] table ... when filter is not a you can add the control keys 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

View File

@@ -1,247 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Prototype
]]
--- Checkboxs.
-- Gui class define for checkbox and radiobuttons
-- @section checkboxs
--[[
>>>> 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.categorize_by_player)
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 individual 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 radiobutton (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 radiobutton in a element to false (unless excluded) and can act recursively
Other functions present from expcore.gui.core
]]
local Gui = require 'expcore.gui.core' --- @dep expcore.gui.core
local Prototype = require 'expcore.gui.prototype' --- @dep expcore.gui.prototype
local Store = require 'expcore.store' --- @dep expcore.store
local Game = require 'utils.game' --- @dep utils.game
--- Store call for store update
-- @tparam table define the define that this is acting on
-- @tparam LuaGuiElement element the element that triggered the event
-- @tparam boolean value the new state of the checkbox
local function store_update(define,element,value)
element.state = value
local player = Game.get_player_by_index(element.player_index)
define:raise_event('on_element_update',player,element,value)
end
local Checkbox = {
option_sets={},
option_categorize={},
_prototype_checkbox=Prototype.extend{
on_element_update = Prototype.event,
on_store_update = Prototype.event,
add_store = Prototype.store(store_update)
},
_prototype_radiobutton=Prototype.extend{
on_element_update = Prototype.event,
on_store_update = Prototype.event,
add_store = Prototype.store(store_update)
}
}
--- Creates a new checkbox element define
-- @tparam[opt] string name the optional debug name that can be added
-- @treturn table the new checkbox element define
function Checkbox.new_checkbox(name)
local self = Gui.new_define(Checkbox._prototype_checkbox,name)
self.draw_data.type = 'checkbox'
self.draw_data.state = false
self:on_draw(function(player,element)
if self.store then
local state = self:get_store(element,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]
self:set_store(element,value)
elseif self.store then
local value = element.state
self:set_store(element,value)
else
self:raise_event('on_element_update',event.player,element,element.state)
end
end)
return self
end
--- Creates a new radiobutton element define, has all functions checkbox has
-- @tparam[opt] string name 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 string option_set the name of the option set to add this element to
-- @tparam string option_name the name of this option that will be used to identify it
-- @treturn 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 string category[opt] the category to get such as player name or force name
-- @tparam boolean internal used to prevent stackover flow
-- @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
return Store.get(location,category)
end
--- Sets the stored value of the radiobutton or the option set if present
-- @tparam string category[opt] the category to get such as player name or force name
-- @tparam boolean value the value to set for this define, must be valid for its type ie for checkbox etc
-- @tparam boolean internal used to prevent stackover flow
-- @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
return Store.set(location,category,value)
end
--- Registers a new option set that can be linked to radiobuttons (only one can be true at a time)
-- @tparam function callback the update callback when the value of the option set changes
-- callback param - value string - the new selected option for this option set
-- callback param - category string - the category that updated if categorize was used
-- @tparam function categorize 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(callback,categorize)
local name = Store.register(categorize)
Store.watch(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 string name the name of the option set to draw the radiobuttons of
-- @tparam LuaGuiElement element 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 radiobutton in a element to false (unless excluded) and can act recursively
-- @tparam LuaGuiElement element the root gui element to start setting radio buttons from
-- @tparam[opt] table exclude ?string|table the name of the radiobutton to exclude or a of radiobuttons where true will set the state true
-- @tparam[opt=false] ?number|boolean recursive if true will recur as much as possible, if a 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

View File

@@ -1,184 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Prototype
]]
--- Dropdowns.
-- Gui class define for dropdowns and list box
-- @section dropdowns
--[[
>>>> Functions
Dropdown.new_dropdown(name) --- Creates a new dropdown element define
Dropdown.new_list_box(name) --- Creates a new list box element define
Dropdown._prototype:on_element_update(callback) --- Registers a handler for when an element instance updates
Dropdown._prototype:on_store_update(callback) --- Registers a handler for when the stored value updates
Dropdown._prototype:new_static_options(options,...) --- Adds new static options to the dropdown which will trigger the general callback
Dropdown._prototype:new_dynamic_options(callback) --- Adds a callback which should return a table of values to be added as options for the dropdown (appended after static options)
Dropdown._prototype:add_option_callback(option,callback) --- Adds a case specific callback which will only run when that option is selected (general case still triggered)
Dropdown.select_value(element,value) --- Selects the option from a dropdown or list box given the value rather than key
Dropdown.get_selected_value(element) --- Returns the currently selected value rather than index
Other functions present from expcore.gui.core
]]
local Gui = require 'expcore.gui.core' --- @dep expcore.gui.core
local Prototype = require 'expcore.gui.prototype' --- @dep expcore.gui.prototype
local Game = require 'utils.game' --- @dep utils.game
local select_value
--- Store call for store update
-- @tparam table define the define that this is acting on
-- @tparam LuaGuiElement element the element that triggered the event
-- @tparam string value the new option for the dropdown
local function store_update(define,element,value)
select_value(element,value)
local player = Game.get_player_by_index(element.player_index)
define:raise_event('on_element_update',player,element,value)
if define.option_callbacks and define.option_callbacks[value] then
define.option_callbacks[value](player,element,value)
end
end
local Dropdown = {
_prototype=Prototype.extend{
on_element_update = Prototype.event,
on_store_update = Prototype.event,
add_store = Prototype.store(store_update)
}
}
--- Creates a new dropdown element define
-- @tparam[opt] string name the optional debug name that can be added
-- @treturn table the new dropdown element define
function Dropdown.new_dropdown(name)
local self = Gui.new_define(Dropdown._prototype,name)
self.draw_data.type = 'drop-down'
self:on_draw(function(player,element)
if self.dynamic_options then
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 value = self:get_store(element)
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
self:set_store(element,value)
else
local player = event.player
local option_callbacks = self.option_callbacks
self:raise_event('on_element_update',player,element,value)
if option_callbacks and option_callbacks[value] then
option_callbacks[value](player,element,value)
end
end
end)
return self
end
--- Creates a new list box element define
-- @tparam[opt] string name 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 table options ?string|table either a of option strings or the first option string, with a table values are the options
-- @tparam[opt] table ... when options is not a 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 function callback 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
-- @treturn 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 string option the name of the option to trigger the callback on; if not already added then will be added as an option
-- @tparam function callback 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
-- @treturn 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 LuaGuiElement element the element that contains the option
-- @tparam string value 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 LuaGuiElement element 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

View File

@@ -1,96 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Prototype
]]
--- Elem Buttons.
-- Gui class defines for elem buttons
-- @section elem-buttons
--[[
>>>> Functions
ElemButton.new_elem_button(name) --- Creates a new elem button element define
ElemButton._prototype:on_element_update(callback) --- Registers a handler for when an element instance updates
ElemButton._prototype:on_store_update(callback) --- Registers a handler for when the stored value updates
ElemButton._prototype:set_type(type) --- Sets the type of the elem button, the type is required so this must be called at least once
ElemButton._prototype:set_default(value) --- Sets the default value for the elem button, this may be a function or a string
Other functions present from expcore.gui.core
]]
local Gui = require 'expcore.gui.core' --- @dep expcore.gui.core
local Prototype = require 'expcore.gui.prototype' --- @dep expcore.gui.prototype
local Game = require 'utils.game' --- @dep utils.game
--- Store call for store update
-- @tparam table define the define that this is acting on
-- @tparam LuaGuiElement element the element that triggered the event
-- @tparam string value the new value for the elem button
local function store_update(define,element,value)
element.elem_value = value
local player = Game.get_player_by_index(element.player_index)
define:raise_event('on_element_update',player,element,value)
end
local ElemButton = {
_prototype=Prototype.extend{
on_element_update = Prototype.event,
on_store_update = Prototype.event,
add_store = Prototype.store(store_update)
}
}
--- Creates a new elem button element define
-- @tparam[opt] string name 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.new_define(ElemButton._prototype,name)
self.draw_data.type = 'choose-elem-button'
self:on_draw(function(player,element)
if type(self.default) == 'function' then
element.elem_value = self.default(player,element)
end
if self.store then
local value = self:get_store(element)
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
self:set_store(element,value)
else
self:raise_event('on_element_update',event.player,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 string type the type that this elem button is see factorio api
-- @treturn the element define to allow for chaining
ElemButton._prototype.set_type = Prototype.setter('string','draw_data','elem_type')
--- Sets the default value for the elem button, this may be a function or a string
-- @tparam ?string|function value string a 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

View File

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

View File

@@ -1,173 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Prototype
]]
--- Sliders.
-- Gui class define for sliders
-- @section sliders
--[[
>>>> 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 'expcore.gui.core' --- @dep expcore.gui.core
local Prototype = require 'expcore.gui.prototype' --- @dep expcore.gui.prototype
local Instances = require 'expcore.gui.instances' --- @dep expcore.gui.instances
local Game = require 'utils.game' --- @dep utils.game
--- Event call for on_value_changed and store update
-- @tparam table define the define that this is acting on
-- @tparam LuaGuiElement element the element that triggered the event
-- @tparam number value 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
define:raise_event('on_element_update',player,element,value,percent)
local category = player.name
if define.categorize then
category = define.categorize(element)
end
Instances.unregistered_get_elements(define.name..'-label',category,function(label)
label.caption = tostring(math.round(value,2))
end)
end
--- Store call for store update
-- @tparam table define the define that this is acting on
-- @tparam LuaGuiElement element the element that triggered the event
-- @tparam number value the new value for the slider
local function store_update(define,element,value)
element.slider_value = value
event_call(define,element,value)
end
local Slider = {
_prototype=Prototype.extend{
on_element_update = Prototype.event,
on_store_update = Prototype.event,
add_store = Prototype.store(store_update)
}
}
--- Creates a new slider element define
-- @tparam[opt] string name the optional debug name that can be added
-- @treturn table the new slider element define
function Slider.new_slider(name)
local self = Gui.new_define(Slider._prototype,name)
self.draw_data.type = 'slider'
self:on_draw(function(player,element)
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 value = self:get_store(element)
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
self:set_store(element,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] number min the minimum value that the slider can take
-- @tparam[opt] number max 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 LuaGuiElement element the parent element that the label 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
value = self:get_store(element) or 0
end
local new_element = element.add{
name=name,
type='label',
caption=tostring(math.round(value,2))
}
local categorise = self.categorise or Gui.categorize_by_player
local category = categorise(new_element)
Instances.unregistered_add_element(name,category,new_element)
return new_element
end
--- Enables auto draw of the label, the label will share the same parent element as the slider
-- @tparam[opt=true] boolean state 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

View File

@@ -1,145 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Prototype
]]
--- Text.
-- Gui class define for text fields and text boxes
-- @section text
--[[
>>>> Functions
Text.new_text_field(name) --- Creates a new text field element define
Text._prototype_field:on_element_update(callback) --- Registers a handler for when an element instance updates
Text._prototype_field:on_store_update(callback) --- Registers a handler for when the stored value updates
Text.new_text_box(name) --- Creates a new text box element define
Text._prototype_field:on_element_update(callback) --- Registers a handler for when an element instance updates
Text._prototype_field:on_store_update(callback) --- Registers a handler for when the stored value updates
Text._prototype_box:set_selectable(state) --- Sets the text box to be selectable
Text._prototype_box:set_word_wrap(state) --- Sets the text box to have word wrap
Text._prototype_box:set_read_only(state) --- Sets the text box to be read only
Other functions present from expcore.gui.core
]]
local Gui = require 'expcore.gui.core' --- @dep expcore.gui.core
local Prototype = require 'expcore.gui.prototype' --- @dep expcore.gui.prototype
local Game = require 'utils.game' --- @dep utils.game
--- Store call for store update
-- @tparam table define the define that this is acting on
-- @tparam LuaGuiElement element the element that triggered the event
-- @tparam string value the new text for the text field
local function store_update(define,element,value)
element.text = value
local player = Game.get_player_by_index(element.player_index)
define:raise_event('on_element_update',player,element,value)
end
local Text = {
_prototype_field=Prototype.extend{
on_element_update = Prototype.event,
on_store_update = Prototype.event,
add_store = Prototype.store(store_update)
},
_prototype_box=Prototype.extend{
on_element_update = Prototype.event,
on_store_update = Prototype.event,
add_store = Prototype.store(store_update)
}
}
--- Creates a new text field element define
-- @tparam[opt] string name 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.new_define(Text._prototype_field,name)
self.draw_data.type = 'textfield'
self:on_draw(function(player,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 value = self:get_store(element)
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
self:set_store(element,value)
else
self:raise_event('on_element_update',event.player,element,value)
end
end)
return self
end
--- Creates a new text box element define
-- @tparam[opt] string name 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] boolean state 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] boolean state 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] boolean state 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

View File

@@ -0,0 +1,91 @@
--[[-- Core Module - Gui
- Functions used to help with the use of guis
@module Gui
]]
local Gui = require 'expcore.gui.prototype'
--- Helper Functions.
-- @section helperFunctions
--[[-- Get the player that owns a gui element
@tparam LuaGuiElement element the element to get the owner of
@treturn LuaPlayer the player that owns this element
@usage-- Geting the owner of an element
local player = Gui.get_player_from_element(element)
]]
function Gui.get_player_from_element(element)
if not element or not element.valid then return end
return game.players[element.player_index]
end
--[[-- Will toggle the enabled state of an element or set it to the one given
@tparam LuaGuiElement element the element to toggle/set the enabled state of
@tparam[opt] boolean state with given will set the state, else state will be toggled
@treturn boolean the new enabled state that the element has
@usage-- Toggling the the enabled state
local new_enabled_state = Gui.toggle_enabled_state(element)
]]
function Gui.toggle_enabled_state(element,state)
if not element or not element.valid then return end
if state == nil then state = not element.enabled end
element.enabled = state
return state
end
--[[-- Will toggle the visible state of an element or set it to the one given
@tparam LuaGuiElement element the element to toggle/set the visible state of
@tparam[opt] boolean state with given will set the state, else state will be toggled
@treturn boolean the new visible state that the element has
@usage-- Toggling the the visible state
local new_visible_state = Gui.toggle_visible_state(element)
]]
function Gui.toggle_visible_state(element,state)
if not element or not element.valid then return end
if state == nil then state = not element.visible end
element.visible = state
return state
end
--[[-- Destory a gui element without causing any errors, often because the element was already removed
@tparam LuaGuiElement element the element that you want to remove
@treturn boolean true if the element was valid and has been removed
@usage-- Remove a child element if it exists
Gui.destroy_if_valid(element[child_name])
]]
function Gui.destroy_if_valid(element)
if not element or not element.valid then return false end
element.destroy()
return true
end
--[[-- Returns a table to be used as the style for a sprite buttons, produces a sqaure button
@tparam number size the size that you want the button to be
@tparam[opt=-2] number padding the padding that you want on the sprite
@tparam[opt] table style any extra style settings that you want to have
@treturn table the style table to be used with element_define:style()
@usage-- Adding a sprite button with size 20
local button =
Gui.element{
type = 'sprite-button',
sprite = 'entity/inserter'
}
:style(Gui.sprite_style(20))
]]
function Gui.sprite_style(size,padding,style)
style = style or {}
style.padding = padding or -2
style.height = size
style.width = size
return style
end

View File

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

270
expcore/gui/left_flow.lua Normal file
View File

@@ -0,0 +1,270 @@
--[[-- Core Module - Gui
- Used to define new gui elements and gui event handlers
@module Gui
]]
local Gui = require 'expcore.gui.prototype'
local mod_gui = require 'mod-gui' --- @dep mod-gui
local hide_left_flow = Gui.core_defines.hide_left_flow.name
--- Left Flow.
-- @section leftFlow
-- Triggered when a user changed the visibility of a left flow element by clicking a button
Gui.events.on_visibility_changed_by_click = 'on_visibility_changed_by_click'
--- Contains the uids of the elements that will shown on the left flow and their join functions
-- @table left_elements
Gui.left_elements = {}
--[[-- Gets the flow refered to as the left flow, each player has one left flow
@function Gui.get_left_flow(player)
@tparam LuaPlayer player the player that you want to get the left flow for
@treturn LuaGuiElement the left element flow
@usage-- Geting your left flow
local left_flow = Gui.get_left_flow(game.player)
]]
Gui.get_left_flow = mod_gui.get_frame_flow
--[[-- Sets an element define to be drawn to the left flow when a player joins, includes optional check
@tparam[opt] ?boolean|function open_on_join called during first darw to decide if the element should be visible
@treturn table the new element define that is used to register events to this element
@usage-- Adding the example button
example_flow_with_button:add_to_left_flow(true)
]]
function Gui._prototype_element:add_to_left_flow(open_on_join)
Gui.left_elements[self.name] = open_on_join or false
return self
end
--[[-- Styles a top flow button depending on the state given
@tparam LuaGuiElement the button element to style
@tparam boolean state The state the button is in
@usage-- Sets the button to the visible style
Gui.left_toolbar_button_style(button, true)
@usage-- Sets the button to the hidden style
Gui.left_toolbar_button_style(button, false)
]]
function Gui.left_toolbar_button_style(button, state)
if state then
button.style = Gui.top_flow_button_visible_style
else
button.style = Gui.top_flow_button_style
end
button.style.minimal_width = 36
button.style.height = 36
button.style.padding = -2
end
--[[-- Creates a button on the top flow which will toggle the given element define, the define must exist in the left flow
@tparam string sprite the sprite that you want to use on the button
@tparam ?string|Concepts.LocalizedString tooltip the tooltip that you want the button to have
@tparam table element_define the element define that you want to have toggled by this button, define must exist on the left flow
@tparam[opt] function authenticator used to decide if the button should be visible to a player
@usage-- Add a button to toggle a left element
local toolbar_button =
Gui.left_toolbar_button('entity/inserter', 'Nothing to see here', example_flow_with_button, function(player)
return player.admin
end)
]]
function Gui.left_toolbar_button(sprite,tooltip,element_define,authenticator)
local button = Gui.element{
type = 'sprite-button',
sprite = sprite,
tooltip = tooltip,
style = Gui.top_flow_button_style
}
:style{
minimal_width = 36,
height = 36,
padding = -2
}
:add_to_top_flow(authenticator)
-- Add on_click handler to handle click events comming from the player
button:on_click(function(player,_,_)
local top_flow = Gui.get_top_flow(player)
local element = top_flow[button.name]
local visibility_state = Gui.toggle_left_element(player, element_define)
-- Raise custom event that tells listening elements if the element has changed visibility by a player clicking
-- Used in warp gui to handle the keep open logic
button:raise_custom_event{
name = Gui.events.on_visibility_changed_by_click,
element = element,
state = visibility_state
}
end)
-- Add property to the left flow element with the name of the button
-- This is for the ability to reverse lookup the button from the left flow element
element_define.toolbar_button = button.name
return button
end
--[[-- Draw all the left elements onto the left flow, internal use only with on join
@tparam LuaPlayer player the player that you want to draw the elements for
@usage Draw all the left elements
Gui.draw_left_flow(player)
]]
function Gui.draw_left_flow(player)
local left_flow = Gui.get_left_flow(player)
local hide_button = left_flow.gui_core_buttons[hide_left_flow]
local show_hide_button = false
for name, open_on_join in pairs(Gui.left_elements) do
-- Draw the element to the left flow
local left_element = Gui.defines[name](left_flow)
-- Check if it should be open by default
local visible = type(open_on_join) == 'boolean' and open_on_join or false
if type(open_on_join) == 'function' then
local success, err = pcall(open_on_join, player)
if not success then
error('There as been an error with an open on join hander for a gui element:\n\t'..err)
end
visible = err
end
-- Set the visible state of the element
left_element.visible = visible
show_hide_button = show_hide_button or visible
-- Get the assosiated element define
local element_define = Gui.defines[name]
local top_flow = Gui.get_top_flow(player)
-- Check if the the element has a button attached
if element_define.toolbar_button then
-- Check if the topflow contains the button
local button = top_flow[element_define.toolbar_button]
if button then
-- Style the button
Gui.left_toolbar_button_style(button, visible)
end
end
end
hide_button.visible = show_hide_button
end
--[[-- Update the visible state of the hide button, can be used to check if any frames are visible
@tparam LuaPlayer player the player to update the left flow for
@treturn boolean true if any left element is visible
@usage-- Check if any left elements are visible
local visible = Gui.update_left_flow(player)
]]
function Gui.update_left_flow(player)
local left_flow = Gui.get_left_flow(player)
local hide_button = left_flow.gui_core_buttons[hide_left_flow]
for name, _ in pairs(Gui.left_elements) do
local left_element = left_flow[name]
if left_element.visible then
hide_button.visible = true
return true
end
end
hide_button.visible = false
return false
end
--[[-- Hides all left elements for a player
@tparam LuaPlayer player the player to hide the elements for
@usage-- Hide your left elements
Gui.hide_left_flow(game.player)
]]
function Gui.hide_left_flow(player)
local top_flow = Gui.get_top_flow(player)
local left_flow = Gui.get_left_flow(player)
local hide_button = left_flow.gui_core_buttons[hide_left_flow]
-- Set the visible state of all elements in the flow
hide_button.visible = false
for name,_ in pairs(Gui.left_elements) do
left_flow[name].visible = false
-- Check if the the element has a toobar button attached
local element_define = Gui.defines[name]
if element_define.toolbar_button then
-- Check if the topflow contains the button
local button = top_flow[element_define.toolbar_button]
if button then
-- Style the button
Gui.left_toolbar_button_style(button, false)
-- Get the button define from the reverse lookup on the element
local button_define = Gui.defines[element_define.toolbar_button]
-- Raise the custom event if all of the top checks have passed
button_define:raise_custom_event{
name = Gui.events.on_visibility_changed_by_click,
element = button,
state = false
}
end
end
end
end
--[[-- Get the element define that is in the left flow, use in events without an element refrence
@tparam LuaPlayer player the player that you want to get the element for
@tparam table element_define the element that you want to get
@treturn LuaGuiElement the gui element linked to this define for this player
@usage-- Get your left element
local frame = Gui.get_left_element(game.player, example_flow_with_button)
]]
function Gui.get_left_element(player,element_define)
local left_flow = Gui.get_left_flow(player)
return left_flow[element_define.name]
end
--[[-- Toggles the visible state of a left element for a given player, can be used to set the visible state
@tparam LuaPlayer player the player that you want to toggle the element for
@tparam table element_define the element that you want to toggle
@tparam[opt] boolean state with given will set the state, else state will be toggled
@treturn boolean the new visible state of the element
@usage-- Toggle your example button
Gui.toggle_top_flow(game.player, example_flow_with_button)
@usage-- Show your example button
Gui.toggle_top_flow(game.player, example_flow_with_button, true)
]]
function Gui.toggle_left_element(player,element_define,state)
local left_flow = Gui.get_left_flow(player)
local top_flow = Gui.get_top_flow(player)
-- Set the visible state
local element = left_flow[element_define.name]
if state == nil then state = not element.visible end
element.visible = state
Gui.update_left_flow(player)
-- Check if the the element has a button attached
if element_define.toolbar_button then
-- Check if the topflow contains the button
local button = top_flow[element_define.toolbar_button]
if button then
-- Style the button
Gui.left_toolbar_button_style(button, state)
end
end
return state
end

View File

@@ -1,300 +1,414 @@
--[[-- Core Module - Gui
@module Gui
@alias Prototype
- Used to simplify gui creation using factory functions called element defines
@core Gui
@alias Gui
@usage-- To draw your element you only need to call the factory function
-- You are able to pass any other arguments that are used in your custom functions but the first is always the parent element
local example_button_element = example_button(parent_element)
@usage-- Making a factory function for a button with the caption "Example Button"
-- This method has all the same features as LuaGuiElement.add
local example_button =
Gui.element{
type = 'button',
caption = 'Example Button'
}
@usage-- Making a factory function for a button which is contained within a flow
-- This method is for when you still want to register event handlers but cant use the table method
local example_flow_with_button =
Gui.element(function(event_trigger,parent,...)
-- ... shows that all other arguments from the factory call are passed to this function
-- Here we are adding a flow which we will then later add a button to
local flow =
parent.add{ -- paraent is the element which is passed to the factory function
name = 'example_flow',
type = 'flow'
}
-- Now we add the button to the flow that we created earlier
local element =
flow.add{
name = event_trigger, -- event_trigger should be the name of any elements you want to trigger your event handlers
type = 'button',
caption = 'Example Button'
}
-- You must return a new element, this is so styles can be applied and returned to the caller
-- You may return any of your elements that you added, consider the context in which it will be used for which should be returned
return element
end)
@usage-- Styles can be added to any element define, simplest way mimics LuaGuiElement.style[key] = value
local example_button =
Gui.element{
type = 'button',
caption = 'Example Button',
style = 'forward_button' -- factorio styles can be applied here
}
:style{
height = 25, -- same as element.style.height = 25
width = 100 -- same as element.style.width = 25
}
@usage-- Styles can also have a custom function when the style is dynamic and depends on other factors
-- Use this method if your style is dynamic and depends on other factors
local example_button =
Gui.element{
type = 'button',
caption = 'Example Button',
style = 'forward_button' -- factorio styles can be applied here
}
:style(function(style,element,...)
-- style is the current style object for the elemenent
-- element is the element that is being changed
-- ... shows that all other arguments from the factory call are passed to this function
local player = game.players[element.player_index]
style.height = 25
style.width = 100
style.font_color = player.color
end)
@usage-- You are able to register event handlers to your elements, these can be factorio events or custom ones
-- All events are checked to be valid before raising any handlers, this means element.valid = true and player.valid = true
Gui.element{
type = 'button',
caption = 'Example Button'
}
:on_click(function(player,element,event)
-- player is the player who interacted with the element to cause the event
-- element is a refrence to the element which caused the event
-- event is a raw refrence to the event data if player and element are not enough
player.print('Clicked: '..element.name)
end)
@usage-- Example from core_defines, Gui.core_defines.hide_left_flow, called like: hide_left_flow(parent_element)
--- Button which hides the elements in the left flow, shows inside the left flow when frames are visible
-- @element hide_left_flow
local hide_left_flow =
Gui.element{
type = 'sprite-button',
sprite = 'utility/close_black',
style = 'tool_button',
tooltip = {'expcore-gui.left-button-tooltip'}
}
:style{
padding = -3,
width = 18,
height = 20
}
:on_click(function(player,_,_)
Gui.hide_left_flow(player)
end)
@usage-- Eample from defines, Gui.alignment, called like: Gui.alignment(parent, name, horizontal_align, vertical_align)
-- Notice how _ are used to blank arguments that are not needed in that context and how they line up with above
Gui.alignment =
Gui.element(function(_,parent,name,_,_)
return parent.add{
name = name or 'alignment',
type = 'flow',
}
end)
:style(function(style,_,_,horizontal_align,vertical_align)
style.padding = {1,2}
style.vertical_align = vertical_align or 'center'
style.horizontal_align = horizontal_align or 'right'
style.vertically_stretchable = style.vertical_align ~= 'center'
style.horizontally_stretchable = style.horizontal_align ~= 'center'
end)
]]
--- Prototype.
-- Used to create new gui prototypes see elements and concepts
-- @section prototype
local Event = require 'utils.event' --- @dep utils.event
--[[
>>>> Functions
Constructor.event(event_name) --- Creates a new function to add functions to an event handler
Constructor.extend(new_prototype) --- Extents a prototype with the base functions of all gui prototypes, no metatables
Constructor.store(sync,callback) --- Creates a new function which adds a store to a gui define
Constructor.setter(value_type,key,second_key) --- Creates a setter function that checks the type when a value is set
local Gui = {
--- The current highest uid that is being used by a define, will not increase during runtime
-- @field uid
uid = 0,
--- String indexed table used to avoid conflict with custom event names, similar to how defines.events works
-- @table events
events = {},
--- Uid indexed array that stores all the factory functions that were defined, no new values will be added during runtime
-- @table defines
defines = {},
--- An string indexed table of all the defines which are used by the core of the gui system, used for internal refrence
-- @table core_defines
core_defines = {},
--- Used to store the file names where elements were defined, this can be useful to find the uid of an element, mostly for debuging
-- @table file_paths
file_paths = {},
--- Used to store extra infomation about elements as they get defined such as the params used and event handlers registered to them
-- @table debug_info
debug_info = {},
--- The prototype used to store the functions of an element define
-- @table _prototype_element
_prototype_element = {},
--- The prototype metatable applied to new element defines
-- @table _mt_element
_mt_element = {
__call = function(self,parent,...)
local element = self._draw(self.name,parent,...)
if self._style then self._style(element.style,element,...) end
return element
end
}
}
Prototype:uid() --- Gets the uid for the element define
Prototype:debug_name(value) --- Sets a debug alias for the define
Prototype:set_caption(value) --- Sets the caption for the element define
Prototype:set_tooltip(value) --- Sets the tooltip for the element define
Prototype:set_style(style,callback) --- Sets the style for the element define
Prototype:set_embedded_flow(state) --- Sets the element to be drawn inside a nameless flow, can be given a name using a function
Gui._mt_element.__index = Gui._prototype_element
Prototype:set_pre_authenticator --- Sets an authenticator that blocks the draw function if check fails
Prototype:set_post_authenticator --- Sets an authenticator that disables the element if check fails
--- Element Define.
-- @section elementDefine
Prototype:raise_event(event_name,...) --- Raises a custom event for this define, any number of params can be given
Prototype:draw_to(element,...) --- The main function for defines, when called will draw an instance of this define to the given element
--[[-- Used to define new elements for your gui, can be used like LuaGuiElement.add or a custom function
@tparam ?table|function element_define the define information for the gui element, same data as LuaGuiElement.add, or a custom function may be used
@treturn table the new element define, this can be considered a factory for the element which can be called to draw the element to any other element
@usage-- Using element defines like LuaGuiElement.add
-- This returns a factory function to draw a button with the caption "Example Button"
local example_button =
Gui.element{
type = 'button',
caption = 'Example Button'
}
@usage-- Using element defines with a custom factory function
-- This method can be used if you still want to be able register event handlers but it is too complex to be compatible with LuaGuiElement.add
local example_flow_with_button =
Gui.element(function(event_trigger,parent,...)
-- ... shows that all other arguments from the factory call are passed to this function
-- parent is the element which was passed to the factory function where you should add your new element
-- here we are adding a flow which we will then later add a button to
local flow =
parent.add{
name = 'example_flow',
type = 'flow'
}
-- event_trigger should be the name of any elements you want to trigger your event handlers, such as on_click or on_state_changed
-- now we add the button to the flow that we created earlier
local element =
flow.add{
name = event_trigger,
type = 'button',
caption = 'Example Button'
}
-- you must return your new element, this is so styles can be applied and returned to the caller
-- you may return any of your elements that you add, consider the context in which it will be used for what should be returned
return element
end)
Prototype:get_store(category) --- Gets the value in this elements store, category needed if serializer function used
Prototype:set_store(category,value) --- Sets the value in this elements store, category needed if serializer function used
Prototype:clear_store(category) --- Sets the value in this elements store to nil, category needed if serializer function used
]]
local Game = require 'utils.game' --- @dep utils.game
local Store = require 'expcore.store' --- @dep expcore.store
local Instances = require 'expcore.gui.instances' --- @dep expcore.gui.instances
function Gui.element(element_define)
-- Set the metatable to allow access to register events
local element = setmetatable({}, Gui._mt_element)
local Constructor = {}
local Prototype = {}
-- Increment the uid counter
local uid = Gui.uid + 1
Gui.uid = uid
local name = tostring(uid)
element.name = name
Gui.debug_info[name] = { draw = 'None', style = 'None', events = {} }
--- Creates a new function to add functions to an event handler
-- @tparam string event_name the name of the event that callbacks will be added to
-- @treturn function the function used to register handlers
function Constructor.event(event_name)
--- Adds a callback as a handler for an event
-- @tparam table self the gui define being acted on
-- @tparam function callback the function that will be added as a handler for the event
-- @treturn table self returned to allowing chaining of functions
return function(self,callback)
if type(callback) ~= 'function' then
return error('Event callback for '..event_name..' must be a function',2)
-- Add the defination function
if type(element_define) == 'table' then
Gui.debug_info[name].draw = element_define
element_define.name = name
element._draw = function(_,parent)
return parent.add(element_define)
end
local handlers = self.events[event_name]
if not handlers then
handlers = {}
self.events[event_name] = handlers
end
handlers[#handlers+1] = callback
return self
end
end
--- Extents a prototype with the base functions of all gui prototypes, no metatables
-- @tparam table new_prototype the prototype that you want to add the functions to
-- @treturn table the same prototype but with the new functions added
function Constructor.extend(new_prototype)
for key,value in pairs(Prototype) do
if type(value) == 'table' then
new_prototype[key] = table.deepcopy(value)
else
new_prototype[key] = value
end
end
for key,value in pairs(new_prototype) do
if value == Constructor.event then
new_prototype[key] = Constructor.event(key)
end
end
return new_prototype
end
--- Creates a new function which adds a store to a gui define
-- @tparam function callback the function called when needing to update the value of an element
-- @treturn function the function that will add a store for this define
function Constructor.store(callback)
--- Adds a store for the define that is shared between all instances of the define in the same category, serializer is a function that returns a string
-- @tparam self table the gui define being acted on
-- @tparam[opt] function serializer function used to determine the category of a LuaGuiElement, when omitted all share one single category
-- serializer param - LuaGuiElement element - the element that needs to be converted
-- serializer return - string - a deterministic string that references to a category such as player name or force name
-- @treturn self the element define to allow chaining
return function(self,serializer)
if self.store then return end
serializer = serializer or function() return '' end
self.store = Store.register(serializer)
Instances.register(self.name,serializer)
Store.watch(self.store,function(value,category)
self:raise_event('on_store_update',value,category)
if Instances.is_registered(self.name) then
Instances.apply_to_elements(self.name,category,function(element)
callback(self,element,value)
end)
end
end)
return self
end
end
--- Creates a setter function that checks the type when a value is set
-- @tparam string value_type the type that the value should be when it is set
-- @tparam string key the key of the define that will be set
-- @tparam[opt] string second_key allows for setting of a key in a sub table
-- @treturn function the function that will check the type and set the value
function Constructor.setter(value_type,key,second_key)
local display_message = 'Gui define '..key..' must be of type '..value_type
if second_key then
display_message = 'Gui define '..second_key..' must be of type '..value_type
end
local locale = false
if value_type == 'locale-string' then
locale = true
value_type = 'table'
end
return function(self,value)
local v_type = type(value)
if v_type ~= value_type and (not locale or v_type ~= 'string') then
error(display_message,2)
end
if second_key then
self[key][second_key] = value
else
self[key] = value
end
return self
end
end
--- Gets the uid for the element define
-- @treturn string the uid of this element define
function Prototype:uid()
return self.name
end
--- Sets a debug alias for the define
-- @tparam string name the debug name for the element define that can be used to get this element define
-- @treturn self the element define to allow chaining
Prototype.debug_name = Constructor.setter('string','debug_name')
--- Sets the caption for the element define
-- @tparam string caption the caption that will be drawn with the element
-- @treturn self the element define to allow chaining
Prototype.set_caption = Constructor.setter('locale-string','draw_data','caption')
--- Sets the tooltip for the element define
-- @tparam string tooltip the tooltip that will be displayed for this element when drawn
-- @treturn self the element define to allow chaining
Prototype.set_tooltip = Constructor.setter('locale-string','draw_data','tooltip')
--- Sets an authenticator that blocks the draw function if check fails
-- @tparam function callback the function that will be ran to test if the element should be drawn or not
-- callback param - LuaPlayer player - the player that the element is being drawn to
-- callback param - string define_name - 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
Prototype.set_pre_authenticator = Constructor.setter('function','pre_authenticator')
--- Sets an authenticator that disables the element if check fails
-- @tparam function callback the function that will be ran to test if the element should be enabled or not
-- callback param - LuaPlayer player - the player that the element is being drawn to
-- callback param - string define_name - 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
Prototype.set_post_authenticator = Constructor.setter('function','post_authenticator')
--- Registers a callback to the on_draw event
-- @tparam function callback
-- callback param - LuaPlayer player - the player that the element was drawn to
-- callback param - LuaGuiElement element - the element that was drawn
-- callback param - any ... - any other params passed by the draw_to function
Prototype.on_draw = Constructor.event('on_draw')
--- Registers a callback to the on_style_update event
-- @tparam function callback
-- callback param - LuaStyle style - the style that was changed and/or needs changing
Prototype.on_style_update = Constructor.event('on_style_update')
--- Sets the style for the element define
-- @tparam string style the style that will be used for this element when drawn
-- @tparam[opt] function callback function is called when element is drawn to alter its style
-- @treturn self the element define to allow chaining
function Prototype:set_style(style,callback)
self.draw_data.style = style
if callback then
self:on_style_update(callback)
end
return self
end
--- Sets the element to be drawn inside a nameless flow, can be given a name using a function
-- @tparam ?boolean|function state when true a padless flow is created to contain the element
-- @treturn self the element define to allow chaining
function Prototype:set_embedded_flow(state)
if state == false or type(state) == 'function' then
self.embedded_flow = state
else
self.embedded_flow = true
Gui.debug_info[name].draw = 'Function'
element._draw = element_define
end
-- Add the define to the base module
local file_path = debug.getinfo(2, 'S').source:match('^.+/currently%-playing/(.+)$'):sub(1, -5)
Gui.file_paths[name] = file_path
Gui.defines[name] = element
-- Return the element so event handers can be accessed
return element
end
--[[-- Used to extent your element define with a style factory, this style will be applied to your element when created, can also be a custom function
@tparam ?table|function style_define style table where each key and value pair is treated like LuaGuiElement.style[key] = value, a custom function can be used
@treturn table the element define is returned to allow for event handlers to be registered
@usage-- Using the table method of setting the style
local example_button =
Gui.element{
type = 'button',
caption = 'Example Button',
style = 'forward_button' -- factorio styles can be applied here
}
:style{
height = 25, -- same as element.style.height = 25
width = 100 -- same as element.style.width = 25
}
@usage-- Using the function method to set the style
-- Use this method if your style is dynamic and depends on other factors
local example_button =
Gui.element{
type = 'button',
caption = 'Example Button',
style = 'forward_button' -- factorio styles can be applied here
}
:style(function(style,element,...)
-- style is the current style object for the elemenent
-- element is the element that is being changed
-- ... shows that all other arguments from the factory call are passed to this function
local player = game.players[element.player_index]
style.height = 25
style.width = 100
style.font_color = player.color
end)
]]
function Gui._prototype_element:style(style_define)
-- Add the defination function
if type(style_define) == 'table' then
Gui.debug_info[self.name].style = style_define
self._style = function(style)
for key,value in pairs(style_define) do
style[key] = value
end
end
else
Gui.debug_info[self.name].style = 'Function'
self._style = style_define
end
-- Return the element so event handers can be accessed
return self
end
--[[-- Set the handler which will be called for a custom event, only one handler can be used per event per element
@tparam string event_name the name of the event you want to handler to be called on, often from Gui.events
@tparam function handler the handler that you want to be called when the event is raised
@treturn table the element define so more handleres can be registered
@usage-- Register a handler to "my_custom_event" for this element
element_deinfe:on_custom_event('my_custom_event', function(event)
event.player.print(player.name)
end)
]]
function Gui._prototype_element:on_custom_event(event_name,handler)
table.insert(Gui.debug_info[self.name].events,event_name)
Gui.events[event_name] = event_name
self[event_name] = handler
return self
end
--[[-- Raise the handler which is attached to an event; external use should be limited to custom events
@tparam table event the event table passed to the handler, must contain fields: name, element
@treturn table the element define so more events can be raised
@usage Raising a custom event
element_define:raise_custom_event{
name = 'my_custom_event',
element = element
}
]]
function Gui._prototype_element:raise_custom_event(event)
-- Check the element is valid
local element = event.element
if not element or not element.valid then
return self
end
-- Get the event handler for this element
local handler = self[event.name]
if not handler then
return self
end
-- Get the player for this event
local player_index = event.player_index or element.player_index
local player = game.players[player_index]
if not player or not player.valid then
return self
end
event.player = player
local success, err = pcall(handler,player,element,event)
if not success then
error('There as been an error with an event handler for a gui element:\n\t'..err)
end
return self
end
--- Raises a custom event for this define, any number of params can be given
-- @tparam string event_name the name of the event that you want to raise
-- @tparam any ... any params that you want to pass to the event
-- @treturn number the number of handlers that were registered
function Prototype:raise_event(event_name,...)
local handlers = self.events[event_name]
if handlers then
for _,handler in pairs(handlers) do
handler(...)
end
-- This function is used to link element define events and the events from the factorio api
local function event_handler_factory(event_name)
Event.add(event_name, function(event)
local element = event.element
if not element or not element.valid then return end
local element_define = Gui.defines[element.name]
element_define:raise_custom_event(event)
end)
return function(self,handler)
table.insert(Gui.debug_info[self.name].events,debug.getinfo(1, "n").name)
self[event_name] = handler
return self
end
return handlers and #handlers or 0
end
--- The main function for defines, when called will draw an instance of this define to the given element
-- what is drawn is based on the data in draw_data which is set using other functions
-- @tparam LuaGuiElement element the element that the define will draw a instance of its self onto
-- @treturn LuaGuiElement the new element that was drawn
function Prototype:draw_to(element,...)
local name = self.name
if element[name] then return end
local player = Game.get_player_by_index(element.player_index)
--- Element Events.
-- @section elementEvents
if self.pre_authenticator then
if not self.pre_authenticator(player,self.name) then return end
end
--- Called when the player opens a GUI.
-- @tparam function handler the event handler which will be called
Gui._prototype_element.on_opened = event_handler_factory(defines.events.on_gui_opened)
if self.embedded_flow then
local embedded_name
if type(self.embedded_flow) == 'function' then
embedded_name = self.embedded_flow(element,...)
end
element = element.add{type='flow',name=embedded_name}
element.style.padding = 0
end
--- Called when the player closes the GUI they have open.
-- @tparam function handler the event handler which will be called
Gui._prototype_element.on_closed = event_handler_factory(defines.events.on_gui_closed)
local new_element = element.add(self.draw_data)
--- Called when LuaGuiElement is clicked.
-- @tparam function handler the event handler which will be called
Gui._prototype_element.on_click = event_handler_factory(defines.events.on_gui_click)
self:raise_event('on_style_update',new_element.style)
--- Called when a LuaGuiElement is confirmed, for example by pressing Enter in a textfield.
-- @tparam function handler the event handler which will be called
Gui._prototype_element.on_confirmed = event_handler_factory(defines.events.on_gui_confirmed)
if self.post_authenticator then
new_element.enabled = self.post_authenticator(player,self.name)
end
--- Called when LuaGuiElement checked state is changed (related to checkboxes and radio buttons).
-- @tparam function handler the event handler which will be called
Gui._prototype_element.on_checked_changed = event_handler_factory(defines.events.on_gui_checked_state_changed)
if Instances.is_registered(self.name) then
Instances.add_element(self.name,new_element)
end
--- Called when LuaGuiElement element value is changed (related to choose element buttons).
-- @tparam function handler the event handler which will be called
Gui._prototype_element.on_elem_changed = event_handler_factory(defines.events.on_gui_elem_changed)
self:raise_event('on_draw',player,new_element,...)
--- Called when LuaGuiElement element location is changed (related to frames in player.gui.screen).
-- @tparam function handler the event handler which will be called
Gui._prototype_element.on_location_changed = event_handler_factory(defines.events.on_gui_location_changed)
return new_element
end
--- Called when LuaGuiElement selected tab is changed (related to tabbed-panes).
-- @tparam function handler the event handler which will be called
Gui._prototype_element.on_tab_changed = event_handler_factory(defines.events.on_gui_selected_tab_changed)
--- Gets the value in this elements store, category needed if serializer function used
-- @tparam string category[opt] the category to get such as player name or force name
-- @treturn any the value that is stored for this define
function Prototype:get_store(category)
if not self.store then return end
return Store.get(self.store,category)
end
--- Called when LuaGuiElement selection state is changed (related to drop-downs and listboxes).
-- @tparam function handler the event handler which will be called
Gui._prototype_element.on_selection_changed = event_handler_factory(defines.events.on_gui_selection_state_changed)
--- Sets the value in this elements store, category needed if serializer function used
-- @tparam string category[opt] the category to get such as player name or force name
-- @tparam any value the value to set for this define, must be valid for its type ie for checkbox etc
-- @treturn boolean true if the value was set
function Prototype:set_store(category,value)
if not self.store then return end
return Store.set(self.store,category,value)
end
--- Called when LuaGuiElement switch state is changed (related to switches).
-- @tparam function handler the event handler which will be called
Gui._prototype_element.on_switch_changed = event_handler_factory(defines.events.on_gui_switch_state_changed)
--- Sets the value in this elements store to nil, category needed if serializer function used
-- @tparam[opt] string category the category to get such as player name or force name
-- @treturn boolean true if the value was set
function Prototype:clear_store(category)
if not self.store then return end
return Store.clear(self.store,category)
end
--- Called when LuaGuiElement text is changed by the player.
-- @tparam function handler the event handler which will be called
Gui._prototype_element.on_text_changed = event_handler_factory(defines.events.on_gui_text_changed)
return Constructor
--- Called when LuaGuiElement slider value is changed (related to the slider element).
-- @tparam function handler the event handler which will be called
Gui._prototype_element.on_value_changed = event_handler_factory(defines.events.on_gui_value_changed)
-- Module return
return Gui

8
expcore/gui/require.lua Normal file
View File

@@ -0,0 +1,8 @@
local Gui = require 'expcore.gui.prototype'
require 'expcore.gui.core_defines'
require 'expcore.gui.top_flow'
require 'expcore.gui.left_flow'
require 'expcore.gui.helper_functions'
require 'expcore.gui.defines'
return Gui

View File

@@ -1,663 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias tests
]]
--- Test.
-- This file creates a test gui that is used to test every input method
-- note that this does not cover every permutation only features in independence
-- for example store in most cases is just by player name, but other store methods are tested with checkbox
-- @section test
local Gui = require 'expcore.gui' --- @dep expcore.gui
local format_chat_colour,table_keys = ext_require('expcore.common','format_chat_colour','table_keys') --- @dep expcore.common
local Colors = require 'resources.color_presets' --- @dep resources.color_presets
local Event = require 'utils.event' --- @dep utils.event
local Store = require 'expcore.store' --- @dep expcore.store
local tests = {}
--[[
Toolbar Tests
> No display - Toolbar button with no display
> With caption - Toolbar button with a caption display
> With icons - Toolbar button with an icon
]]
Gui.new_toolbar_button('click-1')
:set_post_authenticator(function(player,button_name)
return global.click_one
end)
:on_click(function(player,element)
player.print('CLICK 1')
end)
Gui.new_toolbar_button('click-2')
:set_caption('Click Two')
:set_post_authenticator(function(player,button_name)
return global.click_two
end)
:on_click(function(player,element)
player.print('CLICK 2')
end)
Gui.new_toolbar_button('click-3')
:set_sprites('utility/questionmark')
:set_post_authenticator(function(player,button_name)
return global.click_three
end)
:on_click(function(player,element)
player.print('CLICK 3')
end)
--[[
Center Frame Tests
> Main test gui - Main test gui triggers all other tests
]]
local test_gui =
Gui.new_center_frame('gui-test-open')
:set_caption('Open Test Gui')
:set_tooltip('Main test gui triggers all other tests')
:set_post_authenticator(function(player,button_name)
return global.show_test_gui
end)
:on_creation(function(player,frame)
for test_group_name,test_group in pairs(tests) do
player.print('Starting tests for: '..format_chat_colour(test_group_name,Colors.cyan))
local pass_count = 0
local test_count = 0
local flow = frame.add{
type='flow',
name=test_group_name,
direction='vertical'
}
for test_name,test in pairs(test_group) do
local test_function = type(test) == 'function' and test or test.draw_to
test_count = test_count+1
local success,err = pcall(test_function,test,flow)
if success then
pass_count = pass_count+1
else
player.print('Failed Test: '..format_chat_colour(test_name,Colors.red))
log('Gui Test Failed: '..test_name..' stacktrace:\n'..err)
end
end
if pass_count == test_count then
player.print('All tests '..format_chat_colour('passed',Colors.green)..' ('..test_group_name..')')
else
player.print('Passed '..format_chat_colour(pass_count..'/'..test_count,Colors.cyan)..' ('..test_group_name..')')
end
end
end)
--[[
Left Frame Test
> Left frame which holds all online player names, updates when player leaves or joins
]]
local left_frame =
Gui.new_left_frame('test-left-frame')
:set_caption('Test Left Gui')
:set_tooltip('Left frame which holds all online player names, updates when player leaves or joins')
:set_post_authenticator(function(player,button_name)
return global.show_test_gui
end)
:set_open_by_default()
:on_creation(function(_player,frame)
for _,player in pairs(game.connected_players) do
frame.add{
type='label',
caption=player.name
}
end
end)
Event.add(defines.events.on_player_joined_game,left_frame 'update_all')
Event.add(defines.events.on_player_left_game,left_frame 'update_all')
--[[
Popup Test
> Allows opening a popup which contains the players name and tick it was opened
]]
local test_popup =
Gui.new_popup('test-popup')
:on_creation(function(player,frame)
frame.add{
type='label',
caption=player.name
}
frame.add{
type='label',
caption=game.tick
}
end)
Gui.new_toolbar_button('test-popup-open')
:set_caption('Test Popup')
:set_tooltip('Allows opening a popup which contains the players name and tick it was opened')
:set_post_authenticator(function(player,button_name)
return global.show_test_gui
end)
:on_click(function(player,element)
test_popup(player,300)
end)
--[[
Button Tests
> No display - Simple button which has no display
> Caption - Simple button but has a caption on it
> Icons - Button with an icon display plus two icons for hover and select
> Auth - Button which can only be passed when auth is true (press no display to toggle; needs reopen)
]]
local button_no_display =
Gui.new_button('test-button-no-display')
:set_tooltip('Button no display')
:on_click(function(player,element)
player.print('Button no display')
global.test_auth_button = not global.test_auth_button
player.print('Auth Button auth state: '..tostring(global.test_auth_button))
end)
local button_with_caption =
Gui.new_button('test-button-with-caption')
:set_tooltip('Button with caption')
:set_caption('Button Caption')
:on_click(function(player,element)
player.print('Button with caption')
end)
local button_with_icon =
Gui.new_button('test-button-with-icon')
:set_tooltip('Button with icons')
:set_sprites('utility/warning_icon','utility/warning','utility/warning_white')
:on_click(function(player,element)
player.print('Button with icons')
end)
local button_with_auth =
Gui.new_button('test-button-with-auth')
:set_tooltip('Button with auth')
:set_post_authenticator(function(player,button_name)
return global.test_auth_button
end)
:on_click(function(player,element)
player.print('Button with auth')
end)
tests.Buttons = {
['No display']=button_no_display,
['Caption']=button_with_caption,
['Icons']=button_with_icon,
['Auth']=button_with_auth
}
--[[
Checkbox Test
> Local -- Simple checkbox that can toggle
> Game store -- Checkbox which syncs its state between all players
> Force store -- Checkbox which syncs its state with all players on the same force
> Player store -- Checkbox that stores its state between re-draws
]]
local checkbox_local =
Gui.new_checkbox('test-checkbox-local')
:set_tooltip('Checkbox local')
:set_caption('Checkbox Local')
:on_element_update(function(player,element,state)
player.print('Checkbox local: '..tostring(state))
end)
local checkbox_game =
Gui.new_checkbox('test-checkbox-store-game')
:set_tooltip('Checkbox store game')
:set_caption('Checkbox Store Game')
:add_store()
:on_element_update(function(player,element,state)
player.print('Checkbox store game: '..tostring(state))
end)
local checkbox_force =
Gui.new_checkbox('test-checkbox-store-force')
:set_tooltip('Checkbox store force')
:set_caption('Checkbox Store Force')
:add_store(Gui.categorize_by_force)
: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.categorize_by_player)
:on_element_update(function(player,element,state)
player.print('Checkbox store player: '..tostring(state))
end)
tests.Checkboxes = {
['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.categorize_by_player)
:on_element_update(function(player,element,state)
player.print('Radiobutton store: '..tostring(state))
end)
local radiobutton_option_set =
Gui.new_radiobutton_option_set(function(value,category)
game.print('Radiobutton option set for: '..category..' is now: '..tostring(value))
end,Gui.categorize_by_player)
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.categorize_by_player)
: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.categorize_by_player)
: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.categorize_by_player)
: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.categorize_by_player)
:on_element_update(function(player,element,value)
player.print('Dropdown store: '..tostring(value))
end)
tests["List Boxes"] = {
['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('Slider 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('Slider store default')
:add_store(Gui.categorize_by_player)
: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('Slider 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('Slider 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('Slider 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('Slider store label')
:enable_auto_draw_label()
:add_store(Gui.categorize_by_player)
: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.categorize_by_player)
: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.categorize_by_player)
:on_element_update(function(player,element,value)
player.print('Elem store: '..value)
end)
tests["Elem Buttons"] = {
['Local']=elem_local,
['Default']=elem_default,
['Function']=elem_function,
['Store']=elem_store
}
--[[
Progress bar tests
> Simple -- Progress bar that fills every 2 seconds
> Store -- Progress bar that fills every 5 seconds with synced value
> Reverse -- Progress bar that decreases every 2 seconds
]]
local progressbar_one =
Gui.new_progressbar('test-prog-one')
:set_default_maximum(120)
:on_complete(function(player,element,reset_element)
reset_element()
end)
local progressbar_two =
Gui.new_progressbar('test-prog-one')
:set_default_maximum(300)
:add_store(Gui.categorize_by_force)
:on_complete(function(player,element,reset_element)
reset_element()
end)
:on_store_complete(function(category,reset_store)
reset_store()
end)
local progressbar_three =
Gui.new_progressbar('test-prog-one')
:set_default_maximum(120)
:use_count_down()
:on_complete(function(player,element,reset_element)
reset_element()
end)
Event.add(defines.events.on_tick,function()
progressbar_one:increment()
progressbar_three:decrement()
local categories = Store.get(progressbar_two.store) or {}
for category,_ in pairs(categories) do
progressbar_two:increment(1,category)
end
end)
tests["Progress Bars"] = {
['Simple']=progressbar_one,
['Store']=progressbar_two,
['Reverse']=progressbar_three
}

104
expcore/gui/top_flow.lua Normal file
View File

@@ -0,0 +1,104 @@
--[[-- Core Module - Gui
- Controls the elements on the top flow
@module Gui
]]
local Gui = require 'expcore.gui.prototype'
local mod_gui = require 'mod-gui' --- @dep mod-gui
local hide_top_flow = Gui.core_defines.hide_top_flow.name
local show_top_flow = Gui.core_defines.show_top_flow.name
--- Top Flow.
-- @section topFlow
--- Contains the uids of the elements that will shown on the top flow and their auth functions
-- @table top_elements
Gui.top_elements = {}
--- The style that should be used for buttons on the top flow
-- @field Gui.top_flow_button_style
Gui.top_flow_button_style = mod_gui.button_style
--- The style that should be used for buttons on the top flow when their flow is visible
-- @field Gui.top_flow_button_visible_style
Gui.top_flow_button_visible_style = 'menu_button_continue'
--[[-- Gets the flow refered to as the top flow, each player has one top flow
@function Gui.get_top_flow(player)
@tparam LuaPlayer player the player that you want to get the flow for
@treturn LuaGuiElement the top element flow
@usage-- Geting your top flow
local top_flow = Gui.get_top_flow(game.player)
]]
Gui.get_top_flow = mod_gui.get_button_flow
--[[-- Sets an element define to be drawn to the top flow when a player joins, includes optional authenticator
@tparam[opt] function authenticator called during toggle or update to decide weather the element should be visible
@treturn table the new element define to allow event handlers to be registered
@usage-- Adding an element to the top flow on join
example_button:add_to_top_flow(function(player)
-- example button will only be shown if the player is an admin
-- note button will not update its state when player.admin is changed Gui.update_top_flow must be called for this
return player.admin
end)
]]
function Gui._prototype_element:add_to_top_flow(authenticator)
Gui.top_elements[self.name] = authenticator or true
return self
end
--[[-- Updates the visible state of all the elements on the players top flow, uses authenticator
@tparam LuaPlayer player the player that you want to update the top flow for
@usage-- Update your top flow
Gui.update_top_flow(game.player)
]]
function Gui.update_top_flow(player)
local top_flow = Gui.get_top_flow(player)
local hide_button = top_flow[hide_top_flow]
local is_visible = hide_button.visible
-- Set the visible state of all elements in the flow
for name, authenticator in pairs(Gui.top_elements) do
-- Ensure the element exists
local element = top_flow[name]
if not element then
element = Gui.defines[name](top_flow)
end
-- Set the visible state
element.visible = is_visible and authenticator(player) or false
end
end
--[[-- Toggles the visible state of all the elements on a players top flow, effects all elements
@tparam LuaPlayer player the player that you want to toggle the top flow for
@tparam[opt] boolean state if given then the state will be set to this
@treturn boolean the new visible state of the top flow
@usage-- Toggle your flow
Gui.toggle_top_flow(game.player)
@usage-- Open your top flow
Gui.toggle_top_flow(game.player,true)
]]
function Gui.toggle_top_flow(player,state)
-- Get the top flow and hide button
local top_flow = Gui.get_top_flow(player)
if state == nil then state = not top_flow.visible end
-- Change the visiblty of the flow
local left_flow = Gui.get_left_flow(player)
local show_button = left_flow.gui_core_buttons[show_top_flow]
show_button.visible = not state
top_flow.visible = state
return state
end

View File

@@ -27,7 +27,7 @@ local player_scores = Store.register(function(player) -- Use player name as the
end)
-- When any key in the store is changed this function will trigger
Store.watch(player_scores,function(value,key)
Store.watch(player_scores,function(value,key,old_value)
game.print(key..' now has a score of '..value)
end)
@@ -175,7 +175,7 @@ local player_scores = Store.register(function(player)
end)
-- Register the watcher so that when we change the value the message is printed
Store.watch(player_scores,function(value,key)
Store.watch(player_scores,function(value,key,old_value)
game.print(key..' now has a score of '..value)
end)
@@ -274,18 +274,21 @@ Store.clear(player_scores)
]]
function Store.clear(store,key)
key = Store.validate(store,key,2)
local old_value
-- Check if there is a key being used
if key then
if type(data_store[store]) == 'table' then
old_value = data_store[store][key]
data_store[store][key] = nil
end
else
old_value = data_store[store]
data_store[store] = nil
end
-- Trigger any watch functions
Store.raw_trigger(store,key,nil)
Store.raw_trigger(store,key,nil,old_value)
end
--[[-- Used to set the data in a store, will trigger any watchers, key is optional depending on if you are using them
@@ -325,19 +328,22 @@ function Store.set(store,key,value)
-- Check the store is valid
key = Store.validate(store,key,2)
local old_value
-- If there is a key being used then the store must be a able
if key then
if type(data_store[store]) ~= 'table' then
data_store[store] = {_value = data_store[store]}
end
old_value = data_store[store][key]
data_store[store][key] = value
else
old_value = data_store[store]
data_store[store] = value
end
-- Trigger any watchers
Store.raw_trigger(store,key,value)
Store.raw_trigger(store,key,value,old_value)
end
--[[-- Used to update the data in a store, use this with tables, will trigger any watchers, key is optional depending on if you are using them
@@ -386,7 +392,7 @@ function Store.update(store,key,updater)
-- Check the store is valid
key = Store.validate(store,key,2)
local value
local value, old_value
-- If a key is used then the store must be a table
if key then
@@ -397,6 +403,7 @@ function Store.update(store,key,updater)
-- Call the updater and if it returns a value then set this value
local rtn = updater(data_store[store][key])
if rtn then
old_value = data_store[store][key]
data_store[store][key] = rtn
end
value = data_store[store][key]
@@ -405,6 +412,7 @@ function Store.update(store,key,updater)
-- Call the updater and if it returns a value then set this value
local rtn = updater(data_store[store])
if rtn then
old_value = data_store[store][key]
data_store[store] = rtn
end
value = data_store[store]
@@ -412,7 +420,7 @@ function Store.update(store,key,updater)
end
-- Trigger any watchers
Store.raw_trigger(store,key,value)
Store.raw_trigger(store,key,value,old_value)
end
--[[-- Used to update all values that are in a store, similar to Store.update but acts on all keys at once, will trigger watchers for every key present
@@ -455,7 +463,7 @@ function Store.map(store,updater)
if rtn then
data[key] = rtn
end
Store.raw_trigger(store,key,data[key])
Store.raw_trigger(store,key,data[key],value)
end
end
@@ -476,9 +484,10 @@ function Store.trigger(store,key)
-- Get the data from the data store
local data = data_store[store]
if key then
Store.raw_trigger(store,key,data[key])
data = data[key]
Store.raw_trigger(store,key,data,data)
else
Store.raw_trigger(store,key,data)
Store.raw_trigger(store,key,data,data)
end
end
@@ -486,6 +495,7 @@ end
@tparam number store the uid of the store that you want to trigger
@tparam[opt] ?string|any key the key that you want to trigger, must be a string unless you have a serializer
@tparam[opt] any value the new value that is at this key or store, passed directly to the watcher
@tparam[opt] any old_value the old value that was at this key or store often the same if value is a table, passed directly to the watcher
@usage-- Triggering a manule call of the watchers
-- The type of store we use does not really matter for this as long as you pass it what you watchers are expecting
@@ -493,16 +503,16 @@ local scenario_diffculty = Store.register()
-- Trigger the watchers with a fake change of diffculty
-- This is mostly used internally but it can be useful in other cases
Store.raw_trigger(scenario_diffculty,nil,'normal')
Store.raw_trigger(scenario_diffculty,nil,'normal','normal')
]]
function Store.raw_trigger(store,key,value)
function Store.raw_trigger(store,key,value,old_value)
key = Store.validate(store,key,2)
-- Get the watchers and then loop over them
local watchers = Store.watchers[store] or {}
for _,watcher in pairs(watchers) do
local success, err = pcall(watcher,value,key)
local success, err = pcall(watcher,value,key,old_value)
if not success then
error('Store watcher casued an error:\n\t'..err)
end