This commit is contained in:
Cooldude2606
2019-09-28 16:34:52 +01:00
parent 2e06023332
commit 654550bcb1
45 changed files with 4139 additions and 4115 deletions

View File

@@ -39,12 +39,12 @@ return {
'modules.addons.discord-alerts',
'modules.addons.chat-reply',
-- GUI
--'modules.gui.rocket-info',
--'modules.gui.science-info',
--'modules.gui.warp-list',
--'modules.gui.task-list',
'modules.gui.rocket-info',
'modules.gui.science-info',
'modules.gui.warp-list',
'modules.gui.task-list',
'modules.gui.player-list',
--'modules.commands.debug',
'modules.commands.debug',
-- Config Files
'config.expcore-commands.auth_admin', -- commands tagged with admin_only are blocked for non admins
'config.expcore-commands.auth_roles', -- commands must be allowed via the role config

View File

@@ -15,16 +15,12 @@ local Jail = require 'modules.control.jail' --- @dep modules.control.jail
local Colors = require 'resources.color_presets' --- @dep resources.color_presets
local format_chat_player_name = ext_require('expcore.common','format_chat_player_name') --- @dep expcore.common
Gui.require_concept('button')
local action_player_store = 'gui.left.player-list.action-player'
local action_name_store = 'gui.left.player-list.action-name'
-- common style used by all action buttons
local function tool_button_style(properties,parent,element)
element.style = 'tool_button'
local style = element.style
style.padding = -1
local function tool_button_style(style)
Gui.set_padding_style(style,-1,-1,-1,-1)
style.height = 28
style.width = 28
end
@@ -59,12 +55,11 @@ end
--- Teleports the user to the action player
-- @element goto_player
local goto_player =
Gui.new_concept('button')
:set_sprite('utility/export')
Gui.new_button()
:set_sprites('utility/export')
:set_tooltip{'player-list.goto-player'}
:define_draw(tool_button_style)
:on_click(function(event)
local player = event.player
:set_style('tool_button',tool_button_style)
:on_click(function(player,element)
local action_player_name = get_action_player_name(player)
local action_player = Game.get_player_from_any(action_player_name)
if not player.character or not action_player.character then
@@ -77,12 +72,11 @@ end)
--- Teleports the action player to the user
-- @element bring_player
local bring_player =
Gui.new_concept('button')
:set_sprite('utility/import')
Gui.new_button()
:set_sprites('utility/import')
:set_tooltip{'player-list.bring-player'}
:define_draw(tool_button_style)
:on_click(function(event)
local player = event.player
:set_style('tool_button',tool_button_style)
:on_click(function(player,element)
local action_player_name = get_action_player_name(player)
local action_player = Game.get_player_from_any(action_player_name)
if not player.character or not action_player.character then
@@ -95,12 +89,11 @@ end)
--- Kills the action player, if there are alive
-- @element kill_player
local kill_player =
Gui.new_concept('button')
:set_sprite('utility/too_far')
Gui.new_button()
:set_sprites('utility/too_far')
:set_tooltip{'player-list.kill-player'}
:define_draw(tool_button_style)
:on_click(function(event)
local player = event.player
:set_style('tool_button',tool_button_style)
:on_click(function(player,element)
local action_player_name = get_action_player_name(player)
local action_player = Game.get_player_from_any(action_player_name)
if action_player.character then
@@ -113,12 +106,11 @@ end)
--- Reports the action player, requires a reason to be given
-- @element report_player
local report_player =
Gui.new_concept('button')
:set_sprite('utility/spawn_flag')
Gui.new_button()
:set_sprites('utility/spawn_flag')
:set_tooltip{'player-list.report-player'}
:define_draw(tool_button_style)
:on_click(function(event)
local player = event.player
:set_style('tool_button',tool_button_style)
:on_click(function(player,element)
local action_player_name = get_action_player_name(player)
if Reports.is_reported(action_player_name,player.name) then
player.print({'expcom-report.already-reported'},Colors.orange_red)
@@ -138,12 +130,11 @@ end
--- Gives the action player a warning, requires a reason
-- @element warn_player
local warn_player =
Gui.new_concept('button')
:set_sprite('utility/spawn_flag')
Gui.new_button()
:set_sprites('utility/spawn_flag')
:set_tooltip{'player-list.warn-player'}
:define_draw(tool_button_style)
:on_click(function(event)
local player = event.player
:set_style('tool_button',tool_button_style)
:on_click(function(player,element)
Store.set(action_name_store,player.name,'command/give-warning')
end)
@@ -157,12 +148,11 @@ end
--- Jails the action player, requires a reason
-- @element jail_player
local jail_player =
Gui.new_concept('button')
:set_sprite('utility/item_editor_icon')
Gui.new_button()
:set_sprites('utility/item_editor_icon')
:set_tooltip{'player-list.jail-player'}
:define_draw(tool_button_style)
:on_click(function(event)
local player = event.player
:set_style('tool_button',tool_button_style)
:on_click(function(player,element)
local action_player_name,action_player_name_color = get_action_player_name(player)
if Jail.is_jailed(action_player_name) then
player.print({'expcom-jail.already-jailed',action_player_name_color},Colors.orange_red)
@@ -181,12 +171,11 @@ end
--- Temp bans the action player, requires a reason
-- @element temp_ban_player
local temp_ban_player =
Gui.new_concept('button')
:set_sprite('utility/clock')
Gui.new_button()
:set_sprites('utility/clock')
:set_tooltip{'player-list.temp-ban-player'}
:define_draw(tool_button_style)
:on_click(function(event)
local player = event.player
:set_style('tool_button',tool_button_style)
:on_click(function(player,element)
local action_player_name,action_player_name_color = get_action_player_name(player)
if Jail.is_jailed(action_player_name) then
player.print({'expcom-jail.already-banned',action_player_name_color},Colors.orange_red)
@@ -205,12 +194,11 @@ end
--- Kicks the action player, requires a reason
-- @element kick_player
local kick_player =
Gui.new_concept('button')
:set_sprite('utility/warning_icon')
Gui.new_button()
:set_sprites('utility/warning_icon')
:set_tooltip{'player-list.kick-player'}
:define_draw(tool_button_style)
:on_click(function(event)
local player = event.player
:set_style('tool_button',tool_button_style)
:on_click(function(player,element)
Store.set(action_name_store,player.name,'command/kick')
end)
@@ -222,12 +210,11 @@ end
--- Bans the action player, requires a reason
-- @element ban_player
local ban_player =
Gui.new_concept('button')
:set_sprite('utility/danger_icon')
Gui.new_button()
:set_sprites('utility/danger_icon')
:set_tooltip{'player-list.ban-player'}
:define_draw(tool_button_style)
:on_click(function(event)
local player = event.player
:set_style('tool_button',tool_button_style)
:on_click(function(player,element)
Store.set(action_name_store,player.name,'command/ban')
end)

View File

@@ -1,71 +1,284 @@
--[[-- 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
@usage-- Making the base button concept
local button =
Gui.new_concept() -- Make a new empty concept
:save_as('button') -- Save it as Gui.concepts.button so it can be used in other files
:new_event('on_click',defines.events.on_gui_click) -- Add an on click event for this concept
:new_property('tooltip') -- Add a property with the default setter method called tooltip
:new_property('caption',function(properties,value) -- Add a property with a custom setter method called caption
properties.caption = value
properties.sprite = nil
properties.type = 'button'
end)
:new_property('sprite',function(properties,value) -- Add a property with a custom setter method called sprite
properties.image = value
properties.caption = nil
properties.type = 'sprite-button'
end)
:define_draw(function(properties,parent,element) -- Add the draw function to create the element from the concept
-- Properties will include all the information that you need to draw the element
-- Parent is the parent element for the element, this may have been altered by previous draw functions
-- Element is the current element being made, this may have a nil value, if it is nil then this is the first draw function
if properties.type == 'button' then
element = parent.add{
type = properties.type,
name = properties.name,
caption = properties.caption,
tooltip = properties.tooltip
}
else
element = parent.add{
type = properties.type,
name = properties.name,
sprite = properties.sprite,
tooltip = properties.tooltip
}
end
-- If you return element or parent then their values will be updated for the next draw function in the chain
-- It is best practice to always return the values if you have made any changes to them
return element, parent
end)
@usage-- Making a new button which has a custom style
local custom_button =
Gui.new_concept('button') -- We can use button here since we used save as on the concept
-- button:clone() -- If we had not used save as then this is how we would use it as a base
:set_caption('Custom Button') -- Set the caption of the concept, this is possible as we added caption as a property
:set_tooltip('Only admins can press this button') -- Set the tooltip of the concept, this is possible as we added tooltip as a property
:on_click(function(event) -- Register a handler to the click event we added with new event
if not event.player.admin then
event.player.print('You must be admin to use this button')
end
end)
:new_event('on_admin_clicked',defines.events.on_gui_click,function(event) -- Add a click event which has a filter function
return event.player.admin -- Check if the player is admin
end)
:on_admin_clicked(function(event) -- Register a handler to the admin click event we have just created
-- The admin click event is only an example, because of how sinmple the filter is we could have just used an if else statement
game.print(event.player.name..' pressed my admin button')
end)
@usage-- Drawing a concept
custom_button:draw(game.player.gui.left)
]]
return require 'expcore.gui.core'
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(name,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

View File

@@ -1,90 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Gui
]]
local Gui = require 'expcore.gui.core'
--[[-- Clickable elements that fire on_gui_click when clicked.
@element button
@param on_click fired when the player clicks the button
@param on_left_click fired when the player clicks with the left mouse button
@param on_left_click fired when the player clicks with the right mouse button
@tparam ?string|Concepts.LocalisedString caption the message that is shown on the button
@tparam ?string|Concepts.LocalisedString tooltip the tooltip that shows when a player hovers over the button
@tparam Concepts.SpritePath sprite upto three sprites in the order: default, hovered, clicked
@usage-- Making a basic button
local basic_button =
Gui.new_concept('button')
:set_caption('Basic Button')
:set_tooltip('Basic button')
:on_click(function(event)
event.player.print('You pressed basic button!')
end)
@usage-- Making a sprite button
local sprite_button =
Gui.new_concept('button')
:set_sprite('utility/warning_icon')
:set_tooltip('Sprite button')
:on_click(function(event)
event.player.print('You pressed sprite button!')
end)
]]
Gui.new_concept()
:save_as('button')
-- Events
:new_event('on_click',defines.events.on_gui_click)
:new_event('on_left_click',defines.events.on_gui_click,function(event)
return event.mouse_button == defines.mouse_button_type.left
end)
:new_event('on_right_click',defines.events.on_gui_click,function(event)
return event.mouse_button == defines.mouse_button_type.right
end)
-- Properties
:new_property('tooltip')
:new_property('caption',function(properties,value)
properties.caption = value
properties.type = 'button'
end)
:new_property('sprite',function(properties,value,hovered_sprite,clicked_sprite)
properties.sprite = value
properties.hovered_sprite = hovered_sprite
properties.clicked_sprite = clicked_sprite
properties.type = 'sprite-button'
end)
-- Draw
:define_draw(function(properties,parent,element)
-- Check if it should be a sprite button
if properties.type == 'sprite-button' then
-- Draw a sprite button
element = parent.add{
name = properties.name,
type = 'sprite-button',
sprite = properties.sprite,
hovered_sprite = properties.hovered_sprite,
clicked_sprite = properties.clicked_sprite,
tooltip = properties.tooltip
}
else
-- Draw a button
element = parent.add{
name = properties.name,
type = 'button',
caption = properties.caption,
tooltip = properties.tooltip
}
end
return element
end)

View File

@@ -0,0 +1,198 @@
--[[-- 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,62 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Gui
]]
local Gui = require 'expcore.gui.core'
--[[-- Clickable elements with a cross in the middle that can be turned off or on.
@element checkbox
@param on_state_changed fired when the state of the element is changed
@tparam ?string|Concepts.LocalisedString caption the message that is shown next to the checkbox
@tparam ?string|Concepts.LocalisedString tooltip the tooltip that shows when a player hovers over the checkbox
@tparam ?boolean|function default the default state of this checkbox, or a function which returns the default state
@tparam boolean use_radio setting to true will use radio buttons rather than checkboxs
@usage-- Making a basic checkbox
local basic_checkbox =
Gui.new_concept('checkbox')
:set_caption('Basic Checkbox')
:set_tooltip('Basic checkbox')
:on_state_changed(function(event)
event.player.print('Basic checkbox is now: '..tostring(event.element.state))
end)
]]
Gui.new_concept()
:save_as('checkbox')
-- Events
:new_event('on_state_changed',defines.events.on_gui_checked_state_changed)
-- Properties
:new_property('tooltip')
:new_property('caption')
:new_property('default',nil,false)
:new_property('use_radio',nil,false)
-- Draw
:define_draw(function(properties,parent,element)
local default = properties.default
local state = type(default) == 'boolean' and default
-- Draw a checkbox
element = parent.add{
name = properties.name,
type = properties.use_radio and 'radiobutton' or 'checkbox',
caption = properties.caption,
tooltip = properties.tooltip,
state = state
}
-- Set the default state
default = Gui.resolve_property(default,element)
if default and default ~= state then
element.state = default
end
return element
end)

View File

@@ -1,169 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Gui
]]
local Gui = require 'expcore.gui.core'
local array_insert = ext_require('expcore.common','array_insert')
--[[-- A drop down list of other elements.
@element dropdown
@param on_selection_changed fired when the selected value is changed
@tparam ?string|Concepts.LocalisedString|function default the option which is selected by default, or a function which returns the default
@tparam boolean use_list_box when true a list box will be used rather than a dropdown menu
@tparam ?nil|table static_items when called with a table the values will be added as items for the dropdown, if called with nil then all items are cleared
@tparam function dynamic_items the given function will be called to return a list of items and optional start index to add items to the dropdown when it is first drawn
@usage-- Making a basic dropdown
local static_dropdown =
Gui.new_concept('dropdown')
:set_static_items{'Option 1','Option 2','Option 3'}
:on_selection_changed(function(event)
local value = Gui.get_dropdown_value(event.element)
event.player.print('Static dropdown is now: '..value)
end)
@usage-- Making a dropdown with dynamic items, example is name of online players
local dynamic_dropdown =
Gui.new_concept('dropdown')
:set_dynamic_items(function(element)
local items = {}
for _,player in pairs(game.connected_players) do
items[#items+1] = player.name
end
return items
end)
:on_selection_changed(function(event)
local value = Gui.get_dropdown_value(event.element)
event.player.print('Dynamic dropdown is now: '..value)
end)
]]
Gui.new_concept()
:save_as('dropdown')
-- Events
:new_event('on_selection_changed',defines.events.on_gui_selection_state_changed)
-- Properties
:new_property('default')
:new_property('use_list_box',nil,false)
:new_property('static_items',function(properties,value,start_index)
-- Clear all items if value is nil
if not value then
properties.items = {}
end
-- Convert value to a table
if type(value) ~= 'table' then
value = {value}
end
-- If there are no items then set and return
local items = properties.items
if not items then
properties.items = value
return
end
-- Otherwise insert into the array
array_insert(items,start_index,value)
end)
:new_property('dynamic_items',function(properties,value)
-- Check that a function value was given
if type(value) ~= 'function' then
error('Dynamic items must be a function')
end
-- If no dynamic items then set and return
local items = properties.dynamic_items
if not items then
properties.dynamic_items = {value}
return
end
-- Otherwise append to the end
items[#items+1] = value
end)
-- Draw
:define_draw(function(properties,parent,element)
-- Draw a dropdown
element = parent.add{
name = properties.name,
type = properties.use_list_box and 'list-box' or 'drop-down',
items = properties.items
}
-- If there are dynamic items then add them
if properties.dynamic_items then
for _,callback in pairs(properties.dynamic_items) do
local dynamic_items, start_index = callback(element)
Gui.add_dropdown_items(element,start_index,dynamic_items)
end
end
-- If there is a default, select it
local default = Gui.resolve_property(properties.default,element)
if default then
Gui.set_dropdown_value(element,default)
end
return element
end)
--- Dropdowns.
-- functions used with dropdowns
-- @section dropdowns
--[[-- Selects the index of a dropdown with this value
@tparam LuaGuiElement element the dropdown that you want to set the selection for
@tparam ?string|Concepts.LocalisedString value the value that you want selected
@treturn boolean if an item with this value was found
@usage-- Selecting the item with the value 'foo'
Gui.set_dropdown_value(element,'foo')
]]
function Gui.set_dropdown_value(element,value)
for index, item in pairs(element.items) do
if item == value then
element.selected_index = index
return true
end
end
return false
end
--[[-- Gets the selected item value of a dropdown
@tparam LuaGuiElement element the dropdown that you want the selected value of
@treturn ?string|Concepts.LocalisedString the value that is currently selected
@usage-- Getting the selected value
local selected_value = Gui.get_dropdown_value(element)
]]
function Gui.get_dropdown_value(element)
return element.items[element.selected_index]
end
--[[-- Adds the given items into the list of items for this dropdown
@tparam LuaGuiElement element the dropdown that you want to add the items to
@tparam[opt] number start_index the index at which the items will be added, if not given appened to the end
@tparam table new_items the list of new items that you want to add
@treturn table the list of items that the element now has
@usage-- Add the items 'foo' and 'bar' to the end
Gui.add_dropdown_items(element,{'foo','bar'})
@usage-- Add the items 'foo' and 'bar' to the start
Gui.add_dropdown_items(element,1,{'foo','bar'})
]]
function Gui.add_dropdown_items(element,start_index,new_items)
if not new_items then
new_items = start_index
start_index = nil
end
local items = element.items
element.items = array_insert(items,start_index,new_items)
return items
end

View File

@@ -1,51 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Gui
]]
local Gui = require 'expcore.gui.core'
--[[-- A button that lets the player pick one of an: item, entity, tile, or signal similar to the filter-select window.
@element elem_button
@param on_selection_changed fired when the selected value is changed
@tparam ?string|Concepts.SignalID|function default the option which is selected by default, or a function which returns the default
@tparam string elem_type the type of elem selection that this is, default is item selection
@usage-- Making a basic elem button
local basic_elem_button =
Gui.new_concept('elem_button')
:on_selection_changed(function(event)
event.player.print('Basic elem button is now: '..event.element.elem_value)
end)
]]
Gui.new_concept()
:save_as('elem_button')
-- Events
:new_event('on_selection_changed',defines.events.on_gui_elem_changed)
-- Properties
:new_property('default')
:new_property('elem_type',nil,'item')
-- Draw
:define_draw(function(properties,parent,element)
-- Draw a chose elem button
element = parent.add{
name = properties.name,
type = 'choose-elem-button',
elem_type = properties.elem_type
}
-- If there is a default, select it
local default = Gui.resolve_property(properties.default,element)
if default then
element.elem_value = default
end
return element
end)

View File

@@ -1,36 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Gui
]]
local Gui = require 'expcore.gui.core'
--[[-- A empty widget that just exists. The root GUI element screen is an empty-widget.
@element empty
@tparam string style the style that the element will have
@usage-- Making a draggable space styled widget
local draggable_space =
Gui.new_concept('empty')
:set_style('draggable_space')
]]
Gui.new_concept()
:save_as('empty')
-- Properties
:new_property('style')
-- Draw
:define_draw(function(properties,parent,element)
-- Draw an empty widget
element = parent.add{
name = properties.name,
type = 'empty-widget',
style = properties.style
}
return element
end)

View File

@@ -1,42 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Gui
]]
local Gui = require 'expcore.gui.core'
--[[-- Invisible containers that lay out children either horizontally or vertically. The root GUI elements (top, left and center; see LuaGui) are flows.
@element flow
@tparam string direction the direction that children will be added
@usage-- Making a basic flow, contains a label with hello world
local basic_flow =
Gui.new_concept('flow')
:define_draw(function(properties,parent,element)
element.add{
type = 'label',
caption = 'Hello, World!'
}
end)
]]
Gui.new_concept()
:save_as('flow')
-- Properties
:new_property('direction')
-- Draw
:define_draw(function(properties,parent,element)
-- Draw a flow
element = parent.add{
name = properties.name,
type = 'flow',
caption = properties.title,
direction = properties.direction
}
return element
end)

View File

@@ -1,45 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Gui
]]
local Gui = require 'expcore.gui.core'
--[[-- Grey semi-transparent boxes that contain other elements. They have a caption, and, just like flows, they lay out children either horizontally or vertically.
@element frame
@tparam ?string|Concepts.LocalisedString title the title that will show in the frame
@tparam string direction the direction that children will be added
@usage-- Making a basic frame, contains a label with hello world
local basic_frame =
Gui.new_concept('frame')
:set_title('Basic Frame')
:define_draw(function(properties,parent,element)
element.add{
type = 'label',
caption = 'Hello, World!'
}
end)
]]
Gui.new_concept()
:save_as('frame')
-- Properties
:new_property('title')
:new_property('direction')
-- Draw
:define_draw(function(properties,parent,element)
-- Draw a frame
element = parent.add{
name = properties.name,
type = 'frame',
caption = properties.title,
direction = properties.direction
}
return element
end)

View File

@@ -1,46 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Gui
]]
local Gui = require 'expcore.gui.core'
--[[-- A piece of text.
@element label
@tparam ?string|Concepts.LocalisedString caption the caption that will show in the label
@tparam ?string|Concepts.LocalisedString description the description that will show on the label
@tparam defines.rich_text_setting rich_text how this element handles rich text
@usage-- Making a basic label
local basic_label =
Gui.new_concept('label')
:set_caption('Hello, World!')
]]
Gui.new_concept()
:save_as('label')
-- Properties
:new_property('caption')
:new_property('description')
:new_property('rich_text')
-- Draw
:define_draw(function(properties,parent,element)
-- Draw a label
element = parent.add{
name = properties.name,
type = 'label',
caption = properties.caption,
description = properties.description
}
-- Change rich text setting if present
local rich_text = properties.rich_text
if rich_text then
element.style.rich_text_setting = rich_text
end
return element
end)

View File

@@ -0,0 +1,322 @@
--[[-- 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,35 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Gui
]]
local Gui = require 'expcore.gui.core'
--[[-- A vertical or horizontal line.
@element line
@tparam string direction the direction that children will be added
@usage-- Making a basic frame, contains a label with hello world
local basic_line =
Gui.new_concept('line')
]]
Gui.new_concept()
:save_as('line')
-- Properties
:new_property('direction')
-- Draw
:define_draw(function(properties,parent,element)
-- Draw a line
element = parent.add{
name = properties.name,
type = 'line',
direction = properties.direction
}
return element
end)

View File

@@ -0,0 +1,230 @@
--[[-- 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,168 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Gui
]]
local Gui = require 'expcore.gui.core'
--[[-- Indicate progress by displaying a partially filled bar.
@element progress_bar
@param on_completion fired when increment reaches the maxium value set by set_maximum
@tparam ?string|Concepts.LocalisedString tooltip the tooltip that will show for this element
@tparam number maximum the maxium amount an instance can be increased, default 100
@tparam boolean delay_completion when true the progress will be completed untill after the maximum rather than at the maximum
@tparam boolean inverted although this will NOT effect how you use the functions it will make the element start full and reduce as you call increase, note issues with 0 detections
@usage-- Making a basic progress bar, will increase when pressed then will reset when full
local basic_progress_bar =
Gui.new_concept('progress_bar')
:set_tooltip('Basic progress bar')
:set_maximum(5)
:new_event('on_click',defines.events.on_gui_click)
:on_click(function(event)
event.concept:increment(event.element)
end)
:set_delay_completion(true)
:on_completion(function(event)
event.concept:reset(event.element)
end)
]]
local progress_bar =
Gui.new_concept()
:save_as('progress_bar')
-- Events
:new_event('on_completion')
-- Properties
:new_property('tooltip')
:new_property('maximum',nil,100)
:new_property('delay_completion',nil,false)
:new_property('inverted',nil,false)
-- Draw
:define_draw(function(properties,parent,element)
-- Draw a progress bar
element = parent.add{
name = properties.name,
tooltip = properties.tooltip,
type = 'progressbar',
value = properties.inverted and 1 or 0
}
return element
end)
--- Progress Bars.
-- functions used with progress bars
-- @section progress_bars
-- logic for changing the value of a progress bar
local function raw_update(concept,element,amount)
local old_value = element.value
local new_value = old_value + amount
element.value = new_value
local check_value = concept.properties.delay_completion and old_value or new_value
if amount < 0 and check_value <= 0 or amount > 0 and check_value >= 1 then
concept:raise_event('on_completion',{
element = element
})
else
return new_value
end
end
--[[-- Will increase the progress of a progress bar based on this concept, if the concept has an instance store then element acts as the category, if you have a combined store it will NOT update all instances
@tparam ?LuaGuiElement|string element either the element that is changed or the category that is being changed (only if an instance store is defined)
@tparam[opt=1] number amount the amount that will bar will increase, note that this amount must be less than the max
@treturn ?number|nil the new value of the element, use this to sync a data store, if the return is nil then either a instance store was used or the new value may have changed
@usage-- Incrementing progress bar with no instance store
local new_value = progress_bar:increment(element)
@usage-- Incrementing progress bar with an instance store
progress_bar:increment(category)
]]
function progress_bar:increment(element,amount)
local properties = self.properties
local inverted = properties.inverted
local maximum = properties.maximum
amount = amount and amount/maximum or 1/maximum
amount = inverted and -amount or amount
if self.instance_store and not self.sync_instance then
self.update_instances(element,function(next_element)
raw_update(self,next_element,amount)
end)
else
return raw_update(self,element,amount)
end
end
--[[-- Will decrease the progress of a progress bar based on this concept, if the concept has an instance store then element acts as the category, if you have a combined store it will NOT update all instances
@tparam ?LuaGuiElement|string element either the element that is changed or the category that is being changed (only if an instance store is defined)
@tparam[opt=1] number amount the amount that will bar will decrease, note that this amount must be less than the max
@treturn number the new value of the element, use this to sync a data store, if the return is nil then either a instance store was used or the new value may have changed
@usage-- Decrementing progress bar with no instance store
local new_value = progress_bar:decrement(element)
@usage-- Decrementing progress bar with an instance store
progress_bar:decrement(category)
]]
function progress_bar:decrement(element,amount)
self:increment(element,-amount)
end
--[[-- Resets the progress back to 0% for this element, if the concept has an instance store then element acts as the category, if you have a combined store it will NOT update all instances
@tparam ?LuaGuiElement|string element either the element that is changed or the category that is being changed (only if an instance store is defined)
@treturn ?number|nil the new value of the element, use this to sync a data store, if the return is nil then either a instance store was used or the new value may have changed
@usage-- Reseting a progress bar with no instance store
local new_value = progress_bar:reset(element)
@usage-- Reseting a progress bar with an instance store
progress_bar:reset(category)
]]
function progress_bar:reset(element)
local new_value = self.properties.inverted and 1 or 0
if self.instacne_store and not self.sync_instance then
self.update_instances(element,function(next_element)
next_element.value = new_value
end)
else
element.value = new_value
return new_value
end
end
--[[-- Increment any progress bar by the given percentage
@tparam LuaGuiElement element the progress bar that you want to update
@tparam[opt=0.01] number amount the percentage that you want to increment the progress bar by
@treturn boolean true if the bar is now full
@usage-- Increment any progress bar by 10%
Gui.increment_progress_bar(element,0.1)
]]
function Gui.increment_progress_bar(element,amount)
amount = amount or 0.01
element.value = element.value + amount
return element.value >= 1
end
--[[-- Decrement any progress bar by the given percentage
@tparam LuaGuiElement element the progress bar that you want to update
@tparam[opt=0.01] number amount the percentage that you want to decrement the progress bar by
@treturn boolean true if the bar is now empty
@usage-- Decrement any progress bar by 10%
Gui.decrement_progress_bar(element,0.1)
]]
function Gui.decrement_progress_bar(element,amount)
amount = amount or 0.01
element.value = element.value - amount
return element.value <= 0
end

View File

@@ -1,47 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Gui
]]
local Gui = require 'expcore.gui.core'
--[[-- Similar to a flow but includes the ability to show and use scroll bars.
@element scroll
@tparam string horizontal_scroll the horizontal scroll policy for this scroll pane
@tparam string vertical_scroll the vertical scroll policy for this scroll pane
@usage-- Making a basic flow, contains a label with hello world
local basic_scroll =
Gui.new_concept('scroll')
:define_draw(function(properties,parent,element)
element.style.hieght = 50
for i = 1,10 do
element.add{
type = 'label',
caption = i
}
end
end)
]]
Gui.new_concept()
:save_as('scroll')
-- Properties
:new_property('horizontal_scroll')
:new_property('vertical_scroll')
-- Draw
:define_draw(function(properties,parent,element)
-- Draw a scroll pane
element = parent.add{
name = properties.name,
type = 'scroll-pane',
horizontal_scroll_policy = properties.horizontal_scroll,
vertical_scroll_policy = properties.vertical_scroll
}
return element
end)

View File

@@ -1,91 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Gui
]]
local Gui = require 'expcore.gui.core'
--[[-- A number picker.
@element slider
@param on_value_changed fired when the value of the slider is changed
@tparam number value_step the minimum amount by which the value of the slider can be changed
@tparam ?number|function default the default value of the slider or a function which returns the default value
@tparam boolean discrete_slider makes this slider a discrete slider, this means that the slider button will stop at the same interval as the values do
@tparam ?number|function range accepts two params the minimum and the maximum for this slider, or a single function to return both
@usage-- Making a basic slider
local basic_slider =
Gui.new_concept('slider')
:set_range(1,10)
:on_value_changed(function(event)
event.player.print('Basic slider is now: '..event.element.slider_value)
end)
@usage-- Making a discrete_slider
local discrete_slider =
Gui.new_concept('slider')
:set_range(1,10)
:set_value_step(1)
:set_discrete_slider(true)
:on_value_changed(function(event)
event.player.print('Interval slider is now: '..event.element.slider_value)
end)
]]
Gui.new_concept()
:save_as('slider')
-- Events
:new_event('on_value_changed',defines.events.on_gui_value_changed)
-- Properties
:new_property('value_step')
:new_property('default')
:new_property('discrete_slider',nil,false)
:new_property('range',function(properties,minimum,maximum)
if type(minimum) == 'function' then
properties.range = minimum
else
properties.minimum = minimum
properties.maximum = maximum
end
end)
-- Draw
:define_draw(function(properties,parent,element)
local default = properties.default
local value = type(default) == 'number' and default
local value_step = properties.value_step
-- Draw a slider
element = parent.add{
name = properties.name,
type = 'slider',
caption = properties.caption,
minimum_value = properties.minimum,
maximum_value = properties.maximum,
discrete_slider = properties.discrete_slider,
discrete_values = value_step ~= nil,
value_step = value_step,
value = value
}
-- Find the range for the slider and set it
local min, max = Gui.resolve_property(properties.range,element)
if min or max then
min = min or element.get_slider_minimum()
max = max or element.get_slider_maximum()
element.set_slider_minimum_maximum(min,max)
end
-- If there is a default, select it
default = Gui.resolve_property(default,element)
if default and default ~= value then
element.slider_value = default
end
return element
end)

View File

@@ -1,58 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Gui
]]
local Gui = require 'expcore.gui.core'
--[[-- An invisible container that lays out children in a specific number of columns. Column width is given by the largest element contained in that row.
@element table
@tparam ?number|function column_count the column count of the table or a function that returns the count being given then parent element
@tparam boolean vertical_lines when true vertical lines will be drawn on the table
@tparam boolean horizontal_lines when true horizontal lines will be drawn on the table
@tparam boolean header_lines when true horizontal lines will be drawn under the first row
@tparam boolean vertical_centering when true element will be vertically centered with in the table
@usage-- Making a basic table, contains 25 labels
local basic_table =
Gui.new_concept('table')
:set_column_count(5)
:define_draw(function(properties,parent,element)
for i = 1,25 do
element.add{
type = 'lable',
caption = i
}
end
end)
]]
Gui.new_concept()
:save_as('table')
-- Properties
:new_property('column_count')
:new_property('vertical_lines')
:new_property('horizontal_lines')
:new_property('header_lines')
:new_property('vertical_centering')
-- Draw
:define_draw(function(properties,parent,element)
local column_count = Gui.resolve_property(properties.column_count,parent)
-- Draw a table
element = parent.add{
name = properties.name,
type = 'table',
column_count = column_count,
draw_vertical_lines = properties.vertical_lines,
draw_horizontal_lines = properties.horizontal_lines,
draw_horizontal_line_after_headers = properties.header_lines,
vertical_centering = properties.vertical_centering
}
return element
end)

View File

@@ -1,84 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Gui
]]
local Gui = require 'expcore.gui.core'
--[[-- A multi-line text box that supports selection and copy-paste.
@element text_box
@param on_text_changed fired when the text within the text box is changed
@tparam ?string|Concepts.LocalisedString tooltip the tooltip that shows when a player hovers over the text box
@tparam ?string|function default the default text that will appear in the text box, or a function that returns it
@tparam defines.rich_text_setting rich_text how this element handles rich text
@tparam boolean clear_on_rmb if the text box will be cleared and forcused on a right click
@tparam boolean is_selectable when true the text inside the box can be selected
@tparam boolean has_word_wrap when true the text will wrap onto the next line if it reachs the end
@tparam boolean is_read_only when true the text inside the box can not be edited by the player
@usage-- Making a text box
local basic_text_box =
Gui.new_concept('text_box')
:set_default('I am the text that will show in the text box')
@usage-- Making a text box which can be edited
local editible_text_box =
Gui.new_concept('text_box')
:set_is_read_only(false)
:set_default('I am the text that will show in the text box')
:on_confirmation(function(event)
event.player.print('Editible text box is now: '..event.element.text)
end)
]]
Gui.new_concept()
:save_as('text_box')
-- Events
:new_event('on_text_changed',defines.events.on_gui_text_changed)
-- Properties
:new_property('tooltip')
:new_property('default')
:new_property('rich_text')
:new_property('clear_on_rmb',nil,false)
:new_property('is_selectable',nil,true)
:new_property('has_word_wrap',nil,true)
:new_property('is_read_only',nil,true)
-- Draw
:define_draw(function(properties,parent,element)
local default = properties.default
local text = type(default) == 'string' and default or nil
-- Draw a text box
element = parent.add{
name = properties.name,
type = 'text-box',
tooltip = properties.tooltip,
clear_and_focus_on_right_click = properties.clear_on_rmb,
text = text
}
-- Set options for text box
element.selectable = properties.is_selectable
element.word_wrap = properties.has_word_wrap
element.read_only = properties.is_read_only
-- If there is a default, set it
default = Gui.resolve_property(default,element)
if default and default ~= text then
element.text = default
end
-- Change rich text setting if present
local rich_text = properties.rich_text
if rich_text then
element.style.rich_text_setting = rich_text
end
return element
end)

View File

@@ -1,100 +0,0 @@
--[[-- Core Module - Gui
@module Gui
@alias Gui
]]
local Gui = require 'expcore.gui.core'
--[[-- Boxes of text the user can type in.
@element text_field
@param on_text_changed fired when the text within the text field is changed
@param on_confirmation fired when the player presses enter with the text field forcused
@tparam ?string|Concepts.LocalisedString tooltip the tooltip that shows when a player hovers over the text field
@tparam ?string|function default the default text that will appear in the text field, or a function that returns it
@tparam defines.rich_text_setting rich_text how this element handles rich text
@tparam boolean clear_on_rmb if the text field will be cleared and forcused on a right click
@tparam boolean lose_forcus if the text field will lose forcus after the confirmation event
@tparam boolean is_number if this text field contains a number value, can be ignored if is_decimal or is_negitive is used
@tparam boolean is_decimal if this text field contains a decimal value
@tparam boolean is_negative if this text field contains a negative value
@tparam boolean is_password if this text field contains a password value
@usage-- Making a text field
local basic_text_field =
Gui.new_concept('text_field')
:on_confirmation(function(event)
event.player.print('Basic text field is now: '..event.element.text)
end)
@usage-- Making a text field which will clear on right click and un forcus on confirmation
local better_text_field =
Gui.new_concept('text_field')
:set_clear_on_rmb(true)
:set_lose_forcus(true)
:on_confirmation(function(event)
event.player.print('Better text field is now: '..event.element.text)
end)
@usage-- Making a decimal input
local decimal_text_field =
Gui.new_concept('text_field')
:set_is_decimal(true)
:on_confirmation(function(event)
event.player.print('Decimal text field is now: '..event.element.text)
end)
]]
Gui.new_concept()
:save_as('text_field')
-- Events
:new_event('on_text_changed',defines.events.on_gui_text_changed)
:new_event('on_confirmation',defines.events.on_gui_confirmed)
-- Properties
:new_property('tooltip')
:new_property('default')
:new_property('rich_text')
:new_property('clear_on_rmb',nil,false)
:new_property('lose_forcus',nil,false)
:new_property('is_number',nil,false)
:new_property('is_decimal',nil,false)
:new_property('is_negative',nil,false)
:new_property('is_password',nil,false)
-- Draw
:define_draw(function(properties,parent,element)
local default = properties.default
local text = type(default) == 'string' and default or nil
-- Draw a text field
element = parent.add{
name = properties.name,
type = 'textfield',
tooltip = properties.tooltip,
clear_and_focus_on_right_click = properties.clear_on_rmb,
lose_focus_on_confirm = properties.lose_forcus,
numeric = properties.is_number or properties.is_decimal or properties.is_negative,
allow_decimal = properties.is_decimal,
allow_negative = properties.is_negative,
is_password = properties.is_password,
text = text
}
-- If there is a default, set it
default = Gui.resolve_property(default,element)
if default and default ~= text then
element.text = default
end
-- Change rich text setting if present
local rich_text = properties.rich_text
if rich_text then
element.style.rich_text_setting = rich_text
end
return element
end)

View File

@@ -0,0 +1,114 @@
--[[-- 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,293 +1,368 @@
--[[-- Core Module - Gui
@module Gui
@alias Gui
@alias Prototype
]]
local Game = require 'utils.game' -- @dep utils.game
local resolve_value = ext_require('expcore.common','resolve_value') -- @dep expcore.common
local Prototype = require 'expcore.gui.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
local Gui = {
concepts = {}
}
--[[
>>>> Basic useage with no defines
This module can be igroned if you are only wanting only event handlers as utils.gui adds the following:
--- Concept Control.
-- Functions that act as a landing point for the other funtions
-- @section concept-control
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
--[[-- Loads a concept from the concepts file
@tparam string concept_name the name of the concept to require
@usage-- Load a base concept
Gui.require_concept('frame') --- @dep Gui.concept.frame
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
]]
function Gui.require_concept(concept_name)
require('expcore.gui.concepts.'..concept_name)
end
local Gui = require 'utils.gui' --- @dep utils.gui
local Game = require 'utils.game' --- @dep utils.game
--[[-- Loads a set of concepts from the styles file
@tparam string style_name the name of the style to require
@usage-- Load a base style
Gui.require_concept('expgaming') --- @dep Gui.style.frame
]]
function Gui.require_style(style_name)
require('expcore.gui.styles.'..style_name..'.index')
end
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
--[[-- Gets a gui concept from name, id, or its self
@tparam ?string|number|table name the name, id, or the concept you want to get
@usage-- Getting a gui concept
local button = Gui.get_concept('Button')
]]
function Gui.get_concept(name)
if not name then return end
local vtype = type(name)
if vtype == 'string' then
return Gui.concepts[name]
elseif vtype == 'table' then
if name.draw_callbacks then
return name
--- 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
elseif vtype == 'number' then
for _,concept in pairs(Gui.concepts) do
if concept.name == name then
return concept
end
end
end
})
Gui.defines[define.name] = define
return define
end
--[[-- Used to save the concept to the main gui module to allow access from other files
@function Prototype:save_as
@tparam string save_name the new name of the concept
@usage-- Save a concept to allow access in another file
button:save_as('button')
@usage-- Access concept after being saved
Gui.concepts.button
Gui.get_concept('button')
]]
function Prototype:save_as(save_name)
Gui.concepts[save_name] = self
return self
end
--[[-- Returns a new gui concept, option to provide a base concept to copy properties and draw functions from
@tparam[opt] ?string|number|table base_concept the concept that you want to copy the details of
@usage-- Making a new button, see module usage
local button = Gui.new_concept('Button')
]]
function Gui.new_concept(base_concept)
if base_concept then
base_concept = Gui.get_concept(base_concept) or error('Invalid gui concept "'..tostring(base_concept)..'"',2)
return base_concept:clone()
else
return Prototype:clone()
end
end
--[[-- Used to draw a concept to a parent element
@tparam ?string|number|table concept the name of the concept that you want to draw
@tparam LuaGuiElement parent the element that will act as a parent for the new element
@treturn LuaGuiElement the element that was created
@usage-- Drawing a new element
Gui.draw_concept('Button',element)
]]
function Gui.draw_concept(concept,parent,...)
concept = Gui.get_concept(concept) or error('Invalid gui concept "'..tostring(concept)..'"',2)
return concept:draw(parent,...)
end
--- Element Control.
-- Functions that aim to making working with gui elements easier
-- @section element-control
--[[-- Gets the player who owns this element
@tparam LuaGuiElement element the element that you want to get the player of
@treturn LuaPlayer the player who owns this element
@usage-- Getting the player of an element
local player = Gui.get_player_from_element(element)
]]
function Gui.get_player_from_element(element)
return Game.get_player_by_index(element.player_index)
end
--[[-- Simple check for if an element is valid
@tparam LuaGuiElement element the element that you want to check is valid
@treturn boolean true if the element is valid
@usage-- Return if not valid
if not Gui.valid(element) then return end
]]
function Gui.valid(element)
return element and element.valid or false
end
--[[-- Destroies and element if it is valid
@tparam LuaGuiElement element the element that you want to destroy
@treturn boolean true if the element was valid and was destoried
@usage-- Destoring an element
Gui.destroy(element)
]]
function Gui.destroy(element)
if element and element.valid then
element.destroy()
return true
end
return false
end
--[[-- Finds and returns a gui element if it is valid from a long chain of element names or concepts
@tparam LuaGuiElement element the root element to start checking from
@tparam ?string|table ... element names or element concepts that point to your element
@treturn[1] boolean if the element was found, failed
@treturn[1] string the path of the element that the search stoped at
@treturn[2] boolean if the element was found, found
@treturn[2] LuaGuiElement the element that was found
@usage-- Getting the center gui
local exists, center = Gui.exists(player,'gui','center')
]]
function Gui.exists(element,...)
local path = tostring(element.name)
for _,next_element_name in pairs{...} do
if type(next_element_name) == 'table' then
next_element_name = next_element_name.name
end
element = element[next_element_name]
path = path..'.'..tostring(next_element_name)
if not Gui.valid(element) then
return false, path
--- 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
return true, element
end
--[[-- Checks if a gui element exists or not, if not found will throw an error
@see Gui.exists
@tparam LuaGuiElement element the root element to start checking from
@tparam ?string|table ... element names or element concepts that point to your element
@treturn LuaGuiElement the element that was found
@usage-- Getting the center gui
local exists, center = Gui.find(player,'gui','center')
]]
function Gui.find(element,...)
local exists, element = Gui.exists(element,...)
if not exists then
return error('Could not find element: '..element,2)
else
return element
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
--[[-- Toggles the enabled state of an element
@tparam LuaGuiElement element the element that you want to toggle the enabled state of
@treturn boolean the new enabled state of the element
@usage-- Toggle the enabled state of an element
Gui.toggle_enabled(element)
]]
function Gui.toggle_enabled(element)
if not element or not element.valid then return end
if not element.enabled then
element.enabled = true
return true
else
element.enabled = false
return false
end
end
--[[-- Toggles the visible state of an element
@tparam LuaGuiElement element the element that you want to toggle the visible state of
@treturn boolean the new visible state of the element
@usage-- Toggle the visible state of an element
Gui.toggle_visible(element)
]]
function Gui.toggle_visible(element)
if not element or not element.valid then return end
if not element.visible then
element.visible = true
return true
else
element.visible = false
return false
end
end
--[[-- Sets the padding for a gui element
@tparam LuaGuiElement element the element to set the padding for
@tparam[opt=0] ?number|boolean up the amount of padding on the top, true leaves unchanged
@tparam[opt=0] ?number|boolean down the amount of padding on the bottom, true leaves unchanged
@tparam[opt=0] ?number|boolean left the amount of padding on the left, true leaves unchanged
@tparam[opt=0] ?number|boolean right the amount of padding on the right, true leaves unchanged
@usage-- Remove all padding of an element
Gui.set_padding(element)
@usage-- Remove side padding but keep vertical padding
Gui.set_padding(element,true,true)
@usage-- Remove all padding but set right to 2
Gui.set_padding(element,false,false,false,2)
]]
function Gui.set_padding(element,up,down,left,right)
local style = element.style
style.top_padding = up == true and style.top_padding or up or 0
style.bottom_padding = down == true and style.top_padding or down or 0
style.left_padding = left == true and style.top_padding or left or 0
style.right_padding = right == true and style.top_padding or right or 0
end
--[[ Used to check a property exists and if it is a function then call the function
@function Gui.resolve_property
@tparam any value the value that you are testing exists and call if its a function
@tparam LuaGuiElement element the element that is passed to the function if it is a function
@treturn any the value or what it returns if it is a function
@usage-- Getting the default value
local default = Gui.resolve_property(properties.default,element)
if default then
element.value = default
end
]]
Gui.resolve_property = resolve_value
--- Store Categories.
-- Functions that are common types of categories
-- @section store-categories
--[[-- A categorize function to be used with add_store, each player has their own category
@tparam LuaGuiElement element the element that will be converted to a string
@treturn string the player's name who owns this element
@usage-- Storing data on a per player basis, can be used with instances
Gui.get_concept('CustomButton')
:define_data_store(Gui.categorize_by_player)
]]
--- 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 category
@tparam LuaGuiElement element the element that will be converted to a string
@treturn string the player's force name who owns this element
@usage-- Storing data on a per force basis, can be used with instances
Gui.get_concept('CustomButton')
:define_data_store(Gui.categorize_by_force)
]]
--- 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 category
@tparam LuaGuiElement element the element that will be converted to a string
@treturn string the player's surface name who owns this element
@usage-- Storing data on a per surface basis, can be used with instances
Gui.get_concept('CustomButton')
:define_data_store(Gui.categorize_by_surface)
]]
--- 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,128 @@
--[[-- 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

@@ -0,0 +1,252 @@
--[[-- 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(false,store_update),
add_sync_store = Prototype.store(true,store_update)
},
_prototype_radiobutton=Prototype.extend{
on_element_update = Prototype.event,
on_store_update = Prototype.event,
add_store = Prototype.store(false,store_update),
add_sync_store = Prototype.store(true,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 category = self.categorize and self.categorize(element) or nil
local state = self:get_store(category,true)
if state then element.state = true end
end
end)
Gui.on_checked_state_changed(self.name,function(event)
local element = event.element
if self.option_set then
local value = Checkbox.option_sets[self.option_set][element.name]
local category = self.categorize and self.categorize(element)
self:set_store(category,value)
elseif self.store then
local value = element.state
local category = self.categorize and self.categorize(element)
self:set_store(category,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 string name the name of the option set, must be unique
-- @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(name,callback,categorize)
Store.register(name,function(value,category)
local options = Checkbox.option_sets[name]
for opt_name,define_name in pairs(options) do
if Gui.defines[define_name] then
local define = Gui.get_define(define_name)
local state = opt_name == value
define:set_store(category,state,true)
end
end
callback(value,category)
end)
Checkbox.option_categorize[name] = categorize
Checkbox.option_sets[name] = {}
return name
end
--- Draws all radiobuttons that are part of an option set at once (Gui.draw will not work)
-- @tparam 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

@@ -0,0 +1,187 @@
--[[-- 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(false,store_update),
add_sync_store = Prototype.store(true,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 category = self.categorize and self.categorize(element) or nil
local value = self:get_store(category)
if value then Dropdown.select_value(element,value) end
end
end)
Gui.on_selection_state_changed(self.name,function(event)
local element = event.element
local value = Dropdown.get_selected_value(element)
if self.store then
local category = self.categorize and self.categorize(element) or value
self:set_store(category,value)
else
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

@@ -0,0 +1,99 @@
--[[-- 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(false,store_update),
add_sync_store = Prototype.store(true,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 category = self.categorize and self.categorize(element) or nil
local value = self:get_store(category)
if value then element.elem_value = value end
end
end)
Gui.on_elem_changed(self.name,function(event)
local element = event.element
local value = element.elem_value
if self.store then
local category = self.categorize and self.categorize(element) or value
self:set_store(category,value)
else
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

@@ -0,0 +1,390 @@
--[[-- 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(false,store_update),
add_sync_store = Prototype.store(true,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 category = self.categorize and self.categorize(element) or nil
local value = self:get_store(category)
if not value then
value = self.count_down and 1 or 0
self:set_store(category,value)
end
element.value = value
else
if self.count_down then
element.value = 1
end
if not ProgressBar.independent[self.name] then
ProgressBar.independent[self.name] = {}
end
table.insert(ProgressBar.independent[self.name],{
element = element,
maximum = maximum
})
end
end)
return self
end
--- Sets the maximum value that represents the end value of the progress bar
-- @tparam 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
local category = self.categorize and self.categorize(element) or value
self:set_store(category,value)
else
element.value = value
end
end
--- Event handler factory that counts up by 1 every time the event 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

@@ -0,0 +1,177 @@
--[[-- 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(false,store_update),
add_sync_store = Prototype.store(true,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 category = self.categorize and self.categorize(element) or nil
local value = self:get_store(category)
if value then element.slider_value = value end
end
if self.auto_label then
self:draw_label(element.parent)
end
end)
Gui.on_value_changed(self.name,function(event)
local element = event.element
local value = element.slider_value
if self.store then
local category = self.categorize and self.categorize(element) or value
self:set_store(category,value)
else
event_call(self,element,value)
end
end)
return self
end
--- Sets the range of a slider, if not used will use default values for a slider
-- @tparam[opt] 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
local category = self.categorize and self.categorize(element) or value
value = self:get_store(category) or 0
end
local new_element = element.add{
name=name,
type='label',
caption=tostring(math.round(value,2))
}
local categorise = self.categorise or Gui.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

@@ -0,0 +1,149 @@
--[[-- 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(false,store_update),
add_sync_store = Prototype.store(true,store_update)
},
_prototype_box=Prototype.extend{
on_element_update = Prototype.event,
on_store_update = Prototype.event,
add_store = Prototype.store(false,store_update),
add_sync_store = Prototype.store(true,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 category = self.categorize and self.categorize(element) or nil
local value = self:get_store(category)
if value then element.text = value end
end
end)
Gui.on_text_changed(self.name,function(event)
local element = event.element
local value = element.text
if self.store then
local category = self.categorize and self.categorize(element) or value
self:set_store(category,value)
else
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

235
expcore/gui/instances.lua Normal file
View File

@@ -0,0 +1,235 @@
--[[-- 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 categorise function which allows variants to be stored under one
name (like one for each force or player)
-- categorise works in the same way as store categorise
-- so the function will 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 categorise 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 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 Global = require 'utils.global' --- @dep utils.global
local Instances = {
categorise={},
data={}
}
Global.register(Instances.data,function(tbl)
Instances.data = tbl
end)
--- Returns if a instance group has a categorise function; must be registered
-- @tparam string name the name of the instance group
-- @treturn boolean true if there is a categorise function
function Instances.has_categories(name)
return type(Instances.categorise[name]) == 'function'
end
--- Returns if the given name is a registered instance group
-- @tparam 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.categorise[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 categorise function used to turn the element into a string
-- categorise param - element LuaGuiElement - the gui element to be turned into a string
-- categorise return - string - the category that the element will be added to like the player's name or force's name
-- @treturn string the name that was added so it can be used as a variable
function Instances.register(name,categorise)
if _LIFECYCLE ~= _STAGE.control then
return error('Can only be called during the control stage', 2)
end
if Instances.categorise[name] then
return error('Instances for '..name..' already exist.',2)
end
categorise = type(categorise) == 'function' and categorise or true
Instances.data[name] = {}
Instances.categorise[name] = categorise
return name
end
--- Adds an element to the instance group under the correct category; must be registered
-- @tparam 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.categorise[name] then
return error('Invalid name for instance group: '..name,2)
end
if Instances.has_categories(name) then
local category = Instances.categorise[name](element)
if not Instances.data[name][category] then Instances.data[name][category] = {} end
table.insert(Instances.data[name][category],element)
else
table.insert(Instances.data[name],element)
end
end
--- Gets all element instances without first removing any invalid ones; used internally and must be registered
-- @tparam 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 categorise function
-- @treturn table the table of element instances of which some may be invalid
function Instances.get_elements_raw(name,category)
if not Instances.categorise[name] then
return error('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 categorise 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.categorise[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 categorise = Instances.has_categories(name)
for key,element in pairs(elements) do
if not element or not element.valid then
elements[key] = nil
else
if categorise and callback then callback(element)
elseif category then category(element) end
end
end
return elements
end
Instances.get_elements = Instances.get_valid_elements
Instances.apply_to_elements = Instances.get_valid_elements
--- A version of add_element that does not require the group to be registered
-- @tparam 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

View File

@@ -3,721 +3,309 @@
@alias Prototype
]]
--- Concept Base.
-- Functions that are used to make concepts
-- @section concept-base
--- Prototype.
-- Used to create new gui prototypes see elements and concepts
-- @section prototype
local Event = require 'utils.event' -- @dep utils.event
local Store = require 'expcore.store' -- @dep expcore.store
local Game = require 'utils.game' -- @dep utils.game
local Token = require 'utils.token' -- @dep utils.token
--[[
>>>> 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 Factorio_Events = {}
local Prototype = {
draw_callbacks = {},
clone_callbacks = {},
properties = {},
factorio_events = {},
events = {}
}
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
--- Acts as a gernal handler for any factorio event
local function factorio_event_handler(event)
local element = event.element
local rasied_event = event.name
local factorio_event_concepts = Factorio_Events[rasied_event]
if element then
if not element.valid then return end
local concept = factorio_event_concepts[element.name]
if concept then
for event_name, factorio_event in pairs(concept.factorio_events) do
if rasied_event == factorio_event then
concept:raise_event(event_name,event,true)
end
end
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 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
local Constructor = {}
local Prototype = {}
--- 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)
end
else
for _,concept in pairs(factorio_event_concepts) do
for event_name, factorio_event in pairs(concept.factorio_events) do
if rasied_event == factorio_event then
concept:raise_event(event_name,event,true)
end
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
--[[-- Used to copy all the settings from one concept to another and removing links to the orginal
@tparam string concept_name the name of the new concept; must be unique
@treturn GuiConcept the base for building a custom gui
@usage-- Clones the base Button concept to make a alternative button
local custom_button =
Gui.get_concept(Gui.concepts.button):clone()
]]
function Prototype:clone()
local concept = table.deep_copy(self)
-- Replace name of the concept
local uid = tostring(Token.uid())
concept.name = uid
concept.debug_name = uid
concept.properties.name = uid
-- Remove all event handlers that were copied
concept.events = {}
for event_name,_ in pairs(self.events) do
concept.events[event_name] = {}
end
-- Remakes even handlers for factorio
for _,factorio_event in pairs(concept.factorio_events) do
Factorio_Events[factorio_event][concept.name] = concept
end
-- Remove all refrences to an instance store
if concept.instance_store then
concept.instance_store = nil
concept.get_instances = nil
concept.add_instance = nil
concept.update_instances = nil
end
-- Remove all refrences to a data store
if concept.data_store then
concept.data_store = nil
concept.get_data = nil
concept.set_data = nil
concept.clear_data = nil
concept.update_data = nil
concept.on_data_store_update = nil
concept.events.on_data_store_update = nil
end
-- Remove all refrences to a combined store
if concept.sync_instance then
concept.sync_instance = nil
concept.set_store_from_instance = nil
end
-- Loop over all the clone defines, element is updated when a value is returned
for _,clone_callback in pairs(concept.clone_callbacks) do
local success, rtn = pcall(clone_callback,concept)
if not success then
error('Gui clone handler error with '..concept.debug_name..':\n\t'..rtn)
end
end
return concept
end
--[[-- Use to add your own callbacks to the clone function, for example adding to a local table
@tparam function clone_callback the function which is called with the concept to have something done to it
@treturn table self to allow chaining
@usage-- Adding concept to a local table
local buttons = {}
local button =
Gui.get_concept('Button')
:define_clone(function(concept)
buttons[concept.name] = concept
end)
]]
function Prototype:define_clone(clone_callback)
-- Check that it is a function that is being added
if type(clone_callback) ~= 'function' then
error('Draw define must be a function',2)
end
-- Add the draw function
self.clone_callbacks[#self.clone_callbacks+1] = clone_callback
return self
end
--[[-- Used to save the concept to the main gui module to allow access from other files
@function Prototype:save_as
@tparam string save_name the new name of the concept
@usage-- Save a concept to allow access in another file
button:save_as('button')
@usage-- Access concept after being saved
Gui.concepts.button
Gui.get_concept('button')
]]
function Prototype:save_as(save_name)
-- over writen in core file
return self
end
--[[-- Sets a debug name that can be used with error handlers
@tparam string name the name that will be used in error messages
@treturn self to allow chaining
@usage-- Set the debug name
unsaved_concept:debug('Example button')
]]
function Prototype:debug(name)
self.debug_name = name
return self
end
--[[-- Adds a new event trigger to the concept which can be linked to a factorio event
@tparam string event_name the name of the event to add, must be unique, recomented to start with "on_"
@tparam[opt] defines.events factorio_event when given will fire the custom event when the factorio event is raised
@tparam[opt] function event_condition used to filter when a factorio event triggers the custom event; if the event contains a reference to an element then names are automatically filtered
@treturn GuiConcept to allow chaining of functions
@usage-- Adds an on_admin_clicked event to fire when ever an admin clicks the button
local custom_button =
Gui.get_concept('Button'):clone('CustomButton')
:new_event('on_admin_clicked',defines.events.on_gui_click,function(event)
return event.player.admin -- only raise custom event when an admin clicks the button
end)
]]
function Prototype:new_event(event_name,factorio_event,event_condition)
-- Check the event does not already exist
if self.events[event_name] then
error('Event is already defined',2)
end
--[[-- Adds a custom event handler, replace with the name of the event
@function Prototype:on_custom_event
@tparam function handler the function which will recive the event
@treturn GuiConcept to allow chaining of functions
@usage-- When an admin clicks the button a message is printed
local custom_button =
Gui.get_concept('CustomButton')
:on_admin_clicked(function(event)
game.print(event.player.name..' pressed my admin button')
end)
]]
-- Adds a handler table and the event handler adder, comment above not indented to look better in docs
self.events[event_name] = {}
self[event_name] = function(concept,handler)
if type(handler) ~= 'function' then
error('Event handler must be a function',2)
end
local handlers = concept.events[event_name]
handlers[#handlers+1] = handler
return concept
end
-- Adds the factorio event handler if this event is linked to one
if factorio_event then
self.factorio_events[event_name] = factorio_event
self.events[event_name].factorio_event_condition = event_condition
local factorio_event_concepts = Factorio_Events[factorio_event]
if not factorio_event_concepts then
factorio_event_concepts = {}
Factorio_Events[factorio_event] = factorio_event_concepts
Event.add(factorio_event,factorio_event_handler)
end
if not factorio_event_concepts[self.name] then
factorio_event_concepts[self.name] = self
end
end
return self
end
--[[-- Raises a custom event, folowing keys included automaticlly: concept, event name, game tick, player from player_index, element if valid
@tparam string event_name the name of the event that you want to raise
@tparam[opt={}] table event table containg data you want to send with the event, some keys already included
@tparam[opt=false] boolean from_factorio internal use, if the raise came from the factorio event handler
@usage-- Raising the custom event on_admin_clicked
local custom_button =
Gui.get_concept('CustomButton')
-- Note that this is an example and would not work due it expecting a valid element for event.element
-- this will however work fine if you can provide all expected keys, or its not linked to any factorio event
custom_button:raise_event('on_admin_clicked',{
player_index = game.player.index
})
]]
function Prototype:raise_event(event_name,event,from_factorio)
-- Check that the event exists
if not self.events[event_name] then
error('Event is not defined',2)
end
-- Setup the event table with automatic keys
event = event or {}
event.concept = self
event.name = event.name or event_name
event.tick = event.tick or game.tick
event.player = event.player_index and Game.get_player_by_index(event.player_index) or nil
if event.element and not event.element.valid then return end
-- Get the event handlers
local handlers = self.events[event_name]
-- If it is from factorio and the filter fails
if from_factorio and handlers.factorio_event_condition and not handlers.factorio_event_condition(event) then
return
end
-- Trigger every handler
for _,handler in ipairs(handlers) do
local success, err = pcall(handler,event)
if not success then
error('Gui event handler error with '..self.debug_name..'/'..event_name..':\n\t'..err)
end
end
end
--[[-- Adds a new property to the concept, such as caption, tooltip, or some custom property you want to control
@tparam string property_name the name of the new property, must be unique
@tparam[opt] function setter_callback this function is called when set is called, if not provided then key in concept.properties is updated to new value
@tparam[opt] any default use this as the default value, will call the setter callback if defined
@treturn GuiConcept to allow chaining of functions
@usage-- Adding caption, sprite, and tooltip to the base button concept
local button =
Gui.get_concept('Button')
:new_property('tooltip')
:new_property('caption',nil,function(properties,value)
properties.caption = value
properties.sprite = nil
properties.type = 'button'
end)
:new_property('sprite',nil,function(properties,value)
properties.image = value
properties.caption = nil
properties.type = 'sprite-button'
end)
]]
function Prototype:new_property(property_name,setter_callback,default,...)
-- Check that the property does not already exist
if self.properties[property_name] then
error('Property is already defined',2)
end
-- Check that setter is a function if present
if setter_callback and not type(setter_callback) == 'function' then
error('Setter callback must be a function')
end
--[[-- Sets a new value for a property, triggers setter method if provided, replace with property name
@function Prototype:set_custom_property
@tparam any value the value that you want to set for this property
@treturn GuiConcept to allow chaining of functions
@usage-- Setting the caption on the base button concept after a cloning
local custom_button =
Gui.get_concept('Button')
:set_caption('Default Button')
@usage-- In our examples CustomButton is cloned from Button, this means the caption property already exists
-- note that what ever values that properties have at the time of cloning are also copied
local custom_button =
Gui.get_concept('CustomButton')
:set_caption('Custom Button')
]]
self['set_'..property_name] = function(concept,value,...)
if setter_callback then
-- Call the setter method to update values if present
local success, err = pcall(setter_callback,concept.properties,value,...)
if not success then
error('Gui property handler error with '..concept.debug_name..'/'..property_name..':\n\t'..err)
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
-- Otherwise just update the key
concept.properties[property_name] = value
end
return concept
end
-- If a default value if given then set the default value
if default ~= nil then
self['set_'..property_name](self,default,...)
end
return self
end
--[[-- Used to define how the concept is turned into an ingame element or "instance" as we may refer to them
@tparam function draw_callback the function that will be called to draw/update the instance; this function must return the instance or the new acting instance
@treturn GuiConcept to allow chaining of functions
@usage-- Adding the draw define for the base button concept, we then return the element
local button =
Gui.get_concept('Button')
:define_draw(function(properties,parent,element)
-- Properties will include all the information that you need to draw the element
-- Parent is the parent element for the element, this may have been altered by previous draw functions
-- Element is the current element being made, this may have a nil value, if it is nil then this is the first draw function
-- You can also pass any other arguments that you want to this function from the draw call
if properties.type == 'button' then
element = parent.add{
type = properties.type,
name = properties.name,
caption = properties.caption,
tooltip = properties.tooltip
}
else
element = parent.add{
type = properties.type,
name = properties.name,
sprite = properties.sprite,
tooltip = properties.tooltip
}
end
-- If you return element or parent then their values will be updated for the next draw function in the chain
-- It is best practice to always return the values if you have made any changes to them
return element, parent
end)
]]
function Prototype:define_draw(draw_callback)
-- Check that it is a function that is being added
if type(draw_callback) ~= 'function' then
error('Draw define must be a function',2)
end
-- Add the draw function
self.draw_callbacks[#self.draw_callbacks+1] = draw_callback
return self
end
--[[ Used to define a draw callback that is ran before any other draw callbacks, see define_draw
@tparam function draw_callback the function that will be called to draw/update the instance; this function must return the instance or the new acting instance
@treturn GuiConcept to allow chaining of functions
@usage-- Placing a button into a flow
local button =
Gui.get_concept('Button')
:define_pre_draw(function(properties,parent,element)
-- Properties will include all the information that you need to draw the element
-- Parent is the parent element for the element, this may have been altered by previous draw functions
-- Element is the current element being made, this may have a nil value, if it is nil then this is the first draw function
-- You can also pass any other arguments that you want to this function from the draw call
parent = parent.add{
type = 'flow'
}
-- If you return element or parent then their values will be updated for the next draw function in the chain
-- It is best practice to always return the values if you have made any changes to them
return element, parent
end)
]]
function Prototype:define_pre_draw(draw_callback)
-- Check that it is a function that is being added
if type(draw_callback) ~= 'function' then
error('Draw define must be a function',2)
end
-- Add the draw function
table.insert(self.draw_callbacks,1,draw_callback)
return self
end
--[[-- Calls all the draw functions in order to create this concept in game; will also store and sync the instance if stores are used
@tparam LuaGuiElement parent_element the element that the concept will use as a base
@tparam[opt] string override_name when given this will be the name of the element rather than the concept id
@treturn LuaGuiElement the element that was created and then passed though and returned by the draw functions
@usage-- Drawing the custom button concept
local custom_button =
Gui.get_concept('CustomButton')
-- Note that the draw function from button was cloned, so unless we want to alter the base button we dont need a new draw define
custom_button:draw(game.player.gui.left)
]]
function Prototype:draw(parent_element,override_name,...)
local old_name = self.properties.name
local parent = parent_element
local element
if override_name then self.properties.name = override_name end
-- Loop over all the draw defines, element is updated when a value is returned
for _,draw_callback in pairs(self.draw_callbacks) do
local success, _element, _parent = pcall(draw_callback,self.properties,parent,element,...)
if success then
if _element then element = _element end
if _parent then parent = _parent end
elseif not success then
self.properties.name = old_name
error('Gui draw handler error with '..self.debug_name..':\n\t'.._element)
new_prototype[key] = value
end
end
-- Return the name back to its previous value
self.properties.name = old_name
-- Adds the instance if instance store is used
if self.add_instance then
self.add_instance(element)
end
-- Syncs the instance if there is a combined store
if self.sync_instance then
self.sync_instance(element)
end
return element
end
--- Concept Instances.
-- Functions that are used to make store concept instances
-- @section concept-instances
--[[-- Adds an instance store to the concept; when a new instance is made it is stored so you can access it later
@tparam[opt] function category_callback when given will act as a way to turn an element into a string to act as a key; keys returned can over lap
@treturn GuiConcept to allow chaining of functions
@usage-- Allowing storing instances of the custom button; stored by the players index
-- Note even thou this is a copy of Button; if Button had an instance store it would not be cloned over
local custom_button =
Gui.get_concept('CustomButton')
:define_instance_store(function(element)
return element.player_index -- The instances are stored based on player id
end)
]]
function Prototype:define_instance_store(category_callback)
self.instance_store = Store.register('gui_instances_'..self.name)
local valid_category = category_callback and type(category_callback) == 'function'
local function get_category(category)
if type(category) == 'table' and type(category.__self) == 'userdata' then
return valid_category and category_callback(category) or nil
else
return category
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
--[[-- Gets all insatnces in a category, category may be nil to return all
@function Prototype.get_instances
@tparam[opt] ?string|LuaGuiElement category the category to get, can only be nil if categories are not used
@treturn table a table which contains all the instances
@usage-- Getting all the instances of the player with index 1
local custom_button =
Gui.get_concept('CustomButton')
--- Creates a new function which adds a store to a gui define
-- @tparam boolean sync if the function should create a synced store
-- @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(sync,callback)
--- Adds a store for the define that is shared between all instances of the define in the same category, categorize is a function that returns a string
-- @tparam self table the gui define being acted on
-- @tparam[opt] string location a unique location identifier, when omitted a uid location will be used, use when sync is set to true
-- @tparam[opt] function categorize function used to determine the category of a LuaGuiElement, when omitted all share one single category
-- categorize param - LuaGuiElement element - the element that needs to be converted
-- categorize 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,location,categorize)
if self.store then return end
custom_button.get_instances(1) -- player index 1
]]
function self.get_instances(category)
return Store.get(self.instance_store,get_category(category))
end
if not sync then
categorize = location
location = Store.uid_location()
end
--[[-- Adds an instance to this concept, used automatically during concept:draw
@function Prototype.add_instance
@tparam LuaGuiElement element the element that will be added as an instance
@tparam[opt] string category the category to add this element under, if nil the category callback is used to assign one
@usage-- Adding an element as a instance for this concept, mostly for internal use
local custom_button =
Gui.get_concept('CustomButton')
if Store.is_registered(location) then
return error('Location for store is already registered: '..location,2)
end
custom_button.add_instance(element) -- normally not needed due to use in concept:draw
]]
function self.add_instance(element,category)
category = category or get_category(element)
if not valid_category then category = nil end
return Store.update(self.instance_store,category,function(tbl)
if type(tbl) ~= 'table' then
return {element}
else
table.insert(tbl,element)
self.store = location
self.categorize = categorize
Instances.register(self.name,self.categorize)
Store.register(self.store,sync,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
--[[-- Applies an update function to all instances, simialr use to what table.forEach would be
@function Prototype.update_instances
@tparam[opt] ?string|LuaGuiElement category the category to get, can only be nil if categories are not used
@tparam function update_callback the function which is called on each instance, recives other args passed to update_instances
@usage-- Changing the font color of all instances for player 1
local custom_button =
Gui.get_concept('CustomButton')
custom_button.update_instances(1,function(element)
element.style.font_color = {r=1,g=0,b=0}
end)
]]
function self.update_instances(category,update_callback,...)
local args
if type(category) == 'function' then
args = {update_callback,...}
update_callback = category
category = nil
end
local instances = Store.get(self.instance_store,get_category(category)) or {}
for key,instance in pairs(instances) do
if not instance or not instance.valid then
instances[key] = nil
else
if args then
update_callback(instance,unpack(args))
else
update_callback(instance,...)
end
end
end
end
return self
end
--- Concept Data.
-- Functions that are used to store concept data
-- @section concept-data
--- 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
--[[-- Adds a data store to this concept which allows you to store synced/percistent data between instances
@tparam[opt] function category_callback when given will act as a way to turn an element into a string to act as a key; keys returned can over lap
@treturn GuiConcept to allow chaining of functions
@usage-- Adding a way to store data for this concept; each player has their own store
-- Note even thou this is a copy of Button; if Button had an data store it would not be cloned over
local custom_button =
Gui.get_concept('CustomButton')
:define_data_store(function(element)
return element.player_index -- The data is stored based on player id
end)
]]
function Prototype:define_data_store(category_callback)
self:new_event('on_data_store_update')
self.data_store = Store.register('gui_data_'..self.name,function(value,key)
self:raise_event('on_data_store_update',{
category = key,
value = value
})
end)
local locale = false
if value_type == 'locale-string' then
locale = true
value_type = 'table'
end
local valid_category = category_callback and type(category_callback) == 'function'
local function get_category(category)
if type(category) == 'table' and type(category.__self) == 'userdata' then
return valid_category and category_callback(category) or nil
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
return category
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
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
end
--[[-- Gets the data that is stored for this category
@function Prototype.get_data
@tparam[opt] ?string|LuaGuiElement category the category to get, can only be nil if categories are not used
@treturn any the data that you had stored in this location
@usage-- Getting the stored data for player 1
local custom_button =
Gui.get_concept('CustomButton')
custom_button.get_data(1) -- player index 1
]]
function self.get_data(category)
return Store.get(self.data_store,get_category(category))
end
--[[-- Sets the data that is stored for this category
@function Prototype.set_data
@tparam[opt] ?string|LuaGuiElement category the category to set, can only be nil if categories are not used
@tparam any value the data that you want to stored in this location
@usage-- Setting the data for player 1 to a table with two keys
local custom_button =
Gui.get_concept('CustomButton')
-- A table is used to show correct way to use a table with self.update_data
-- but a table is not required and can be any data, however upvalues may cause desyncs
custom_button.set_data(1,{
clicks = 0,
required_clicks = 100
}) -- player index 1
]]
function self.set_data(category,value)
return Store.set(self.data_store,get_category(category),value)
end
--[[-- Clears the data that is stored for this category
@function Prototype.clear_data
@tparam[opt] ?string|LuaGuiElement category the category to clear, can only be nil if categories are not used
@usage-- Clearing the data for player 1
local custom_button =
Gui.get_concept('CustomButton')
custom_button.clear_data(1) -- player index 1
]]
function self.clear_data(category)
return Store.clear(self.data_store,get_category(category))
end
--[[-- Updates the data that is stored for this category
@function Prototype.update_data
@tparam[opt] ?string|LuaGuiElement category the category to clear, can only be nil if categories are not used
@tparam function update_callback the function which is called to update the data
@usage-- Updating the clicks key in the concept data for player 1
local custom_button =
Gui.get_concept('CustomButton')
custom_button.update_data(1,function(tbl)
tbl.clicks = tbl.clicks + 1 -- here we are incrementing the clicks by 1
end) -- player index 1
@usage-- Updating a value when a table is not used, alterative to get set
-- so for this example assume that we did custom_button.set_data(1,0)
custom_button.update_data(1,function(value)
return value + 1 -- here we are incrementing the value by 1, we may only be tracking clicks
end) -- player index 1
]]
function self.update_data(category,update_callback,...)
return Store.update(self.data_store,get_category(category),update_callback,...)
end
return self
return handlers and #handlers or 0
end
--- Concept Combined Instances.
-- Functions that are used to make store concept instances and data
-- @section concept-instances
--- 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)
--[[-- Used to add a both instance and data store which are linked together, new instances are synced to the current value, changing the stored value will change all instances
@tparam[opt] function category_callback when given will act as a way to turn an element into a string to act as a key; keys returned can over lap
@tparam function sync_callback the function which is called to update an instance to match the store, this is called on all instances when concept.set_data or update_data is used
@treturn GuiConcept to allow chaining of functions
@usage-- Adding a check box which is a "global setting" synced between all players
local custom_button =
Gui.get_concept('checkbox'):clone('my_checkbox')
:set_caption('My Checkbox')
:set_tooltip('Clicking this check box will change it for everyone')
:on_state_changed(function(event)
local element = event.element
event.concept.set_data(element,element.state) -- Update the stored data to trigger an update of all other instances
end)
:define_combined_store(function(element,state) -- We could add a category function here if we wanted to
element.state = state or false -- Note that the value passed may be nil if there is no stored value and no default set
end)
]]
function Prototype:define_combined_store(category_callback,sync_callback)
if sync_callback == nil then
sync_callback = category_callback
category_callback = nil
if self.pre_authenticator then
if not self.pre_authenticator(player,self.name) then return end
end
self:define_data_store(category_callback)
self:define_instance_store(category_callback)
-- Will update all instances when the data store updates
self:on_data_store_update(function(event)
self.update_instances(event.category,sync_callback,event.value)
end)
--[[-- Will sync an instance to match the stored value based on the given sync callback
@function Prototype.sync_instance
@tparam LuaGuiElement element the element that you want to have update
@usage-- Setting the caption of this element to be the same as the stored value
local custom_button =
Gui.get_concept('CustomButton')
-- Used internally when first draw and automatically when the store updates
custom_button.sync_instance(element)
]]
local properties = self.properties
function self.sync_instance(element)
local default = properties.default
local value = self.get_data(element) or type(default) == 'function' and default(element) or default
sync_callback(element,value)
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
return self
local new_element = element.add(self.draw_data)
self:raise_event('on_style_update',new_element.style)
if self.post_authenticator then
new_element.enabled = self.post_authenticator(player,self.name)
end
if Instances.is_registered(self.name) then
Instances.add_element(self.name,new_element)
end
self:raise_event('on_draw',player,new_element)
return new_element
end
return Prototype
--- Gets the value in this elements store, category needed if categorize 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
--- Sets the value in this elements store, category needed if categorize 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
--- Sets the value in this elements store to nil, category needed if categorize 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
return Constructor

View File

@@ -1,49 +0,0 @@
--[[-- Core Module - ExpStyle
@core ExpStyle
@alias expstyle
]]
local Gui = require 'expcore.gui' -- @dep expcore.gui
Gui.require_concept 'flow' -- @dep gui.concept.flow
--[[-- A flow which can be used to align text and other elements
@see Gui.flow
@element alignment
@usage-- Concept Structure
-- Root
--> [alignment] - the alignment area
Gui.new_concept('alignment')
:set_horizontal_align('center')
]]
Gui.new_concept('flow')
:save_as('alignment')
:new_property('horizontal_align',nil,'right')
:new_property('vertical_align',nil,'center')
:new_property('width')
:new_property('height')
:define_draw(function(properties,parent,element)
local style = element.style
Gui.set_padding(element,1,1,2,2)
-- Set the alignment of the flow
style.horizontal_align = properties.horizontal_align
style.vertical_align = properties.vertical_align
-- Set the stretchablity based on the alignment
style.horizontally_stretchable = style.horizontal_align ~= 'center'
style.vertically_stretchable = style.vertical_align ~= 'center'
-- Set the width if given
local width = properties.width
if width then style.width = width end
-- Set the hieght if given
local height = properties.height
if height then style.height = height end
return element
end)

View File

@@ -1,37 +0,0 @@
--[[-- Core Module - ExpStyle
@module ExpStyle
]]
local Gui = require 'expcore.gui' -- @dep expcore.gui
Gui.require_concept 'frame' -- @dep gui.concept.frame
--[[-- A container frame that can be used to add a boader around your content
@see Gui.frame
@element container
@usage-- Concept Structure
-- Root
--> [container] - the outer frame
-->> container - the content area
Gui.new_concept('container')
]]
Gui.new_concept('frame')
:save_as('container')
:define_draw(function(properties,parent,element)
-- Update the outter frame padding
element.style.padding = 2
-- Add the inner frame
element = element.add{
name = 'container',
type = 'frame',
direction = properties.direction,
style = 'window_content_frame_packed'
}
-- Update the inner frame padding
element.style.padding = 0
return element
end)

View File

@@ -1,101 +0,0 @@
--[[-- Core Module - ExpStyle
@module ExpStyle
]]
local Gui = require 'expcore.gui' -- @dep expcore.gui
Gui.require_concept 'label' -- @dep gui.concept.frame
local right_align =
Gui.new_concept('alignment')
--[[-- A label pair which has a static label and a data label which can be changed
@see Gui.label
@element data_label
@usage-- Concept Structure
-- Root
--> [data_label] - the static label
--> [properties.data_name] - the data label which can be updated
Gui.new_concept('data_label')
:set_data_label_name('game_ticks')
:set_data_caption('0')
:set_data_format(function(concept,element,data,...)
-- This is used with update_data_element and update_from_parent
local caption = tostirng('data')
local tooltip = 'This game has beeing running for: '..caption..' ticks'
return caption, tooltip
end)
]]
local data_label =
Gui.new_concept('label')
:save_as('data_label')
:new_property('data_label_name')
:new_property('data_caption')
:new_property('data_tooltip')
:new_property('data_format',nil,function(concept,element,data,...)
return tostring(data)
end)
-- Draw
:define_draw(function(properties,parent,element)
-- Make the label right aligned
local data_name = properties.data_label_name or properties.name..'_data'
local right_align_element = right_align:draw(parent,data_name)
-- Add a new label
local data_label_element =
right_align_element.add{
name = 'data_label'
}
-- Get the data caption
local caption = Gui.resolve_property(properties.data_caption,element)
if caption then
data_label_element.caption = caption
end
-- Get the data tooltip
local tooltip = Gui.resolve_property(properties.data_tooltip,element)
if tooltip then
data_label_element.tooltip = tooltip
end
return data_label_element
end)
--[[-- Updates the caption and tooltip of the data label using the data format function
@tparam LuaGuiElement element the data label element that you want to update
@tparam any data the data that you want to pass to the format function
@usage-- Updating the data to the current game tick
data_label:update_data_element(element,game.tick)
]]
function data_label:update_data_element(element,data,...)
local caption, tooltip = self.properties.data_format(self,element,data,...)
if caption then
element.caption = caption
end
if tooltip then
element.tooltip = tooltip
end
end
--[[-- Updates the caption and tooltip of the data label using the data format function, given the parent of the data label
@tparam LuaGuiElement parent the parent element to the data label element that you want to update
@tparam any data the data that you want to pass to the format function
@usage-- Updating the data to the current game tick
data_label:update_from_parent(parent,game.tick)
]]
function data_label:update_from_parent(parent,data,...)
local properties = self.properties
local data_name = properties.data_label_name or properties.name..'_data'
local element = parent[data_name] and parent[data_name].data_label or error('Data label is not a child of this element element',2)
local caption, tooltip = properties.data_format(self,element,data,...)
if caption then
element.caption = caption
end
if tooltip then
element.tooltip = tooltip
end
end

View File

@@ -1,54 +0,0 @@
--[[-- Core Module - ExpStyle
@module ExpStyle
]]
local Gui = require 'expcore.gui' -- @dep expcore.gui
Gui.require_concept 'frame' -- @dep gui.concept.table
local right_align =
Gui.new_concept('alignment')
--[[-- A frame that acts as a footer to a section of content
@see Gui.frame
@element footer
@tparam string tooltip the tooltip to show on the title
@usage-- Concept Structure
-- Root
--> [footer] - the footer frame
-->> footer_caption - the lable with the title in it
-->> footer_content - the area to contain butons
Gui.new_concept('footer')
:set_title('Example Footer')
]]
Gui.new_concept('frame')
:save_as('footer')
:new_property('tooltip')
-- Draw
:define_draw(function(properties,parent,element)
-- Update the table style
Gui.set_padding(element,2,2,4,4)
element.style = 'subfooter_frame'
element.caption = ''
local style = element.style
style.horizontally_stretchable = true
style.use_header_filler = false
-- Add the caption to the frame
if properties.title then
element.add{
type = 'label',
name = 'footer_caption',
caption = properties.title,
tooltip = properties.tooltip
}
end
-- Add the right align area
local align = right_align:draw(element,'footer_content')
return align
end)

View File

@@ -1,52 +0,0 @@
--[[-- Core Module - ExpStyle
@module ExpStyle
]]
local Gui = require 'expcore.gui' -- @dep expcore.gui
Gui.require_concept 'frame' -- @dep gui.concept.table
local right_align =
Gui.new_concept('alignment')
--[[-- A frame that acts as a header to a section of content
@see Gui.frame
@element header
@tparam string tooltip the tooltip to show on the title
@usage-- Concept Structure
-- Root
--> [header] - the header frame
-->> header_caption - the lable with the title in it
-->> header_content - the area to contain butons
Gui.new_concept('header')
:set_title('Example Header')
]]
Gui.new_concept('frame')
:save_as('header')
:new_property('tooltip')
-- Draw
:define_draw(function(properties,parent,element)
-- Update the table style
Gui.set_padding(element,2,2,4,4)
element.style = 'subheader_frame'
element.caption = nil
local style = element.style
style.horizontally_stretchable = true
style.use_header_filler = false
-- Add the caption to the frame
element.add{
type = 'label',
name = 'header_caption',
caption = properties.title,
tooltip = properties.tooltip
}
-- Add the right align area
local align = right_align:draw(element,'header_content')
return align
end)

View File

@@ -1,28 +0,0 @@
--[[-- Core Module - ExpStyle
@module ExpStyle
@alias expstyle
]]
--- @dep expcore.gui
--- @dep gui.concept.frame
--- @dep gui.concept.flow
--- @dep gui.concept.table
--- @dep gui.concept.scroll
local function r(name)
require('expcore.gui.styles.expstyle.'..name)
end
r 'container'
r 'alignment'
r 'header'
r 'footer'
r 'scroll_table'
r 'time_label'
r 'data_label'
r 'unit_label'
r 'toggle_button'

View File

@@ -1,60 +0,0 @@
--[[-- Core Module - ExpStyle
@module ExpStyle
]]
local Gui = require 'expcore.gui' -- @dep expcore.gui
Gui.require_concept 'table' -- @dep gui.concept.table
Gui.require_concept 'scroll' -- @dep gui.concept.scroll
local scroll_area =
Gui.new_concept('scroll')
:set_vertical_scroll('auto-and-reserve-space')
:set_horizontal_scroll('never')
--[[-- A table that is inside a vertical scroll area
@see Gui.table
@element scroll_table
@tparam number hight the max hight of the scroll area
@usage-- Concept Structure
-- Root
--> [scroll_table] - the scroll area
-->> table - the table area
Gui.new_concept('scroll_table')
:set_height(200)
:set_column_count(2)
]]
Gui.new_concept('table')
:save_as('scroll_table')
:new_property('height',nil,100)
-- Add a scroll before the table is drawn
:define_pre_draw(function(properties,parent,element)
local scroll = scroll_area:draw(parent,properties.name)
-- Set the scroll style
Gui.set_padding(scroll,1,1,2,2)
scroll.style.horizontally_stretchable = true
scroll.style.maximal_height = properties.height
-- Change the name of the element to table before it is drawn
properties.name = 'table'
return element, scroll
end)
-- Draw
:define_draw(function(properties,parent,element)
-- Update the table style
local style = element.style
style.padding = 0
style.horizontally_stretchable = true
style.vertical_align = 'center'
style.cell_padding = 0
-- Change the stored name back to the actual name
properties.name = element.parent.name
return element
end)

View File

@@ -1,92 +0,0 @@
--[[-- Core Module - ExpStyle
@module ExpStyle
]]
local Gui = require 'expcore.gui' -- @dep expcore.gui
local format_time = ext_require('expcore.common','format_time') --- @dep expcore.common
--- Converts a tick into string format with workds and symbols
local function get_format(properties,time)
local caption, tooltip
-- Check if a custom format is wanted
if properties.time_format then
-- Get the caption
local format = table.deep_copy(properties.time_format)
caption = format_time(time,format)
-- Get the tooltip, always long format
format.long = true
tooltip = format_time(time,format)
else
-- Get the caption
caption = format_time(time,{
hours = properties.use_hours,
minutes = true,
seconds = true
})
-- Get the tooltip, same as the caption but long format
tooltip = format_time(time,{
hours = properties.use_hours,
minutes = true,
seconds = true,
long = true
})
end
return caption, tooltip
end
--[[-- A label that show time in a nice, user friendly way
@element time_label
@tparam number time the time to display in tick
@usage-- Concept Structure
-- Root
--> [time_label] - the label with the time
local time_label =
Gui.new_concept('time_label')
:set_use_hours(true)
:set_time(game.tick)
time_label:update_time(element,game.tick)
]]
local time_label =
Gui.new_concept()
:save_as('time_label')
-- Properties
:new_property('time')
:new_property('use_hours',nil,false)
:new_property('time_format')
-- Draw
:define_draw(function(properties,parent,element,time)
-- Get the caption and tooltip
local caption, tooltip = get_format(properties,time or properties.time)
-- Draw a label
element = parent.add{
name = properties.name,
type = 'label',
caption = caption,
tooltip = tooltip
}
return element
end)
--[[-- Updates the time that is on a label
@tparam LuaGuiElement element the label that you want to update
@tparam number time the number of tick you want it to show
@usage-- Update the time to show game time
time_label:update_time(element,game.time)
]]
function time_label:update_time(element,time)
local caption, tooltip = get_format(self.properties,time)
element.caption = caption
element.tooltip = tooltip
end

View File

@@ -1,49 +0,0 @@
--[[-- Core Module - ExpStyle
@module ExpStyle
]]
local Gui = require 'expcore.gui' -- @dep expcore.gui
Gui.require_concept 'button' -- @dep gui.concept.table
--[[-- A button that will toggle its caption each time it is pressed
@see Gui.button
@element toggle_button
@tparam string alt_caption the caption to show on the button in its true state
@tparam string alt_tooltip the tooltip to show on the button in its true state
@usage-- Concept Structure
-- Root
--> [toggle_button] - the header button
Gui.new_concept('toggle_button')
:set_caption('<')
:set_tooltip('Press to close.')
:set_alt_caption('>')
:set_alt_tooltip('Press to open.')
:on_click(function(event)
local state = event.state and 'close' or 'open'
event.player.print('Toggle button is now: '..state)
end)
]]
Gui.new_concept('button')
:save_as('toggle_button')
:new_property('alt_caption')
:new_property('alt_tooltip')
-- Events
:on_click(function(event)
local concept = event.concept
local properties = concept.properties
local element = event.element
if element.caption == properties.caption then
element.caption = properties.alt_caption
element.tooltip = properties.alt_tooltip or properties.tooltip
event.state = true
else
element.caption = properties.caption
element.tooltip = properties.tooltip or properties.alt_tooltip
event.state = false
end
end)

View File

@@ -1,100 +0,0 @@
--[[-- Core Module - ExpStyle
@module ExpStyle
]]
local Gui = require 'expcore.gui' -- @dep expcore.gui
--[[-- A label triplet which has a static label, a data label which can be changed, and a unit label
@see Gui.label
@see data_label
@element unit_label
@usage-- Concept Structure
-- Root
--> [unit_label] - the static label
--> [properties.data_name] - the data label which can be updated
--> [properties.data_name..'_unit'] - the data label unit which can be updated
Gui.new_concept('unit_label')
:set_data_label_name('game_ticks')
:set_data_caption('0')
:set_data_unit('ticks')
:set_data_format(function(concept,element,data,...)
-- This is used with update_data_element and update_from_parent
local caption = tostirng(data)
local unit = data > 1 and 'ticks' or 'tick'
local tooltip = 'This game has beeing running for: '..caption..' ticks'
return caption, unit, tooltip
end)
]]
local unit_label =
Gui.new_concept('data_label')
:save_as('unit_label')
:new_property('data_unit')
:set_data_format(function(concept,element,data,...)
local base_unit = concept.properties.data_unit
local caption = tostring(data)
local unit = data == 1 and base_unit or base_unit..'s'
return caption, unit
end)
-- Draw
:define_draw(function(properties,parent,element)
-- Get the unit data
local unit = Gui.resolve_property(properties.data_unit,element)
-- Add the unit label
parent.add{
name = element.name..'_unit',
type='label',
caption = unit or '',
tooltip = element.tooltip
}
return element
end)
--[[-- Updates the caption and tooltip and unit of the data label using the data format function
@tparam LuaGuiElement element the unit label element that you want to update
@tparam any data the data that you want to pass to the format function
@usage-- Updating the data to the current game tick
unit_label:update_data_element(element,game.tick)
]]
function unit_label:update_data_element(element,data,...)
local caption, unit, tooltip = self.properties.data_format(self,element,data,...)
local unit_element = element.parent.parent[element.name..'_unit']
if caption then
element.caption = caption
end
if tooltip then
element.tooltip = tooltip
unit_element.tooltip = tooltip
end
if unit then
unit_element.caption = unit
end
end
--[[-- Updates the caption and tooltip and unit of the unit label using the data format function, given the parent of the unit label
@tparam LuaGuiElement parent the parent element to the unit label element that you want to update
@tparam any data the data that you want to pass to the format function
@usage-- Updating the data to the current game tick
unit_label:update_from_parent(parent,game.tick)
]]
function unit_label:update_from_parent(parent,data,...)
local properties = self.properties
local data_name = properties.data_label_name or properties.name..'_data'
local element = parent[data_name] and parent[data_name].data_label or error('Data label is not a child of this element element',2)
local unit_element = parent[data_name..'_unit']
local caption, unit, tooltip = properties.data_format(self,element,data,...)
if caption then
element.caption = caption
end
if tooltip then
element.tooltip = tooltip
unit_element.tooltip = tooltip
end
if unit then
unit_element.caption = unit
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,493 +0,0 @@
--[[-- Core Module - Toolbar
@core Toolbar
@alias Toolbar
]]
local Gui = require 'expcore.gui' --- @dep expcore.gui
local Roles = require 'expcore.roles' --- @dep expcore.roles
local Event = require 'utils.event' --- @dep utils.event
local Game = require 'utils.game' --- @dep utils.game
local mod_gui = require 'mod-gui' --- @dep mod-gui
Gui.require_concept('button') --- @dep Gui.concept.button
local toolbar_toggle_concept
local toolbar_hide_concept
local toolbar_concept
local Toolbar = {
button_concepts = {},
frame_concepts = {},
permissions = {}
}
Gui.Toolbar = Toolbar
--- Permissions.
-- Functions to do with deciding which player can do what
-- @section permissions
--[[-- Used to test if a player is allowed to use a button on the toolbar, if you are not using expcore.roles then change this function
@tparam LuaPlayer player the player you want ot test is allowed to use this button
@tparam string concept_name the name of the button concept that you want to see if the player is allowed to use
@treturn boolean true if the player is allowed to use it
@usage-- Test if a player can use 'test-player-list'
local allowed = Toolbar.allowed(game.player,'test-player-list')
]]
function Toolbar.allowed(player,concept_name)
local permission = Toolbar.permissions[concept_name] or concept_name
return Roles.player_allowed(player,permission)
end
--[[-- Use to add an alias for the allowed test, alias is what is tested for rather than the concept name
@tparam string concept_name the name of the concept that will point to this alias
@tparam string alias the permission string that will be tested when this concept is used with Toolbar.allowed
@usage-- Adding an alias for the 'test-player-list' concept
Toolbar.set_permission_alias('test-player-list','gui/player-list')
]]
function Toolbar.set_permission_alias(concept_name,alias)
Toolbar.permissions[concept_name] = alias
end
--- Buttons.
-- All function to do with the toolbar buttons
-- @section buttons
--[[-- Adds a concept to be drawn to the button area and allows it to be toggled with the toggle toolbar button
@tparam table concept the gui concept that you want to add to the button area
@usage-- Adding a basic button to the toolbar
local new_button =
Gui.new_concept('button')
:set_caption('Click Me')
:on_click(function(event)
event.player.print('You Clicked Me!!')
end)
Toolbar.add_button_concept(new_button)
]]
function Toolbar.add_button_concept(concept)
local concepts = Toolbar.button_concepts
concepts[#concepts+1] = concept
end
--[[-- Updates all the buttons for a player, this means hide and show buttons based on permissions
@tparam LuaPlayer player the player to update the toolbar buttons for
@usage-- Updating your toolbar
Toolbar.update_buttons(player)
]]
function Toolbar.update_buttons(player)
toolbar_concept:raise_event('on_button_update',{
player_index = player.index
})
end
--[[-- Returns an array of buttons names that the given player is able to see, returns none if toolbar hidden
@tparam LuaPlayer player the player you want to get the visible buttons of
@treturn table an array of names of the visible buttons
@usage-- Get a list of all your visible buttons
Toolbar.get_visible_buttons(game.player)
]]
function Toolbar.get_visible_buttons(player)
local rtn = {}
local top_flow = mod_gui.get_button_flow(player)
for _,concept in pairs(Toolbar.button_concepts) do
local element = top_flow[concept.name]
if element.visible then
rtn[#rtn+1] = element.name
end
end
return rtn
end
--[[-- The base element to be used with the toolbar, others can be used but this is recomented
@element toolbar-button
@tparam string permission_alias the alias used with Toolbar.allowed
@usage-- Adding a basic button to the toolbar, note no need to call Toolbar.add_button_concept
Gui.new_concept('toolbar-button')
:set_caption('Click Me')
:on_click(function(event)
event.player.print('You Clicked Me!!')
end)
]]
Toolbar.button =
Gui.new_concept('button')
:save_as('toolbar-button')
:new_property('permission_alias',nil,function(properties,value)
Toolbar.set_permission_alias(properties.name,value)
end)
:define_clone(Toolbar.add_button_concept)
:define_draw(function(properties,parent,element)
element.style = mod_gui.button_style
end)
--- Frames.
-- Functions to do with the toolbar frames
-- @section frames
--[[-- Adds a frame concept to the toolbar frame area, this will not add a button to the toolbar
@tparam table concept the gui concept that you want to add to the toolbar frame area
@usage-- Adding a basic frame to the frame area
local new_frame =
Gui.new_concept('frame')
:set_title('Test')
Toolbar.add_frame_concept(new_frame)
]]
function Toolbar.add_frame_concept(concept)
local concepts = Toolbar.frame_concepts
concepts[#concepts+1] = concept
end
--[[-- Hides all the frames for a player
@tparam LuaPlayer player the player to hide the frames for
@usage-- Hiding all your frames
Toolbar.hide_frames(game.player)
]]
function Toolbar.hide_frames(player)
toolbar_concept:raise_event('on_hide_frames',{
player_index = player.index
})
end
--[[-- Gets an array of the names of all the visible frames for a player
@tparam LuaPlayer player the player that you want to get the visible frames of
@treturn table an array of names of the visible frames for the given player
@usage-- Get all your visible frames
Toolbar.get_visible_frames(game.player)
]]
function Toolbar.get_visible_frames(player)
local rtn = {}
local left_flow = mod_gui.get_frame_flow(player)
for _,concept in pairs(Toolbar.frame_concepts) do
local element = left_flow[concept.name..'-frame']
if element.visible then
rtn[#rtn+1] = element.name
end
end
left_flow[toolbar_hide_concept.name].visible = #rtn > 0
return rtn
end
--[[-- The base toolbar frame, others can be used but this is recomented
@element toolbar-frame
@param on_update fired when the frame is to have its content updated
@tparam boolean open_by_default weather the frame should be open when a player first joins
@tparam boolean use_container true by default and will place a container inside the frame for content
@tparam string direction the direction that the items in the frame are added
@usage-- Adding a basic player list
local player_list =
Gui.new_concept('toolbar-frame')
:set_permission_alias('player_list')
:set_caption('Player List')
:toggle_with_click()
:define_draw(function(properties,parent,element)
local list_area =
element.add{
name = 'scroll',
type = 'scroll-pane',
direction = 'vertical',
horizontal_scroll_policy = 'never',
vertical_scroll_policy = 'auto-and-reserve-space'
}
Gui.set_padding(list_area,1,1,2,2)
list_area.style.horizontally_stretchable = true
list_area.style.maximal_height = 200
for _,player in pairs(game.connected_players) do
list_area.add{
type='label',
caption=player.name
}
end
end)
:on_update(function(event)
local list_area = event.element.scroll
list_area.clear()
for _,player in pairs(game.connected_players) do
list_area.add{
type='label',
caption=player.name
}
end
end)
]]
Toolbar.frame =
Gui.new_concept('toolbar-button')
:save_as('toolbar-frame')
-- Properties
:new_property('open_by_default',nil,false)
:new_property('use_container',nil,true)
:new_property('direction',nil,'horizontal')
:new_event('on_update')
-- Clone
:define_clone(function(concept)
Toolbar.add_frame_concept(concept)
concept:on_click(function(event)
event.concept:toggle_visible_state(event.player)
end)
end)
-- Draw
:define_draw(function(properties,parent,element)
-- Add the base frame element, the button is already drawn to parent
local player = Gui.get_player_from_element(element)
local left_flow = mod_gui.get_frame_flow(player)
local frame = left_flow.add{
name = properties.name..'-frame',
type = 'frame',
direction = properties.direction
}
frame.style.padding = 2
if properties.use_container then
local container =
frame.add{
name = 'container',
type = 'frame',
direction = properties.direction,
style = 'window_content_frame_packed'
}
Gui.set_padding(container)
return container
end
return frame
end)
--[[-- Gets the content area of the frame concept for this player, each player only has one area
@tparam LuaPlayer player the player that you want to get the frame content for
@treturn LuaGuiElement the content area of this concept for this player
@usage-- Get the content area of a concept
local frame = player_list:get_content(game.player)
]]
function Toolbar.frame:get_content(player)
local left_flow = mod_gui.get_frame_flow(player)
local frame = left_flow[self.name..'-frame']
return frame.container or frame
end
--[[-- Toggles the visibilty of this concept for the given player
@tparam LuaPlayer player the player that you want to toggle the frame for
@treturn boolean the new state of the visibilty of this concept for the player
@usage-- Toggle the frame for your self
player_list:toggle_visible_state(game.player)
]]
function Toolbar.frame:toggle_visible_state(player)
local left_flow = mod_gui.get_frame_flow(player)
local frame = left_flow[self.name..'-frame']
if frame.visible then
frame.visible = false
Toolbar.get_visible_frames(player)
return false
else
frame.visible = true
Toolbar.get_visible_frames(player)
return true
end
end
--[[-- Gets the current visibilty state of this conept for this player
@tparam LuaPlayer player the player that you want the visibilty state for
@treturn boolean the current visiblity state of this concept to the player
@usage-- Getting the current visiblity state
player_list:get_visible_state(player)]]
function Toolbar.frame:get_visible_state(player)
local left_flow = mod_gui.get_frame_flow(player)
return left_flow[self.name..'-frame'].visible
end
--[[-- Triggers an update of the content within the concept for this player, uses on_update handlers
@tparam LuaPlayer player the player to update the concept content for
@tparam[opt] table event the event data that you want to pass to the update handlers
@usage-- Updating the frame for your player
player_list:update(game.player)
]]
function Toolbar.frame:update(player,event)
event = event or {}
event.player_index = player.index
event.element = self:get_content(player)
self:raise_event('on_update',event)
end
--[[-- Triggers an update of the content with in this frame for all players
@tparam[opt] table event the event data that you want to pass to the update handlers
@usage-- Update the grame for all players
player_list:update_all()
]]
function Toolbar.frame:update_all(event)
local players = event.update_offline == true and game.players or game.connected_players
for _,player in pairs(players) do
self:update(player)
end
end
--- Other Elements.
-- All the other elements that are used to make this work
-- @section elements
--[[-- The main toolbar element, draws, updates, and controls the other concepts
@element toolbar
@param on_button_update fired when the buttons are updated for a player
@param on_hide_frames fired when the frames are hidden for a player
]]
toolbar_concept =
Gui.new_concept()
:debug('toolbar')
:define_draw(function(properties,player)
-- Get the main flows
local top_flow = mod_gui.get_button_flow(player)
if not top_flow then return end
local left_flow = mod_gui.get_frame_flow(player)
if not left_flow then return end
-- Draw toggle buttons first
toolbar_toggle_concept:draw(top_flow)
toolbar_hide_concept:draw(left_flow)
-- Draw all the buttons and frames
local done = {}
for _,concept in pairs(Toolbar.button_concepts) do
done[concept.name] = true
concept:draw(top_flow)
top_flow[concept.name].visible = Toolbar.allowed(player,concept.name)
local frame = left_flow[concept.name..'-frame']
if frame then
frame.visible = Gui.resolve_property(concept.properties.open_by_default,frame)
end
end
-- Draws frames that did not have buttons
for _,concept in pairs(Toolbar.frame_concepts) do
if not done[concept.name] then
concept:draw(left_flow)
local frame = left_flow[concept.name..'-frame']
if frame then
frame.visible = Gui.resolve_property(concept.properties.open_by_default,frame)
end
end
end
-- Toggle the clear toobar if needed
Toolbar.get_visible_frames(player)
end)
-- When the buttons are updated
:new_event('on_button_update')
:on_button_update(function(event)
-- Get the top flow
local player = event.player
local top_flow = mod_gui.get_button_flow(player)
if not top_flow then return end
-- Set the visiblity of the elements
local visible = top_flow[toolbar_toggle_concept.name].caption == '<'
for _,concept in pairs(Toolbar.button_concepts) do
local element = top_flow[concept.name]
if Gui.valid(element) then
element.visible = visible and Toolbar.allowed(player,concept.name)
end
end
end)
-- When frames are hidden
:new_event('on_hide_frames')
:on_hide_frames(function(event)
-- Get the left flow
local player = event.player
local left_flow = mod_gui.get_frame_flow(player)
if not left_flow then return end
-- Set the visiblity of the elements
left_flow[toolbar_hide_concept.name].visible = false
for _,concept in pairs(Toolbar.frame_concepts) do
local element = left_flow[concept.name..'-frame']
if Gui.valid(element) then element.visible = false end
end
end)
--- Used so toggle and hide can have the same look as each other
local function toolbar_button_draw(properties,parent,element)
element.style = mod_gui.button_style
local style = element.style
style.width = 18
style.height = 36
style.padding = 0
style.left_padding = 1
style.font = 'default-small-bold'
end
--[[-- Button which toggles the the visible state of all toolbar buttons, triggers on_button_update
@element toolbar-toggle
]]
toolbar_toggle_concept =
Gui.new_concept('button')
:set_caption('<')
:set_tooltip{'gui_util.button_tooltip'}
:define_draw(toolbar_button_draw)
:on_click(function(event)
local element = event.element
element.caption = element.caption == '<' and '>' or '<'
toolbar_concept:raise_event('on_button_update',{
player_index = event.player_index
})
end)
--[[-- Button which hides all visible toolbar frames, triggers on_hide_frames
@element toolbar-clear
]]
toolbar_hide_concept =
Gui.new_concept('button')
:set_caption('<')
:set_tooltip{'expcore-gui.left-button-tooltip'}
:define_draw(toolbar_button_draw)
:on_click(function(event)
event.element.visible = false
toolbar_concept:raise_event('on_hide_frames',{
player_index = event.player_index
})
end)
--- When there is a new player they will have the toolbar update
Event.add(defines.events.on_player_created,function(event)
local player = Game.get_player_by_index(event.player_index)
toolbar_concept:draw(player)
end)
--- When a player gets a new role they will have the toolbar updated
Event.add(Roles.events.on_role_assigned,function(event)
toolbar_concept:raise_event('on_button_update',{
player_index = event.player_index
})
end)
--- When a player loses a role they will have the toolbar updated
Event.add(Roles.events.on_role_unassigned,function(event)
toolbar_concept:raise_event('on_button_update',{
player_index = event.player_index
})
end)
return Toolbar

View File

@@ -9,25 +9,16 @@ local Roles = require 'expcore.roles' --- @dep expcore.roles
local Store = require 'expcore.store' --- @dep expcore.store
local Game = require 'utils.game' --- @dep utils.game
local Event = require 'utils.event' --- @dep utils.event
local format_time = ext_require('expcore.common','format_time') --- @dep expcore.common
local config = require 'config.action_buttons' --- @dep config.action_buttons
local Colors = require 'resources.color_presets' --- @dep resources.color_presets
require 'expcore.toolbar' --- @dep expcore.toolbar
Gui.require_concept('label')
Gui.require_concept('button')
Gui.require_concept('text_field')
Gui.require_concept('frame')
Gui.require_style('expstyle')
local action_player_store = 'gui.left.player-list.action-player'
local action_name_store = 'gui.left.player-list.action-name'
--- Name label that alows zoom to map
-- @element zoom_to_map
local zoom_to_map =
Gui.new_concept('label')
:new_event('on_click',defines.events.on_gui_click)
:on_click(function(event)
--- used on player name label to allow zoom to map
local zoom_to_map_name = Gui.uid_name()
Gui.on_click(zoom_to_map_name,function(event)
local action_player_name = event.element.caption
local action_player = Game.get_player_from_any(action_player_name)
if event.button == defines.mouse_button_type.left then
@@ -45,130 +36,136 @@ Gui.new_concept('label')
end
end
end)
:define_pre_draw(function(properties,parent,element)
-- Place the button into a flow
local flow =
parent.add{
type = 'flow',
}
return element, flow
end)
:define_draw(function(properties,parent,element,player,role_name)
local player_name = player.name
element.caption = player_name
element.tooltip = {'player-list.open-map',player_name,player.tag,role_name}
Gui.set_padding(element,0,0,0,2)
element.style.font_color = player.chat_color
end)
--- Right align for the time label
-- @element right_align
local right_align =
Gui.new_concept('alignment')
--- Shows the players online time
-- @element time_label
local time =
Gui.new_concept('time_label')
:set_time_format{minutes = true}
:set_time(0)
:define_draw(function(properties,parent,element)
Gui.set_padding(element)
end)
--- Button used to open the action bar
-- @element open_action_bar
local open_action_bar =
Gui.new_concept('button')
:set_sprite('utility/expand_dots_white')
Gui.new_button()
:set_sprites('utility/expand_dots_white')
:set_tooltip{'player-list.open-action-bar'}
:define_pre_draw(function(properties,parent,element,action_player_name)
-- Place the button into a flow
local flow =
parent.add{
type = 'flow',
name = action_player_name
}
return element, flow
:set_embedded_flow(function(element,action_player_name)
return action_player_name
end)
:define_draw(function(properties,parent,element)
-- Update the style of the element
element.style = 'frame_button'
local style = element.style
style.padding = -2
:set_style('frame_button',function(style)
Gui.set_padding_style(style,-2,-2,-2,-2)
style.width = 8
style.height = 14
end)
:on_click(function(event)
-- Open the action bar when pressed
local element = event.element
local player_name = event.player.name
:on_click(function(player,element)
local new_action_player_name = element.parent.name
local action_player_name = Store.get(action_player_store,player_name)
local action_player_name = Store.get(action_player_store,player.name)
if action_player_name == new_action_player_name then
Store.clear(action_player_store,player_name) -- will close if already open
Store.clear(action_player_store,player.name) -- will close if already open
else
Store.set(action_player_store,player_name,new_action_player_name)
Store.set(action_player_store,player.name,new_action_player_name)
end
end)
--- Button used to close the action bar
-- @element close_action_bar
local close_action_bar =
Gui.new_concept('button')
:set_sprite('utility/close_black','utility/close_white')
Gui.new_button()
:set_sprites('utility/close_black','utility/close_white')
:set_tooltip{'player-list.close-action-bar'}
:define_draw(function(properties,parent,element)
-- Update the style of the element
element.style = 'tool_button'
local style = element.style
style.padding = -1
:set_style('tool_button',function(style)
Gui.set_padding_style(style,-1,-1,-1,-1)
style.height = 28
style.width = 28
end)
:on_click(function(event)
-- Close the action bar
local player_name = event.player.name
Store.clear(action_player_store,player_name)
Store.clear(action_name_store,player_name)
:on_click(function(player,element)
Store.clear(action_player_store,player.name)
Store.clear(action_name_store,player.name)
end)
--- Adds all the player info into the content table
-- @element player_info
local player_info =
Gui.new_concept()
:define_draw(function(properties,parent,element,player,role_name)
local player_name = player.name
open_action_bar:draw(parent,nil,player_name)
zoom_to_map:draw(parent,nil,player,role_name)
time:update_time(time:draw(right_align:draw(parent,time.name..player_name)),player.online_time)
--- Button used to confirm a reason
-- @element reason_confirm
local reason_confirm =
Gui.new_button()
:set_sprites('utility/confirm_slot')
:set_tooltip{'player-list.reason-confirm'}
:set_style('tool_button',function(style)
Gui.set_padding_style(style,-1,-1,-1,-1)
style.height = 28
style.width = 28
end)
:on_click(function(player,element)
local reason = element.parent.entry.text or 'Non Given'
local action_name = Store.get(action_name_store,player.name)
local reason_callback = config[action_name].reason_callback
reason_callback(player,reason)
Store.clear(action_player_store,player.name)
Store.clear(action_name_store,player.name)
element.parent.entry.text = ''
end)
--- Stores all the online players
-- @element content_table
local content_table =
Gui.new_concept('scroll_table')
:set_height(188)
:set_column_count(3)
--[[ Creates the main gui areas for the player list
element
> container
>> scroll
>>> table
>> action_bar
]]
local function generate_container(player,element)
Gui.set_padding(element,2,2,2,2)
element.style.minimal_width = 200
--- Stores all the action buttons
-- @element action_bar
local action_bar =
Gui.new_concept('frame')
:define_draw(function(properties,parent,element)
element.style = 'subfooter_frame'
Gui.set_padding(element,1,1,3,3)
-- main container which contains the other elements
local container =
element.add{
name='container',
type='frame',
direction='vertical',
style='window_content_frame_packed'
}
Gui.set_padding(container)
local style = element.style
style.horizontally_stretchable = true
style.height = 35
-- 3 wide table to contain: action button, player name, and play time
local list_table = Gui.create_scroll_table(container,3,188)
close_action_bar:draw(element)
-- action bar which contains the different action buttons
local action_bar =
container.add{
name='action_bar',
type='frame',
style='subfooter_frame'
}
Gui.set_padding(action_bar,1,1,3,3)
action_bar.style.horizontally_stretchable = true
action_bar.style.height = 35
local player = Gui.get_player_from_element(element)
-- reason bar which contains the reason text field and confirm button
local reason_bar =
container.add{
name='reason_bar',
type='frame',
style='subfooter_frame'
}
Gui.set_padding(reason_bar,-1,-1,3,3)
reason_bar.style.horizontally_stretchable = true
reason_bar.style.height = 35
local action_name = Store.get(action_name_store,player.name)
reason_bar.visible = action_name ~= nil
-- text entry for the reason bar
local reason_field =
reason_bar.add{
name='entry',
type='textfield',
style='stretchable_textfield',
tooltip={'player-list.reason-entry'}
}
Gui.set_padding(reason_field)
reason_field.style.height = 28
reason_field.style.minimal_width = 160
reason_confirm(reason_bar)
return list_table, action_bar
end
--- Adds buttons and permission flows to the action bar
local function generate_action_bar(player,element)
close_action_bar(element)
local action_player = Store.get(action_player_store,player.name)
for action_name,buttons in pairs(config) do
@@ -179,7 +176,7 @@ Gui.new_concept('frame')
}
for _,button in ipairs(buttons) do
button:draw(permission_flow)
button(permission_flow)
end
if not Roles.player_allowed(player,action_name) then
@@ -189,79 +186,18 @@ Gui.new_concept('frame')
if buttons.auth and action_player and not buttons.auth(player,action_player) then
permission_flow.visible = false
end
end
if not action_player then
element.visible = false
end
end)
--- Text entry for reason
-- @element reason_field
local reason_field =
Gui.new_concept('text_field')
:set_tooltip{'player-list.reason-entry'}
:define_draw(function(properties,parent,element)
element.style = 'stretchable_textfield'
local style = element.style
style.padding = 0
style.minimal_width = 160
style.height = 28
end)
--- Button used to confirm a reason
-- @element reason_confirm
local reason_confirm =
Gui.new_concept('button')
:set_sprite('utility/confirm_slot')
:set_tooltip{'player-list.reason-confirm'}
:define_draw(function(properties,parent,element)
-- Update the style of the element
element.style = 'tool_button'
local style = element.style
style.padding = -1
style.height = 28
style.width = 28
end)
:on_click(function(event)
-- Confirm the reason given
local element = event.element
local player_name = event.player.name
local reason = element.parent.entry.text or 'Non Given'
local action_name = Store.get(action_name_store,player_name)
local reason_callback = config[action_name].reason_callback
reason_callback(event.player,reason)
Store.clear(action_player_store,player_name)
Store.clear(action_name_store,player_name)
element.parent.entry.text = ''
end)
--- Stores the reason entry and confirmation button
-- @element reason_bar
local reason_bar =
Gui.new_concept('frame')
:define_draw(function(properties,parent,element)
element.style = 'subfooter_frame'
Gui.set_padding(element,-1,-1,3,3)
local style = element.style
style.horizontally_stretchable = true
style.height = 35
local player = Gui.get_player_from_element(element)
local action_name = Store.get(action_name_store,player.name)
element.visible = action_name ~= nil
reason_field:draw(element)
reason_confirm:draw(element)
end)
end
--- Updates the action bar
local player_list
local player_list_name
local function update_action_bar(player)
local content = player_list:get_content(player)
local element = Gui.find(content,action_bar)
local frame = Gui.classes.left_frames.get_frame(player_list_name,player)
local element = frame.container.action_bar
local action_player_name = Store.get(action_player_store,player.name)
if not action_player_name then
@@ -276,7 +212,6 @@ local function update_action_bar(player)
element.visible = true
for action_name,buttons in pairs(config) do
if buttons.auth and not buttons.auth(player,action_player) then
print(action_name)
element[action_name].visible = false
elseif Roles.player_allowed(player,action_name) then
element[action_name].visible = true
@@ -286,37 +221,70 @@ local function update_action_bar(player)
end
end
--- Adds a player to the player list
local function add_player(list_table,player,role_name)
open_action_bar(list_table,player.name)
-- flow to contain player_name to allow all to have trigger for zoom to map
local player_name_flow =
list_table.add{
type='flow'
}
Gui.set_padding(player_name_flow)
-- player name with the tooltip of their highest role and in they colour
local player_name =
player_name_flow.add{
name=zoom_to_map_name,
type='label',
caption=player.name,
tooltip={'player-list.open-map',player.name,player.tag,role_name}
}
Gui.set_padding(player_name,0,0,0,2)
player_name.style.font_color = player.chat_color
-- flow which allows right align for the play time
local time_flow = Gui.create_alignment(list_table,'player-time-'..player.index)
-- time given in Xh Ym and is right aligned
local tick = game.tick > 0 and game.tick or 1
local percent = math.round(player.online_time/tick,3)*100
local time =
time_flow.add{
name='label',
type='label',
caption=format_time(player.online_time),
tooltip={'player-list.afk-time',percent,format_time(player.afk_time,{minutes=true,long=true})}
}
Gui.set_padding(time)
end
--- Adds fake players to the player list
local function add_fake_players(content_area,count)
local function add_fake_players(list_table,count)
local role_name = 'Fake Player'
for i = 1,count do
local player = {
add_player(list_table,{
name='Player '..i,
index=0-i,
tag='',
online_time=math.random(0,game.tick),
afk_time=math.random(0,game.tick),
chat_color=table.get_random_dictionary_entry(Colors)
}
player_info:draw(content_area,nil,player,role_name)
},role_name)
end
end
--- Registers the player list
-- @element player_list
player_list =
Gui.new_concept('toolbar-frame')
:set_permission_alias('gui/player-list')
:set_sprite('entity/character')
local player_list =
Gui.new_left_frame('gui/player-list')
:set_sprites('entity/character')
:set_tooltip{'player-list.main-tooltip'}
:set_open_by_default(true)
:set_open_by_default()
:set_direction('vertical')
:define_draw(function(properties,parent,element)
local content_area =
content_table:draw(element)
action_bar:draw(element)
reason_bar:draw(element)
:on_creation(function(player,element)
local list_table,action_bar = generate_container(player,element)
generate_action_bar(player,action_bar)
local players = {}
for _,next_player in pairs(game.connected_players) do
@@ -330,49 +298,55 @@ Gui.new_concept('toolbar-frame')
for _,role_name in pairs(Roles.config.order) do
if players[role_name] then
for _,next_player in pairs(players[role_name]) do
player_info:draw(content_area,nil,next_player,role_name)
add_player(list_table,next_player,role_name)
end
end
end
add_fake_players(content_area,4)
--add_fake_players(list_table,6)
--add_fake_players(list_table,20)
end)
:on_update(function(event)
local list = Gui.find(event.element,content_table,'table')
:on_update(function(player,element)
local list = element.container.scroll.table
for _,next_player in pairs(game.connected_players) do
local time_element = Gui.find(list,time.name..next_player.name,time)
local time_element_name = 'player-time-'..next_player.index
local time_element = list[time_element_name]
if time_element and time_element.valid then
time:update_time(time_element,next_player.online_time)
time_element.label.caption = format_time(next_player.online_time)
local tick = game.tick > 0 and game.tick or 1
local percent = math.round(next_player.online_time/tick,3)*100
time_element.label.tooltip = {'player-list.afk-time',percent,format_time(next_player.afk_time,{minutes=true,long=true})}
end
end
end)
player_list_name = player_list:uid()
--- When the action player is changed the action bar will update
Store.register(action_player_store,function(value,category)
local player = Game.get_player_from_any(category)
update_action_bar(player)
local frame = player_list:get_content(player)
local data_table = Gui.find(frame,content_table,'table')
local frame = player_list:get_frame(player)
local data_table = frame.container.scroll.table
for _,next_player in pairs(game.connected_players) do
local element = Gui.find(data_table,next_player.name,open_action_bar)
local element = data_table[next_player.name][open_action_bar.name]
local style = 'frame_button'
if next_player.name == value then
style = 'tool_button'
end
element.style = style
style = element.style
style.padding = -2
style.width = 8
style.height = 14
Gui.set_padding(element,-2,-2,-2,-2)
element.style.width = 8
element.style.height = 14
end
end)
--- When the action name is changed the reason input will update
Store.register(action_name_store,function(value,category)
local player = Game.get_player_from_any(category)
local frame = player_list:get_content(player)
local element = Gui.find(frame,reason_bar)
local frame = Gui.classes.left_frames.get_frame(player_list_name,player)
local element = frame.container.reason_bar
if value then
local action_player_name = Store.get(action_player_store,category)
local action_player = Game.get_player_from_any(action_player_name)
@@ -388,11 +362,10 @@ Store.register(action_name_store,function(value,category)
end)
--- Many events which trigger the gui to be re drawn, it will also update the times every 30 seconds
local update = function(event) player_list:update_all(event) end
Event.on_nth_tick(1800,update)
Event.add(defines.events.on_player_joined_game,update)
Event.add(defines.events.on_player_left_game,update)
Event.add(Roles.events.on_role_assigned,update)
Event.add(Roles.events.on_role_unassigned,update)
Event.on_nth_tick(1800,player_list 'update_all')
Event.add(defines.events.on_player_joined_game,player_list 'redraw_all')
Event.add(defines.events.on_player_left_game,player_list 'redraw_all')
Event.add(Roles.events.on_role_assigned,player_list 'redraw_all')
Event.add(Roles.events.on_role_unassigned,player_list 'redraw_all')
return player_list