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

@@ -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