diff --git a/config/_file_loader.lua b/config/_file_loader.lua index 3821a304..c4a5bba6 100644 --- a/config/_file_loader.lua +++ b/config/_file_loader.lua @@ -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 diff --git a/config/action_buttons.lua b/config/action_buttons.lua index 13a15c7a..2e2fb9ae 100644 --- a/config/action_buttons.lua +++ b/config/action_buttons.lua @@ -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) diff --git a/expcore/gui.lua b/expcore/gui.lua index 18584b47..6cf395f1 100644 --- a/expcore/gui.lua +++ b/expcore/gui.lua @@ -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' \ No newline at end of file +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 \ No newline at end of file diff --git a/expcore/gui/concepts/button.lua b/expcore/gui/concepts/button.lua deleted file mode 100644 index 4cbcc6cd..00000000 --- a/expcore/gui/concepts/button.lua +++ /dev/null @@ -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) \ No newline at end of file diff --git a/expcore/gui/concepts/center.lua b/expcore/gui/concepts/center.lua new file mode 100644 index 00000000..ffbc3f9f --- /dev/null +++ b/expcore/gui/concepts/center.lua @@ -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 \ No newline at end of file diff --git a/expcore/gui/concepts/checkbox.lua b/expcore/gui/concepts/checkbox.lua deleted file mode 100644 index 364d9624..00000000 --- a/expcore/gui/concepts/checkbox.lua +++ /dev/null @@ -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) \ No newline at end of file diff --git a/expcore/gui/concepts/dropdown.lua b/expcore/gui/concepts/dropdown.lua deleted file mode 100644 index 40e10afc..00000000 --- a/expcore/gui/concepts/dropdown.lua +++ /dev/null @@ -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 \ No newline at end of file diff --git a/expcore/gui/concepts/elem_button.lua b/expcore/gui/concepts/elem_button.lua deleted file mode 100644 index 4318b181..00000000 --- a/expcore/gui/concepts/elem_button.lua +++ /dev/null @@ -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) \ No newline at end of file diff --git a/expcore/gui/concepts/empty.lua b/expcore/gui/concepts/empty.lua deleted file mode 100644 index 46c0233a..00000000 --- a/expcore/gui/concepts/empty.lua +++ /dev/null @@ -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) \ No newline at end of file diff --git a/expcore/gui/concepts/flow.lua b/expcore/gui/concepts/flow.lua deleted file mode 100644 index b4af20d4..00000000 --- a/expcore/gui/concepts/flow.lua +++ /dev/null @@ -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) \ No newline at end of file diff --git a/expcore/gui/concepts/frame.lua b/expcore/gui/concepts/frame.lua deleted file mode 100644 index 6a216353..00000000 --- a/expcore/gui/concepts/frame.lua +++ /dev/null @@ -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) \ No newline at end of file diff --git a/expcore/gui/concepts/label.lua b/expcore/gui/concepts/label.lua deleted file mode 100644 index 9c2df448..00000000 --- a/expcore/gui/concepts/label.lua +++ /dev/null @@ -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) \ No newline at end of file diff --git a/expcore/gui/concepts/left.lua b/expcore/gui/concepts/left.lua new file mode 100644 index 00000000..e827f36a --- /dev/null +++ b/expcore/gui/concepts/left.lua @@ -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 \ No newline at end of file diff --git a/expcore/gui/concepts/line.lua b/expcore/gui/concepts/line.lua deleted file mode 100644 index 28f41cc7..00000000 --- a/expcore/gui/concepts/line.lua +++ /dev/null @@ -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) \ No newline at end of file diff --git a/expcore/gui/concepts/popups.lua b/expcore/gui/concepts/popups.lua new file mode 100644 index 00000000..bda932b3 --- /dev/null +++ b/expcore/gui/concepts/popups.lua @@ -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 \ No newline at end of file diff --git a/expcore/gui/concepts/progress_bar.lua b/expcore/gui/concepts/progress_bar.lua deleted file mode 100644 index 4bcb0b8a..00000000 --- a/expcore/gui/concepts/progress_bar.lua +++ /dev/null @@ -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 \ No newline at end of file diff --git a/expcore/gui/concepts/scroll.lua b/expcore/gui/concepts/scroll.lua deleted file mode 100644 index 034766f7..00000000 --- a/expcore/gui/concepts/scroll.lua +++ /dev/null @@ -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) \ No newline at end of file diff --git a/expcore/gui/concepts/slider.lua b/expcore/gui/concepts/slider.lua deleted file mode 100644 index 703fe102..00000000 --- a/expcore/gui/concepts/slider.lua +++ /dev/null @@ -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) \ No newline at end of file diff --git a/expcore/gui/concepts/table.lua b/expcore/gui/concepts/table.lua deleted file mode 100644 index 3a7ae3bf..00000000 --- a/expcore/gui/concepts/table.lua +++ /dev/null @@ -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) \ No newline at end of file diff --git a/expcore/gui/concepts/text_box.lua b/expcore/gui/concepts/text_box.lua deleted file mode 100644 index 122ba17f..00000000 --- a/expcore/gui/concepts/text_box.lua +++ /dev/null @@ -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) \ No newline at end of file diff --git a/expcore/gui/concepts/text_field.lua b/expcore/gui/concepts/text_field.lua deleted file mode 100644 index dd8f862d..00000000 --- a/expcore/gui/concepts/text_field.lua +++ /dev/null @@ -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) \ No newline at end of file diff --git a/expcore/gui/concepts/toolbar.lua b/expcore/gui/concepts/toolbar.lua new file mode 100644 index 00000000..10a8d971 --- /dev/null +++ b/expcore/gui/concepts/toolbar.lua @@ -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 \ No newline at end of file diff --git a/expcore/gui/core.lua b/expcore/gui/core.lua index 118bfc87..1fd93480 100644 --- a/expcore/gui/core.lua +++ b/expcore/gui/core.lua @@ -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 \ No newline at end of file diff --git a/expcore/gui/elements/buttons.lua b/expcore/gui/elements/buttons.lua new file mode 100644 index 00000000..aa06f58b --- /dev/null +++ b/expcore/gui/elements/buttons.lua @@ -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 \ No newline at end of file diff --git a/expcore/gui/elements/checkbox.lua b/expcore/gui/elements/checkbox.lua new file mode 100644 index 00000000..98f50811 --- /dev/null +++ b/expcore/gui/elements/checkbox.lua @@ -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 \ No newline at end of file diff --git a/expcore/gui/elements/dropdown.lua b/expcore/gui/elements/dropdown.lua new file mode 100644 index 00000000..c56eb960 --- /dev/null +++ b/expcore/gui/elements/dropdown.lua @@ -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 \ No newline at end of file diff --git a/expcore/gui/elements/elem-button.lua b/expcore/gui/elements/elem-button.lua new file mode 100644 index 00000000..e61859bf --- /dev/null +++ b/expcore/gui/elements/elem-button.lua @@ -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 \ No newline at end of file diff --git a/expcore/gui/elements/progress-bar.lua b/expcore/gui/elements/progress-bar.lua new file mode 100644 index 00000000..671f11cb --- /dev/null +++ b/expcore/gui/elements/progress-bar.lua @@ -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 \ No newline at end of file diff --git a/expcore/gui/elements/slider.lua b/expcore/gui/elements/slider.lua new file mode 100644 index 00000000..110dd1bd --- /dev/null +++ b/expcore/gui/elements/slider.lua @@ -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 \ No newline at end of file diff --git a/expcore/gui/elements/text.lua b/expcore/gui/elements/text.lua new file mode 100644 index 00000000..406c4ea0 --- /dev/null +++ b/expcore/gui/elements/text.lua @@ -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 \ No newline at end of file diff --git a/expcore/gui/instances.lua b/expcore/gui/instances.lua new file mode 100644 index 00000000..86fcd11f --- /dev/null +++ b/expcore/gui/instances.lua @@ -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 \ No newline at end of file diff --git a/expcore/gui/prototype.lua b/expcore/gui/prototype.lua index 269b4023..20624583 100644 --- a/expcore/gui/prototype.lua +++ b/expcore/gui/prototype.lua @@ -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 \ No newline at end of file +--- 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 \ No newline at end of file diff --git a/expcore/gui/styles/expstyle/alignment.lua b/expcore/gui/styles/expstyle/alignment.lua deleted file mode 100644 index 80c44f8a..00000000 --- a/expcore/gui/styles/expstyle/alignment.lua +++ /dev/null @@ -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) \ No newline at end of file diff --git a/expcore/gui/styles/expstyle/container.lua b/expcore/gui/styles/expstyle/container.lua deleted file mode 100644 index 00ac52ac..00000000 --- a/expcore/gui/styles/expstyle/container.lua +++ /dev/null @@ -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) \ No newline at end of file diff --git a/expcore/gui/styles/expstyle/data_label.lua b/expcore/gui/styles/expstyle/data_label.lua deleted file mode 100644 index a393ba8b..00000000 --- a/expcore/gui/styles/expstyle/data_label.lua +++ /dev/null @@ -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 \ No newline at end of file diff --git a/expcore/gui/styles/expstyle/footer.lua b/expcore/gui/styles/expstyle/footer.lua deleted file mode 100644 index 01035f19..00000000 --- a/expcore/gui/styles/expstyle/footer.lua +++ /dev/null @@ -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) \ No newline at end of file diff --git a/expcore/gui/styles/expstyle/header.lua b/expcore/gui/styles/expstyle/header.lua deleted file mode 100644 index f570442e..00000000 --- a/expcore/gui/styles/expstyle/header.lua +++ /dev/null @@ -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) \ No newline at end of file diff --git a/expcore/gui/styles/expstyle/index.lua b/expcore/gui/styles/expstyle/index.lua deleted file mode 100644 index 6d773cfb..00000000 --- a/expcore/gui/styles/expstyle/index.lua +++ /dev/null @@ -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' \ No newline at end of file diff --git a/expcore/gui/styles/expstyle/scroll_table.lua b/expcore/gui/styles/expstyle/scroll_table.lua deleted file mode 100644 index 8da23384..00000000 --- a/expcore/gui/styles/expstyle/scroll_table.lua +++ /dev/null @@ -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) \ No newline at end of file diff --git a/expcore/gui/styles/expstyle/time_label.lua b/expcore/gui/styles/expstyle/time_label.lua deleted file mode 100644 index 3fc4b004..00000000 --- a/expcore/gui/styles/expstyle/time_label.lua +++ /dev/null @@ -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 \ No newline at end of file diff --git a/expcore/gui/styles/expstyle/toggle_button.lua b/expcore/gui/styles/expstyle/toggle_button.lua deleted file mode 100644 index 00a894c5..00000000 --- a/expcore/gui/styles/expstyle/toggle_button.lua +++ /dev/null @@ -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) \ No newline at end of file diff --git a/expcore/gui/styles/expstyle/unit_label.lua b/expcore/gui/styles/expstyle/unit_label.lua deleted file mode 100644 index ca47ffd3..00000000 --- a/expcore/gui/styles/expstyle/unit_label.lua +++ /dev/null @@ -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 \ No newline at end of file diff --git a/expcore/gui/test.lua b/expcore/gui/test.lua index e57b7ece..2e25c61b 100644 --- a/expcore/gui/test.lua +++ b/expcore/gui/test.lua @@ -1,717 +1,663 @@ --[[-- Core Module - Gui @module Gui - @alias Gui + @alias tests ]] ---- Tests. --- functions used to test --- @section tests +--- Test. +-- This file creates a test gui that is used to test every input method +-- note that this does not cover every permutation only features in independence +-- for example store in most cases is just by player name, but other store methods are tested with checkbox +-- @section test -local Gui = require 'expcore.gui' -local Game = require 'utils.game' -local Event = require 'utils.event' -require 'expcore.toolbar' +local Gui = require 'expcore.gui' --- @dep expcore.gui +local format_chat_colour,table_keys = ext_require('expcore.common','format_chat_colour','table_keys') --- @dep expcore.common +local Colors = require 'resources.color_presets' --- @dep resources.color_presets +local Event = require 'utils.event' --- @dep utils.event +local Store = require 'expcore.store' --- @dep expcore.store local tests = {} --[[ -The main test frame + Toolbar Tests + > No display - Toolbar button with no display + > With caption - Toolbar button with a caption display + > With icons - Toolbar button with an icon ]] -Gui.require_concept('frame') - -local test_frame = -Gui.new_concept('frame') -:set_title('Gui Tests') -:define_draw(function(properties,parent,element) - for category, _ in pairs(tests) do - element.add{ - type = 'flow', - name = category, - direction = 'vertical' - } - end +Gui.new_toolbar_button('click-1') +:set_post_authenticator(function(player,button_name) + return global.click_one +end) +:on_click(function(player,element) + player.print('CLICK 1') end) -Gui.new_concept('toolbar-button') -:set_permission_alias('gui-test') -:set_caption('Element Tests') -:on_click(function(event) - local player = event.player - if not Gui.destroy(player.gui.center[test_frame.name]) then - Gui.run_tests(event.player) - end +Gui.new_toolbar_button('click-2') +:set_caption('Click Two') +:set_post_authenticator(function(player,button_name) + return global.click_two +end) +:on_click(function(player,element) + player.print('CLICK 2') end) -local test_left_frame = -Gui.new_concept('toolbar-frame') -:set_permission_alias('gui-test') -:set_caption('Frame Test Left') -:define_draw(function(properties,parent,element) - local list_area = - element.add{ - name = 'scroll', - type = 'scroll-pane', - direction = 'vertical', - horizontal_scroll_policy = 'never', - vertical_scroll_policy = 'auto-and-reserve-space' - } - Gui.set_padding(list_area,1,1,2,2) - list_area.style.horizontally_stretchable = true - list_area.style.maximal_height = 200 - - -- Add player names - for _,player in pairs(game.connected_players) do - list_area.add{ - type='label', - caption=player.name - } - end +Gui.new_toolbar_button('click-3') +:set_sprites('utility/questionmark') +:set_post_authenticator(function(player,button_name) + return global.click_three end) -:on_update(function(event) - local list_area = event.element.scroll - list_area.clear() - - -- Add player names - for _,player in pairs(game.connected_players) do - list_area.add{ - type='label', - caption=player.name - } - end +:on_click(function(player,element) + player.print('CLICK 3') end) -Event.add(defines.events.on_player_joined_game,function(event) - test_left_frame:update_all(event) -end) -Event.add(defines.events.on_player_left_game,function(event) - test_left_frame:update_all(event) -end) - ---[[-- Runs a set of gui tests to ensure that the system is working -@tparam LuaPlayer player the player that the guis are made for and who recives the results -@tparam[opt] string category when given only tests in this category are ran -@usage-- Run all gui tests -Gui.run_tests(game.player) -]] -function Gui.run_tests(player,category) - local results = { - passed = 0, - failed = 0, - total = 0, - errors = {} - } - - if not category then - results.breakdown = {} - - for cat,_ in pairs(tests) do - local rtn = Gui.run_tests(player,cat) - results.passed = results.passed + rtn.passed - results.failed = results.failed + rtn.failed - results.total = results.total + rtn.total - - for test_name, err in pairs(rtn.errors) do - results.errors[cat..'/'..test_name] = err - end - - results.breakdown[cat] = rtn - end - - player.print(string.format('All Tests Complete. %d failed.',results.failed)) - - return results - end - - local frame = player.gui.center[test_frame.name] or test_frame:draw(player.gui.center) - local cat_tests = tests[category] - - results.total = #cat_tests - - local output = player.print - for test_name, concept in pairs(cat_tests) do - local success, err = pcall(concept.draw,concept,frame[category]) - - if success then - results.passed = results.passed + 1 - else - results.errors[test_name] = err - results.failed = results.failed + 1 - output(string.format('Test "%s / %s" failed:\n%s',category,test_name,err)) - end - - end - - output(string.format('Test Complete "%s". %d failed.',category,results.failed)) - - return results -end - --[[ -Buttons -> Basic Button -- Button with a caption and a tooltip -> Sprite Button -- Button with a single sprite and a tooltip -> Multi Sprite Button -- Button with three sprites and a tooltip -> Admin Button -- Button which is disabled if the player is not an admin + Center Frame Tests + > Main test gui - Main test gui triggers all other tests ]] -Gui.require_concept('button') - -local basic_button = -Gui.new_concept('button') -:debug('basic_button') -:set_caption('Basic Button') -:set_tooltip('Basic button') -:on_click(function(event) - event.player.print('You pressed basic button!') +local test_gui = +Gui.new_center_frame('gui-test-open') +:set_caption('Open Test Gui') +:set_tooltip('Main test gui triggers all other tests') +:set_post_authenticator(function(player,button_name) + return global.show_test_gui end) -local sprite_button = -Gui.new_concept('button') -:debug('sprite_button') -:set_sprite('utility/warning_icon') -:set_tooltip('Sprite button') -:on_click(function(event) - event.player.print('You pressed sprite button!') -end) +:on_creation(function(player,frame) + for test_group_name,test_group in pairs(tests) do -local multi_sprite_button = -Gui.new_concept('button') -:debug('multi_sprite_button') -:set_sprite('utility/warning_icon','utility/warning','utility/warning_white') -:set_tooltip('Multi-sprite button') -:on_click(function(event) - event.player.print('You pressed multi sprite button!') -end) + player.print('Starting tests for: '..format_chat_colour(test_group_name,Colors.cyan)) + + local pass_count = 0 + local test_count = 0 + + local flow = frame.add{ + type='flow', + name=test_group_name, + direction='vertical' + } + + for test_name,test in pairs(test_group) do + local test_function = type(test) == 'function' and test or test.draw_to + test_count = test_count+1 + + local success,err = pcall(test_function,test,flow) + if success then + pass_count = pass_count+1 + else + player.print('Failed Test: '..format_chat_colour(test_name,Colors.red)) + log('Gui Test Failed: '..test_name..' stacktrace:\n'..err) + end + + end + + if pass_count == test_count then + player.print('All tests '..format_chat_colour('passed',Colors.green)..' ('..test_group_name..')') + else + player.print('Passed '..format_chat_colour(pass_count..'/'..test_count,Colors.cyan)..' ('..test_group_name..')') + end -local admin_button = -Gui.new_concept('button') -:debug('admin_button') -:set_caption('Admin Button') -:set_tooltip('Admin button') -:define_draw(function(properties,parent,element) - local player = Game.get_player_by_index(element.player_index) - if not player.admin then - element.enabled = false - element.tooltip = 'You must be admin to press this button' end end) -:on_click(function(event) - event.player.print('You pressed admin button!') + +--[[ + Left Frame Test + > Left frame which holds all online player names, updates when player leaves or joins +]] + +local left_frame = +Gui.new_left_frame('test-left-frame') +:set_caption('Test Left Gui') +:set_tooltip('Left frame which holds all online player names, updates when player leaves or joins') +:set_post_authenticator(function(player,button_name) + return global.show_test_gui +end) + +:set_open_by_default() +:on_creation(function(_player,frame) + for _,player in pairs(game.connected_players) do + frame.add{ + type='label', + caption=player.name + } + end +end) + +Event.add(defines.events.on_player_joined_game,left_frame 'update_all') +Event.add(defines.events.on_player_left_game,left_frame 'update_all') + +--[[ + Popup Test + > Allows opening a popup which contains the players name and tick it was opened +]] + +local test_popup = +Gui.new_popup('test-popup') +:on_creation(function(player,frame) + frame.add{ + type='label', + caption=player.name + } + frame.add{ + type='label', + caption=game.tick + } +end) + +Gui.new_toolbar_button('test-popup-open') +:set_caption('Test Popup') +:set_tooltip('Allows opening a popup which contains the players name and tick it was opened') +:set_post_authenticator(function(player,button_name) + return global.show_test_gui +end) +:on_click(function(player,element) + test_popup(player,300) +end) + +--[[ + Button Tests + > No display - Simple button which has no display + > Caption - Simple button but has a caption on it + > Icons - Button with an icon display plus two icons for hover and select + > Auth - Button which can only be passed when auth is true (press no display to toggle; needs reopen) +]] + +local button_no_display = +Gui.new_button('test-button-no-display') +:set_tooltip('Button no display') +:on_click(function(player,element) + player.print('Button no display') + global.test_auth_button = not global.test_auth_button + player.print('Auth Button auth state: '..tostring(global.test_auth_button)) +end) + +local button_with_caption = +Gui.new_button('test-button-with-caption') +:set_tooltip('Button with caption') +:set_caption('Button Caption') +:on_click(function(player,element) + player.print('Button with caption') +end) + +local button_with_icon = +Gui.new_button('test-button-with-icon') +:set_tooltip('Button with icons') +:set_sprites('utility/warning_icon','utility/warning','utility/warning_white') +:on_click(function(player,element) + player.print('Button with icons') +end) + +local button_with_auth = +Gui.new_button('test-button-with-auth') +:set_tooltip('Button with auth') +:set_post_authenticator(function(player,button_name) + return global.test_auth_button +end) +:on_click(function(player,element) + player.print('Button with auth') end) tests.Buttons = { - ['Basic Button'] = basic_button, - ['Sprite Button'] = sprite_button, - ['Multi Sprite Button'] = multi_sprite_button, - ['Admin Button'] = admin_button, + ['No display']=button_no_display, + ['Caption']=button_with_caption, + ['Icons']=button_with_icon, + ['Auth']=button_with_auth } --[[ -Checkboxs -> Basic Checkbox -- Simple checkbox that can be toggled -> Game Stored Checkbox -- Checkbox which syncs its state between all players -> Force Stored Checkbox -- Checkbox which syncs its state with all players on the same force -> Player Stored Checkbox -- Checkbox that stores its state between re-draws + Checkbox Test + > Local -- Simple checkbox that can toggle + > Game store -- Checkbox which syncs its state between all players + > Force store -- Checkbox which syncs its state with all players on the same force + > Player store -- Checkbox that stores its state between re-draws ]] -Gui.require_concept('checkbox') - -local basic_checkbox = -Gui.new_concept('checkbox') -:debug('basic_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)) +local checkbox_local = +Gui.new_checkbox('test-checkbox-local') +:set_tooltip('Checkbox local') +:set_caption('Checkbox Local') +:on_element_update(function(player,element,state) + player.print('Checkbox local: '..tostring(state)) end) -local game_checkbox = -Gui.new_concept('checkbox') -:debug('game_checkbox') -:set_caption('Game Stored Checkbox') -:set_tooltip('Game stored checkbox') -:on_state_changed(function(event) - local element = event.element - event.concept.set_data(element,element.state) -- Update other instances - event.player.print('Game stored checkbox is now: '..tostring(element.state)) -end) -:define_combined_store(function(element,state) - element.state = state or false +local checkbox_game = +Gui.new_checkbox('test-checkbox-store-game') +:set_tooltip('Checkbox store game') +:set_caption('Checkbox Store Game') +:add_store() +:on_element_update(function(player,element,state) + player.print('Checkbox store game: '..tostring(state)) end) -local force_checkbox = -Gui.new_concept('checkbox') -:debug('force_checkbox') -:set_caption('Force Stored Checkbox') -:set_tooltip('Force stored checkbox') -:on_state_changed(function(event) - local element = event.element - event.concept.set_data(element,element.state) -- Update other instances - event.player.print('Force stored checkbox is now: '..tostring(element.state)) -end) -:define_combined_store(Gui.categorize_by_force,function(element,state) - element.state = state or false +local checkbox_force = +Gui.new_checkbox('test-checkbox-store-force') +:set_tooltip('Checkbox store force') +:set_caption('Checkbox Store Force') +:add_store(Gui.categorize_by_force) +:on_element_update(function(player,element,state) + player.print('Checkbox store force: '..tostring(state)) end) -local player_checkbox = -Gui.new_concept('checkbox') -:debug('player_checkbox') -:set_caption('Player Stored Checkbox') -:set_tooltip('Player stored checkbox') -:on_state_changed(function(event) - local element = event.element - event.concept.set_data(element,element.state) -- Update other instances - event.player.print('Player stored checkbox is now: '..tostring(element.state)) -end) -:define_combined_store(Gui.categorize_by_player,function(element,state) - element.state = state or false +local checkbox_player = +Gui.new_checkbox('test-checkbox-store-player') +:set_tooltip('Checkbox store player') +:set_caption('Checkbox Store Player') +:add_store(Gui.categorize_by_player) +:on_element_update(function(player,element,state) + player.print('Checkbox store player: '..tostring(state)) end) -tests.Checkboxs = { - ['Basic Checkbox'] = basic_checkbox, - ['Game Stored Checkbox'] = game_checkbox, - ['Force Stored Checkbox'] = force_checkbox, - ['Player Stored Checkbox'] = player_checkbox +tests.Checkboxes = { + ['Local']=checkbox_local, + ['Game store']=checkbox_game, + ['Force store']=checkbox_force, + ['Player store']=checkbox_player } --[[ -Dropdowns -> Static Dropdown -- Simple dropdown with all options being static -> Dynamic Dropdown -- Dropdown which has items based on when it is drawn -> Static Player Stored Dropdown -- Dropdown where the values is synced for each player -> Dynamic Player Stored Dropdown -- Same as above but now with dynamic options + Radiobutton Tests + > Local -- Simple radiobutton that can only be toggled true + > Player store -- Radio button that saves its state between re-draws + > Option set -- A set of radio buttons where only one can be true at a time ]] -Gui.require_concept('dropdown') - -local static_dropdown = -Gui.new_concept('dropdown') -:debug('static_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) +local radiobutton_local = +Gui.new_radiobutton('test-radiobutton-local') +:set_tooltip('Radiobutton local') +:set_caption('Radiobutton Local') +:on_element_update(function(player,element,state) + player.print('Radiobutton local: '..tostring(state)) end) -local dynamic_dropdown = -Gui.new_concept('dropdown') -:debug('dynamic_dropdown') -:set_dynamic_items(function(element) - local items = {} - for concept_name,_ in pairs(Gui.concepts) do - if concept_name:len() < 16 then - items[#items+1] = concept_name - end +local radiobutton_player = +Gui.new_radiobutton('test-radiobutton-store') +:set_tooltip('Radiobutton store') +:set_caption('Radiobutton Store') +:add_store(Gui.categorize_by_player) +:on_element_update(function(player,element,state) + player.print('Radiobutton store: '..tostring(state)) +end) + +local radiobutton_option_set = +Gui.new_radiobutton_option_set('gui.test.share',function(value,category) + game.print('Radiobutton option set for: '..category..' is now: '..tostring(value)) +end,Gui.categorize_by_player) + +local radiobutton_option_one = +Gui.new_radiobutton('test-radiobutton-option-one') +:set_tooltip('Radiobutton option set') +:set_caption('Radiobutton Option One') +:add_as_option(radiobutton_option_set,'One') +:on_element_update(function(player,element,state) + player.print('Radiobutton option one: '..tostring(state)) +end) + +local radiobutton_option_two = +Gui.new_radiobutton('test-radiobutton-option-two') +:set_tooltip('Radiobutton option set') +:set_caption('Radiobutton Option Two') +:add_as_option(radiobutton_option_set,'Two') +:on_element_update(function(player,element,state) + player.print('Radiobutton option two: '..tostring(state)) +end) + +local radiobutton_option_three = +Gui.new_radiobutton('test-radiobutton-option-three') +:set_tooltip('Radiobutton option set') +:set_caption('Radiobutton Option Three') +:add_as_option(radiobutton_option_set,'Three') +:on_element_update(function(player,element,state) + player.print('Radiobutton option three: '..tostring(state)) +end) + +tests.Radiobuttons = { + ['Local']=radiobutton_local, + ['Player store']=radiobutton_player, + ['Option set']=function(self,frame) + Gui.draw_option_set(radiobutton_option_set,frame) end - return items -end) -:on_selection_changed(function(event) - local value = Gui.get_dropdown_value(event.element) - event.player.print('Dynamic dropdown is now: '..value) +} + +--[[ + Dropdown Test + > Local static general -- Simple dropdown with all static options and general handler + > Player startic general -- Dropdown with all static options and general handler and stores option between re-draws + > Local static case -- Dropdown with all static options but case handlers and a general handler + > Player static case -- Dropdown with all static options but case handlers and a general handler and stores option between re-draws + > Local dynamic -- Dropdown with one static option with the reset generated by a function + > Player dynamic -- Dropdown with one static option with the reset generated by a function and stores option between re-draws +]] + +local dropdown_local_static_general = +Gui.new_dropdown('test-dropdown-local-static-general') +:set_tooltip('Dropdown local static general') +:add_options('One','Two','Three','Four') +:on_element_update(function(player,element,value) + player.print('Dropdown local static general: '..tostring(value)) end) -local static_player_dropdown = -Gui.new_concept('dropdown') -:debug('static_player_dropdown') -:set_static_items{'Option 1','Option 2','Option 3'} -:on_selection_changed(function(event) - local element = event.element - local value = Gui.get_dropdown_value(element) - event.concept.set_data(element,value) - event.player.print('Static player stored dropdown is now: '..value) -end) -:define_combined_store(Gui.categorize_by_player,function(element,value) - Gui.set_dropdown_value(element,value) +local dropdown_player_static_general = +Gui.new_dropdown('test-dropdown-store-static-general') +:set_tooltip('Dropdown store static general') +:add_options('One','Two','Three','Four') +:add_store(Gui.categorize_by_player) +:on_element_update(function(player,element,value) + player.print('Dropdown store static general: '..tostring(value)) end) -local dynamic_player_dropdown = -Gui.new_concept('dropdown') -:debug('dynamic_player_dropdown') -:set_dynamic_items(function(element) - local items = {} - for concept_name,_ in pairs(Gui.concepts) do - if concept_name:len() < 16 then - items[#items+1] = concept_name - end - end - return items +local function print_option_selected_1(player,element,value) + player.print('Dropdown local static case (case): '..tostring(value)) +end + +local dropdown_local_static_case = +Gui.new_dropdown('test-dropdown-local-static-case') +:set_tooltip('Dropdown local static case') +:add_options('One','Two') +:add_option_callback('One',print_option_selected_1) +:add_option_callback('Two',print_option_selected_1) +:add_option_callback('Three',print_option_selected_1) +:add_option_callback('Four',print_option_selected_1) +:on_element_update(function(player,element,value) + player.print('Dropdown local static case (general): '..tostring(value)) end) -:on_selection_changed(function(event) - local element = event.element - local value = Gui.get_dropdown_value(element) - event.concept.set_data(element,value) - event.player.print('Dynamic player dropdown is now: '..value) + +local function print_option_selected_2(player,element,value) + player.print('Dropdown store static case (case): '..tostring(value)) +end + +local dropdown_player_static_case = +Gui.new_dropdown('test-dropdown-store-static-case') +:set_tooltip('Dropdown store static case') +:add_store(Gui.categorize_by_player) +:add_options('One','Two') +:add_option_callback('One',print_option_selected_2) +:add_option_callback('Two',print_option_selected_2) +:add_option_callback('Three',print_option_selected_2) +:add_option_callback('Four',print_option_selected_2) +:on_element_update(function(player,element,value) + player.print('Dropdown store static case (general): '..tostring(value)) end) -:define_combined_store(Gui.categorize_by_player,function(element,value) - Gui.set_dropdown_value(element,value) + +local dropdown_local_dynamic = +Gui.new_dropdown('test-dropdown-local-dynamic') +:set_tooltip('Dropdown local dynamic') +:add_options('Static') +:add_dynamic(function(player,element) + return table_keys(Colors) +end) +:on_element_update(function(player,element,value) + player.print('Dropdown local dynamic: '..tostring(value)) +end) + +local dropdown_player_dynamic = +Gui.new_dropdown('test-dropdown-store-dynamic') +:set_tooltip('Dropdown store dynamic') +:add_options('Static') +:add_dynamic(function(player,element) + return table_keys(Colors) +end) +:add_store(Gui.categorize_by_player) +:on_element_update(function(player,element,value) + player.print('Dropdown store dynamic: '..tostring(value)) end) tests.Dropdowns = { - ['Static Dropdown'] = static_dropdown, - ['Dynamic Dropdown'] = dynamic_dropdown, - ['Static Player Stored Dropdown'] = static_player_dropdown, - ['Dynamic Player Stored Dropdown'] = dynamic_player_dropdown + ['Local static general']=dropdown_local_static_general, + ['Player startic general']=dropdown_player_static_general, + ['Local static case']=dropdown_local_static_case, + ['Player static case']=dropdown_player_static_case, + ['Local dynamic general']=dropdown_local_dynamic, + ['Player dynamic general']=dropdown_player_dynamic } --[[ -Listboxs -> Static Listbox -- Simple Listbox with all options being static -> Static Player Stored Listbox -- Listbox where the values is synced for each player + List Box Tests + > Local -- A list box with all static options and general handler + > Store -- A list box with all static options and general handler and stores options between re-draws ]] -local static_listbox = -Gui.new_concept('dropdown') -:debug('static_listbox') -:set_use_list_box(true) -: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 listbox is now: '..value) +local list_box_local = +Gui.new_list_box('test-list-box-local') +:set_tooltip('List box local') +:add_options('One','Two','Three','Four') +:on_element_update(function(player,element,value) + player.print('Dropdown local: '..tostring(value)) end) -local static_player_listbox = -Gui.new_concept('dropdown') -:debug('static_player_listbox') -:set_use_list_box(true) -:set_static_items{'Option 1','Option 2','Option 3'} -:on_selection_changed(function(event) - local element = event.element - local value = Gui.get_dropdown_value(element) - event.concept.set_data(element,value) - event.player.print('Static player stored listbox is now: '..value) -end) -:define_combined_store(Gui.categorize_by_player,function(element,value) - Gui.set_dropdown_value(element,value) +local list_box_player = +Gui.new_list_box('test-list-box-store') +:set_tooltip('List box store') +:add_options('One','Two','Three','Four') +:add_store(Gui.categorize_by_player) +:on_element_update(function(player,element,value) + player.print('Dropdown store: '..tostring(value)) end) -tests.Listboxs = { - ['Static Listbox'] = static_listbox, - ['Static Player Stored Listbox'] = static_player_listbox +tests["List Boxes"] = { + ['Local']=list_box_local, + ['Player']=list_box_player } --[[ -Elem Buttons -> Basic Elem Button -- Basic elem button -> Defaut Selection Elem Button -- Same as above but has a default selection -> Player Stored Elem Button -- Same as above but is stored per player + Slider Tests + > Local default -- Simple slider with default range + > Store default -- Slider with default range that stores value between re-draws + > Static range -- Simple slider with a static range + > Dynamic range -- Slider with a dynamic range + > Local label -- Simple slider with default range which has a label + > Store label -- Slider with default range which has a label and stores value between re-draws ]] -Gui.require_concept('elem_button') - -local basic_elem_button = -Gui.new_concept('elem_button') -:debug('basic_elem_button') -:on_selection_changed(function(event) - event.player.print('Basic elem button is now: '..event.element.elem_value) +local slider_local_default = +Gui.new_slider('test-slider-local-default') +:set_tooltip('Slider local default') +:on_element_update(function(player,element,value,percent) + player.print('Slider local default: '..tostring(math.round(value))..' '..tostring(math.round(percent,1))) end) -local default_selection_elem_button = -Gui.new_concept('elem_button') -:debug('default_selection_elem_button') -:set_elem_type('signal') -:set_default{type='virtual',name='signal-info'} -:on_selection_changed(function(event) - local value = event.element.elem_value - event.player.print('Default selection elem button is now: '..value.type..'/'..value.name) + +local slider_player_default = +Gui.new_slider('test-slider-store-default') +:set_tooltip('Slider store default') +:add_store(Gui.categorize_by_player) +:on_element_update(function(player,element,value,percent) + player.print('Slider store default: '..tostring(math.round(value))..' '..tostring(math.round(percent,1))) end) -local player_elem_button = -Gui.new_concept('elem_button') -:debug('player_elem_button') -:set_elem_type('technology') -:on_selection_changed(function(event) - local element = event.element - local value = element.elem_value - event.concept.set_data(element,value) - event.player.print('Player stored elem button is now: '..value) -end) -:define_combined_store(Gui.categorize_by_player,function(element,value) - element.elem_value = value +local slider_static = +Gui.new_slider('test-slider-static-range') +:set_tooltip('Slider static range') +:set_range(5,50) +:on_element_update(function(player,element,value,percent) + player.print('Slider static range: '..tostring(math.round(value))..' '..tostring(math.round(percent,1))) end) -tests['Elem Buttons'] = { - ['Basic Elem Button'] = basic_elem_button, - ['Defaut Selection Elem Button'] = default_selection_elem_button, - ['Player Stored Elem Button'] = player_elem_button -} - ---[[ -Progress Bars -> Basic Progress Bar -- will increse when pressed, when full then it will reset -> Inverted Progress Bar -- will increse when pressed, when empty then it will reset -> Game Instance Progress Bar -- will take 5 seconds to fill, when full it will reset, note instances are required due to on_tick -> Force Instance Progress Bar -- will increse when pressed, instance only means all instances will increse at same time but may not have the same value -> Force Stored Progress Bar -- will increse when pressed, unlike above all will increse at same time and will have the same value -]] - -Gui.require_concept('progress_bar') - -local basic_progress_bar = -Gui.new_concept('progress_bar') -:debug('basic_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) +local slider_dynamic = +Gui.new_slider('test-slider-dynamic-range') +:set_tooltip('Slider dynamic range') +:set_range(function(player,element) + return player.index - 5 +end,function(player,element) + return player.index + 4 end) -:set_delay_completion(true) -:on_completion(function(event) - event.concept:reset(event.element) +:on_element_update(function(player,element,value,percent) + player.print('Slider dynamic range: '..tostring(math.round(value))..' '..tostring(math.round(percent,1))) end) -local inverted_progress_bar = -Gui.new_concept('progress_bar') -:debug('inverted_progress_bar') -:set_tooltip('Inverted progress bar') -:set_inverted(true) -:set_maximum(5) -:new_event('on_click',defines.events.on_gui_click) -:on_click(function(event) - event.concept:increment(event.element) -end) -:on_completion(function(event) - event.concept:reset(event.element) +local label_slider_local = +Gui.new_slider('test-slider-local-label') +:set_tooltip('Slider local label') +:enable_auto_draw_label() +:on_element_update(function(player,element,value,percent) + player.print('Slider local label: '..tostring(math.round(value))..' '..tostring(math.round(percent,1))) end) -local game_progress_bar = -Gui.new_concept('progress_bar') -:debug('game_progress_bar') -:set_tooltip('Game progress bar') -:set_maximum(300) -:new_event('on_tick',defines.events.on_tick) -:on_tick(function(event) - event.concept:increment(event.element) -end) -:set_delay_completion(true) -:on_completion(function(event) - event.concept:reset(event.element) -end) -:define_instance_store() - -local force_instance_progress_bar = -Gui.new_concept('progress_bar') -:debug('force_instance_progress_bar') -:set_tooltip('Force instance 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) -:define_instance_store(Gui.categorize_by_force) - -local force_stored_progress_bar = -Gui.new_concept('progress_bar') -:debug('force_stored_progress_bar') -:set_tooltip('Force stored progress bar') -:set_maximum(5) -:new_event('on_click',defines.events.on_gui_click) -:on_click(function(event) - local element = event.element - local concept = event.concept - local new_value = concept:increment(element) - if new_value then concept.set_data(element,new_value) end -end) -:set_delay_completion(true) -:on_completion(function(event) - local element = event.element - local concept = event.concept - local new_value = concept:reset(element) - concept.set_data(element,new_value) -end) -:define_combined_store(Gui.categorize_by_force,function(element,value) - element.value = value or 0 -end) - -tests['Progress Bars'] = { - ['Basic Progress Bar'] = basic_progress_bar, - ['Inverted Progress Bar'] = inverted_progress_bar, - ['Game Instance Progress Bar'] = game_progress_bar, - ['Force Instance Progress Bar'] = force_instance_progress_bar, - ['Force Stored Progress Bar'] = force_stored_progress_bar -} - ---[[ -Sliders -> Basic Slider -- Just a basic slider with range 1 to 10 -> Interval Slider -- Same as above but can only be intergers -> Discrete Slider -- A discrete slider -> Dynamic Slider -- A slider which has a dynamic range -> Player Stored Slider -- Slider which stores the value per player, also goes 1 to 10 -]] - -Gui.require_concept('slider') - -local basic_slider = -Gui.new_concept('slider') -:debug('basic_slider') -:set_range(1,10) -:on_value_changed(function(event) - event.player.print('Basic slider is now: '..event.element.slider_value) -end) - -local interval_slider = -Gui.new_concept('slider') -:debug('interval_slider') -:set_range(1,10) -:set_value_step(1) -:on_value_changed(function(event) - event.player.print('Interval slider is now: '..event.element.slider_value) -end) - -local discrete_slider = -Gui.new_concept('slider') -:debug('discrete_slider') -:set_range(1,10) -:set_value_step(1) -:set_discrete_slider(true) -:on_value_changed(function(event) - event.player.print('Discrete slider is now: '..event.element.slider_value) -end) - -local dynamic_slider = -Gui.new_concept('slider') -:debug('dynamic_slider') -:set_range(function(element) - local player = Gui.get_player_from_element(element) - return 1, player.name:len() -end) -:set_value_step(1) -:set_discrete_slider(true) -:on_value_changed(function(event) - event.player.print('Dynamic slider is now: '..event.element.slider_value) -end) - -local player_slider = -Gui.new_concept('slider') -:debug('player_slider') -:set_range(1,10) -:set_value_step(1) -:set_discrete_slider(true) -:on_value_changed(function(event) - local element = event.element - local value = element.slider_value - event.concept.set_data(element,value) - event.player.print('Player stored slider is now: '..value) -end) -:define_combined_store(Gui.categorize_by_player,function(element,value) - element.slider_value = value or 0 +local label_slider_player = +Gui.new_slider('test-slider-store-label') +:set_tooltip('Slider store label') +:enable_auto_draw_label() +:add_store(Gui.categorize_by_player) +:on_element_update(function(player,element,value,percent) + player.print('Slider store label: '..tostring(math.round(value))..' '..tostring(math.round(percent,1))) end) tests.Sliders = { - ['Basic Slider'] = basic_slider, - ['Interval Slider'] = interval_slider, - ['Discrete Slider'] = discrete_slider, - ['Dynamic Slider'] = dynamic_slider, - ['Player Stored Slider'] = player_slider + ['Local default']=slider_local_default, + ['Player default']=slider_player_default, + ['Static range']=slider_static, + ['Dynamic range']=slider_dynamic, + ['Local label']=function(self,frame) + local flow = frame.add{type='flow'} + label_slider_local:draw_to(flow) + end, + ['Player label']=function(self,frame) + local flow = frame.add{type='flow'} + label_slider_player:draw_to(flow) + end } --[[ -Text Fields -> Basic Text Field -- Just a text field which text can be entered into -> Better Text Field -- Same as above but will clear on rmb and un forcus on confirmation -> Decimal Text Field -- Text field which accepts decimal values -> Password Text Field -- Text field which stars out the typed characters -> Player Stored Text Field - Same as basic but will store value per player + Text Tests + > Local field -- Simple text field + > Store field -- Test field that stores text between re-draws + > Local box -- Simple text box + > Wrap box -- Text box which has word wrap and selection disabled ]] -Gui.require_concept('text_field') - --- Making a text field -local basic_text_field = -Gui.new_concept('text_field') -:debug('basic_text_field') -:set_tooltip('Basic text field') -:on_confirmation(function(event) - event.player.print('Basic text field is now: '..event.element.text) +local text_filed_local = +Gui.new_text_filed('test-text-field-local') +:set_tooltip('Text field local') +:on_element_update(function(player,element,value) + player.print('Text field local: '..value) end) -local better_text_field = -Gui.new_concept('text_field') -:debug('better_text_field') -:set_tooltip('Better 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) +local text_filed_store = +Gui.new_text_filed('test-text-field-store') +:set_tooltip('Text field store') +:add_store(Gui.categorize_by_player) +:on_element_update(function(player,element,value) + player.print('Text field store: '..value) end) -local decimal_text_field = -Gui.new_concept('text_field') -:debug('decimal_text_field') -:set_tooltip('Decimal text field') -:set_is_decimal(true) -:on_confirmation(function(event) - event.player.print('Decimal text field is now: '..event.element.text) +local text_box_local = +Gui.new_text_box('test-text-box-local') +:set_tooltip('Text box local') +:on_element_update(function(player,element,value) + player.print('Text box local: '..value) end) -local password_text_field = -Gui.new_concept('text_field') -:debug('password_text_field') -:set_tooltip('Password text field') -:set_is_password(true) -:on_confirmation(function(event) - event.player.print('Password text field is now: '..event.element.text) +local text_box_wrap = +Gui.new_text_box('test-text-box-wrap') +:set_tooltip('Text box wrap') +:set_selectable(false) +:set_word_wrap() +:on_element_update(function(player,element,value) + player.print('Text box wrap: '..value) end) -local player_text_field = -Gui.new_concept('text_field') -:debug('player_text_field') -:set_tooltip('Player stored text field') -:on_confirmation(function(event) - local element = event.element - local text = element.text - event.concept.set_data(element,text) - event.player.print('Player stored text field is now: '..text) -end) -:define_combined_store(Gui.categorize_by_player, function(element,value) - element.text = value or '' -end) - -tests['Text Fields'] = { - ['Basic Text Field'] = basic_text_field, - ['Better Text Field'] = better_text_field, - ['Decimal Text Field'] = decimal_text_field, - ['Password Text Field'] = password_text_field, - ['Player Stored Text Field'] = player_text_field +tests.Texts = { + ['Local field']=text_filed_local, + ['Store field']=text_filed_store, + ['Local box']=text_box_local, + ['Wrap box']=text_box_wrap } --[[ -Text Boxs -> Basic Text Box -- A text box that can not be edited -> Editible Text Box -- A text box that can be edited + Elem Button Tests + > Local -- Simple elem button + > Default -- Simple elem button which has a default value + > Function -- Elem button which has a dynamic default + > Store -- Elem button which stores its value between re-draws ]] -Gui.require_concept('text_box') - -local basic_text_box = -Gui.new_concept('text_box') -:debug('basic_text_box') -:set_tooltip('Basic text box') -:set_default('I am the text that will show in the text box') -:define_draw(function(properties,parent,element) - element.style.height = 75 +local elem_local = +Gui.new_elem_button('test-elem-local') +:set_tooltip('Elem') +:set_type('item') +:on_element_update(function(player,element,value) + player.print('Elem: '..value) end) -local editible_text_box = -Gui.new_concept('text_box') -:debug('editible_text_box') -:set_tooltip('Editible text box') -:set_is_read_only(false) -:set_default('I am the text that will show in the text box') -:on_text_changed(function(event) - event.player.print('Editible text box is now: '..event.element.text) -end) -:define_draw(function(properties,parent,element) - element.style.height = 75 +local elem_default = +Gui.new_elem_button('test-elem-default') +:set_tooltip('Elem default') +:set_type('item') +:set_default('iron-plate') +:on_element_update(function(player,element,value) + player.print('Elem default: '..value) end) -tests['Text Boxs'] = { - ['Basic Text Box'] = basic_text_box, - ['Editible Text Box'] = editible_text_box +local elem_function = +Gui.new_elem_button('test-elem-function') +:set_tooltip('Elem function') +:set_type('item') +:set_default(function(player,element) + return 'iron-plate' +end) +:on_element_update(function(player,element,value) + player.print('Elem function: '..value) +end) + +local elem_store = +Gui.new_elem_button('test-elem-store') +:set_tooltip('Elem store') +:set_type('item') +:add_store(Gui.categorize_by_player) +:on_element_update(function(player,element,value) + player.print('Elem store: '..value) +end) + +tests["Elem Buttons"] = { + ['Local']=elem_local, + ['Default']=elem_default, + ['Function']=elem_function, + ['Store']=elem_store +} + +--[[ + Progress bar tests + > Simple -- Progress bar that fills every 2 seconds + > Store -- Progress bar that fills every 5 seconds with synced value + > Reverse -- Progress bar that decreases every 2 seconds +]] + +local progressbar_one = +Gui.new_progressbar('test-prog-one') +:set_default_maximum(120) +:on_complete(function(player,element,reset_element) + reset_element() +end) + +local progressbar_two = +Gui.new_progressbar('test-prog-one') +:set_default_maximum(300) +:add_store(Gui.categorize_by_force) +:on_complete(function(player,element,reset_element) + reset_element() +end) +:on_store_complete(function(category,reset_store) + reset_store() +end) + +local progressbar_three = +Gui.new_progressbar('test-prog-one') +:set_default_maximum(120) +:use_count_down() +:on_complete(function(player,element,reset_element) + reset_element() +end) + +Event.add(defines.events.on_tick,function() + progressbar_one:increment() + progressbar_three:decrement() + local categories = Store.get_children(progressbar_two.store) + for _,category in pairs(categories) do + progressbar_two:increment(1,category) + end +end) + +tests["Progress Bars"] = { + ['Simple']=progressbar_one, + ['Store']=progressbar_two, + ['Reverse']=progressbar_three } \ No newline at end of file diff --git a/expcore/toolbar.lua b/expcore/toolbar.lua deleted file mode 100644 index 03dd4a1b..00000000 --- a/expcore/toolbar.lua +++ /dev/null @@ -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 \ No newline at end of file diff --git a/modules/gui/player-list.lua b/modules/gui/player-list.lua index a97db8c9..e58a55bd 100644 --- a/modules/gui/player-list.lua +++ b/modules/gui/player-list.lua @@ -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 \ No newline at end of file