From f1214ba398589c96018e387acbc56a40a5c9ec19 Mon Sep 17 00:00:00 2001 From: Dirk Sohler Date: Fri, 2 Apr 2021 04:07:40 +0200 Subject: [PATCH] update main menu to use icons This commit also implements a button getting function to automatically create icon buttons. --- init.lua | 1 + locale/mtimer.de.tr | 5 -- locale/mtimer.it.tr | 5 -- system/formspec/formspec_creation.lua | 45 +--------- system/formspec/formspec_helpers.lua | 77 +++++++++++++++- system/formspec/main_menu.lua | 82 ++++++++++++++++++ .../mtimer_apply.png} | Bin .../mtimer_back.png} | Bin textures/buttons/mtimer_exit.png | Bin 0 -> 12380 bytes .../mtimer_reset_default.png} | Bin .../mtimer_reset_everything.png} | Bin .../mtimer_custom_timer.png} | Bin .../mtimer_host_time_format.png} | Bin .../mtimer_hud_element_offset.png} | Bin .../mtimer_hud_element_size.png} | Bin .../mtimer_ingame_time_format.png} | Bin .../mtimer_main_menu.png} | Bin .../mtimer_real_world_time_format.png} | Bin .../mtimer_session_duration_format.png} | Bin .../mtimer_session_start_time_format.png} | Bin .../mtimer_set_color.png} | Bin .../mtimer_set_position.png} | Bin .../mtimer_set_visibility.png} | Bin .../mtimer_timer_format.png} | Bin .../mtimer_timezone_offset.png} | Bin .../{ => ui}/mtimer_positions_orientation.png | Bin textures/{ => ui}/mtimer_transparent.png | Bin 27 files changed, 161 insertions(+), 54 deletions(-) create mode 100644 system/formspec/main_menu.lua rename textures/{mtimer_button_apply.png => buttons/mtimer_apply.png} (100%) rename textures/{mtimer_button_back.png => buttons/mtimer_back.png} (100%) create mode 100644 textures/buttons/mtimer_exit.png rename textures/{mtimer_button_reset_default.png => buttons/mtimer_reset_default.png} (100%) rename textures/{mtimer_button_reset_everything.png => buttons/mtimer_reset_everything.png} (100%) rename textures/{mtimer_icon_custom_timer.png => icons/mtimer_custom_timer.png} (100%) rename textures/{mtimer_icon_host_time_format.png => icons/mtimer_host_time_format.png} (100%) rename textures/{mtimer_icon_hud_element_offset.png => icons/mtimer_hud_element_offset.png} (100%) rename textures/{mtimer_icon_hud_element_size.png => icons/mtimer_hud_element_size.png} (100%) rename textures/{mtimer_icon_ingame_time_format.png => icons/mtimer_ingame_time_format.png} (100%) rename textures/{mtimer_icon_main_menu.png => icons/mtimer_main_menu.png} (100%) rename textures/{mtimer_icon_real_world_time_format.png => icons/mtimer_real_world_time_format.png} (100%) rename textures/{mtimer_icon_session_duration_format.png => icons/mtimer_session_duration_format.png} (100%) rename textures/{mtimer_icon_session_start_time_format.png => icons/mtimer_session_start_time_format.png} (100%) rename textures/{mtimer_icon_set_color.png => icons/mtimer_set_color.png} (100%) rename textures/{mtimer_icon_set_position.png => icons/mtimer_set_position.png} (100%) rename textures/{mtimer_icon_set_visibility.png => icons/mtimer_set_visibility.png} (100%) rename textures/{mtimer_icon_timer_format.png => icons/mtimer_timer_format.png} (100%) rename textures/{mtimer_icon_timezone_offset.png => icons/mtimer_timezone_offset.png} (100%) rename textures/{ => ui}/mtimer_positions_orientation.png (100%) rename textures/{ => ui}/mtimer_transparent.png (100%) diff --git a/init.lua b/init.lua index 3fc047f..a923a3e 100644 --- a/init.lua +++ b/init.lua @@ -19,6 +19,7 @@ dofile(syspath..'load_configuration.lua') -- Load formspec functionality dofile(syspath..'formspec'..DIR_DELIM..'formspec_helpers.lua') +dofile(syspath..'formspec'..DIR_DELIM..'main_menu.lua') dofile(syspath..'formspec'..DIR_DELIM..'real_time_universal.lua') dofile(syspath..'formspec'..DIR_DELIM..'formspec_creation.lua') diff --git a/locale/mtimer.de.tr b/locale/mtimer.de.tr index 0bd6697..e42547f 100644 --- a/locale/mtimer.de.tr +++ b/locale/mtimer.de.tr @@ -18,11 +18,6 @@ Custom Timer=Individueller Timer Visibility=Sichtbarkeit mTimer Configuration=mTimer-Konfiguration -# Main Menu -Time Representation=Zeit-Repräsentation -Timer Configuration=Timerkonfiguration -Visuals=Aussehen - # Settings Invisible=Unsichtbar Visible=Sichtbar diff --git a/locale/mtimer.it.tr b/locale/mtimer.it.tr index 5ff401b..3a961b1 100755 --- a/locale/mtimer.it.tr +++ b/locale/mtimer.it.tr @@ -14,11 +14,6 @@ Timezone Offset=Fascia oraria Visibility=Visibilità mTimer Configuration=mTimer-Configurazione -# Main Menu -Time Representation=Formato del tempo -Timer Configuration=Conf. cronometro -Visuals=Visualizzazioni - # Settings Visible=Visibile Invisible=Invisibile diff --git a/system/formspec/formspec_creation.lua b/system/formspec/formspec_creation.lua index 4a7940f..4d2d012 100644 --- a/system/formspec/formspec_creation.lua +++ b/system/formspec/formspec_creation.lua @@ -7,7 +7,6 @@ -- Localise needed functions local m = mtimer local S = m.translator -local build_frame = m.build_frame local fe = minetest.formspec_escape @@ -15,54 +14,16 @@ local fe = minetest.formspec_escape -- correlates with the formspec to show. All of the names are self-explanatory -- and within the functions no logic is used. -- +-- The Main menu is a bit more complex and is generated seperately. +-- -- @see mtimer.show_formspec -- @see mtimer.get_times -- @see ./system/on_receive_fields.lua -- @see ./system/chat_command.lua +-- @see ./system/formspec/main_menu.lua -- @see https://dev.minetest.net/formspec -mtimer.dialog.main_menu = function (player_name) - mtimer.show_formspec('mtimer:main_menu', { - title = S('mTimer'), - width = 9.75, - height = 6, - prefix = '', - add_buttons = false, - show_to = player_name, - formspec = { - 'container[0,0]', - 'label[0,0;'..S('Visuals')..']', - 'button[0,0.25;3,0.5;set_visibility;'..S('Visibility')..']', - 'button[0,1;3,0.5;set_position;'..S('Position')..']', - 'button[0,1.75;3,0.5;set_color;'..S('Color')..']', - 'button[0,2.5;3,0.5;hud_element_size;'..S('HUD Element Size')..']', - 'button[0,3.25;3,0.5;hud_element_offset;'..S('HUD Element Offset')..']', - 'container_end[]', - 'container[3.25,0]', - 'label[0,0;'..S('Time Representation')..']', - 'button[0,0.25;3,0.5;ingame_time_format;'..S('Ingame Time Format')..']', - 'button[0,1;3,0.5;real_world_time_format;'..S('Real-World Time Format')..']', - 'button[0,1.75;3,0.5;session_start_time_format;'..S('Session Start Time Format')..']', - 'button[0,2.5;3,0.5;session_duration_format;'..S('Session Duration Format')..']', - 'button[0,3.25;3,0.5;host_time_format;'..S('Host Time Format')..']', - 'container_end[]', - 'container[6.5,0]', - 'label[0,0;'..S('Timer Configuration')..']', - 'button[0,0.25;3,0.5;timer_format;'..S('Timer Format')..']', - 'button[0,1;3,0.5;timezone_offset;'..S('Timezone Offset')..']', - 'button[0,1.75;3,0.5;custom_timer;'..S('Custom Timer')..']', - 'container_end[]', - 'container[0,4.125]', - 'box[0,0;+linewidth,0.04;#ffffff]', - 'button[4.3,0.225;2.5,0.5;reset_everything;'..S('Reset Everything')..']', - 'button_exit[7,0.225;2.5,0.5;exit;'..S('Exit')..']', - 'container_end[]' - } - }) -end - - mtimer.dialog.set_visibility = function (player_name) mtimer.show_formspec('mtimer:set_visibility', { title = S('Visibility'), diff --git a/system/formspec/formspec_helpers.lua b/system/formspec/formspec_helpers.lua index 821f9b8..a71961d 100644 --- a/system/formspec/formspec_helpers.lua +++ b/system/formspec/formspec_helpers.lua @@ -1,5 +1,78 @@ local m = mtimer local S = m.translator +local esc = minetest.formspec_escape + + +-- Get a button with icon +-- +-- This function returns a fully formatted button with icon. +-- +-- The provided ID is set as the button’s ID as well as the button’s image. +-- When set as image all colons are replaced with underscores and the id then +-- is formatted: `mtimer_ID.png` where `ID` is the given ID. +-- +-- The definition table sets up the button’s parameters and is fully optional +-- even if it makes no sense. +-- +-- definition = { +-- label = 'My Cool Button label' or '', +-- width = 5, or 3, +-- image_size = 1, or 0,5, +-- container = { left = 1, top = 1} or pos_container +-- exit_button = true or false +-- } +-- +-- The container table is used to position the button. If the `container` sub +-- table is unset, the following table is used, causing the button to be in the +-- top left corner of the formspec if not contained in another container. +-- +-- pos_container = { +-- left = 0 +-- top = 0 +-- } +-- +-- @param id The ID for the button +-- @param definition The definition table as described +-- @return string The created button as formspec code +mtimer.get_icon_button = function (id, definition) + local def = definition or {} + + -- Set button defaults + local b_width = def.width or 3 + local i_size = def.image_size or 0.5 + local c_left = (def.container or {}).left or 0 + local c_top = (def.container or {}).top or 0 + + -- Calculate button parameters + local b_padding = i_size / 4 + local b_height = (b_padding * 2) + i_size + local l_top_pos = i_size / 2 + local l_left_pos = i_size + (i_size / 4) + + -- Create/Return the button + return (table.concat({ + 'container[+containerLeft,+containerTop]', + ' container[+buttonPadding,+buttonPadding]', + ' image[0,0;+imageSize,+imageSize;+icon]', + ' label[+labelLeftPos,+labelTopPos;+label]', + ' container_end[]', + ' +buttonType[0,0;+buttonWidth,+buttonHeight;+id;]', + 'container_end[]' + }, ' '):gsub('%+%w+', { + ['+containerLeft'] = c_left, + ['+containerTop'] = c_top, + ['+buttonType'] = def.exit_button == true and 'button_exit' or 'button', + ['+buttonWidth'] = b_width, + ['+buttonHeight'] = b_height, + ['+buttonPadding'] = b_padding, + ['+imageSize'] = i_size, + ['+icon'] = 'mtimer_'..id:gsub(':', '_')..'.png', + ['+labelLeftPos'] = l_left_pos, + ['+labelTopPos'] = l_top_pos, + ['+id'] = id, + ['+label'] = esc(def.label or '') + })) +end -- Build the formspec frame @@ -31,7 +104,7 @@ local S = m.translator -- formspec size will be reduced by the amout of units the buttons would -- have taken place. -- --- Some formspec elements do nbot properly start at 0,0 even if set so. The +-- Some formspec elements do not properly start at 0,0 even if set so. The -- `content_offset` attribute offsets the content vertically by the given -- amount of units. Formspec height and button positions are fixed according -- to the given value. @@ -104,7 +177,7 @@ mtimer.show_formspec = function (name, def) ['+height'] = height, ['+linewidth'] = width-0.25, ['+title'] = minetest.formspec_escape(prefix..def.title), - ['+icon'] = 'mtimer_icon_'..name:gsub('mtimer:', '')..'.png', + ['+icon'] = 'mtimer_'..name:gsub('mtimer:', '')..'.png', ['+content'] = 0.9 + content_offset, ['+buttons'] = height-1, ['+defbutton'] = 0.25, diff --git a/system/formspec/main_menu.lua b/system/formspec/main_menu.lua new file mode 100644 index 0000000..aed8bc9 --- /dev/null +++ b/system/formspec/main_menu.lua @@ -0,0 +1,82 @@ +-- Localise needed functions +local m = mtimer +local S = m.translator +local fe = minetest.formspec_escape + + +-- Create buttons at the given position in the main menu formspec +-- +-- This function takes a column and a row and translates that to values used +-- by the icon button function. All buttons are 5 units wide and the image +-- size is 0.5 units. +-- +-- @param column The desired column, starting with 1 +-- @param row The desired row, starting with 1 +-- @param id The button’s ID +-- @param label The button’s label +-- @return string The parsed main menu button +local button = function (column, row, id, label) + local b_width = 5 + local i_size = 0.5 + + -- Calculations + local b_padding = i_size / 4 + local b_height = (b_padding * 2) + i_size + local b_top_position = (row - 1) * b_height + local b_top_spacing = b_top_position == 0 and 0 or b_padding * 1.5 + local c_position = (column - 1) * b_width + local c_spacing = c_position == 0 and 0 or b_padding * 3 + local bc_top = b_top_position + (b_top_spacing * (row - 1)) + local bc_left = c_position + (c_spacing * (column - 1)) + + return mtimer.get_icon_button(id, { + label = fe(label), + width = b_width, + image_size = i_size, + container = { + top = bc_top, + left = bc_left + } + }) +end + + +-- Main Menu generation +-- +-- @see mtimer.show_formspec +-- @see https://dev.minetest.net/formspec +mtimer.dialog.main_menu = function (player_name) + mtimer.show_formspec('mtimer:main_menu', { + title = S('mTimer'), + width = 16, + height = 6.9, + prefix = '', + add_buttons = false, + show_to = player_name, + formspec = { + -- Visuals + button(1, 1, 'set_visibility', S('Visibility')), + button(1, 2, 'set_position', S('Position')), + button(1, 3, 'set_color', S('Color')), + button(1, 4, 'hud_element_size', S('HUD Element Size')), + button(1, 5, 'hud_element_offset', S('HUD Element Offset')), + -- Time Representation + button(2, 1, 'ingame_time_format', S('Ingame Time Format')), + button(2, 2, 'real_world_time_format', S('Real-World Time Format')), + button(2, 3, 'session_start_time_format', S('Session Start Time Format')), + button(2, 4, 'session_duration_format', S('Session Duration Format')), + button(2, 5, 'host_time_format', S('Host Time Format')), + -- Timer configuration + button(3, 1, 'timer_format', S('Timer Format')), + button(3, 2, 'timezone_offset', S('Timezone Offset')), + button(3, 3, 'custom_timer', S('Custom Timer')), + 'container[0,4.75]', + ' box[0,0;+linewidth,0.04;#ffffff]', + ' container[+linewidth,0]', + mtimer.get_icon_button('exit', { label = S('Exit'), exit_button = true, container = { top = 0.25, left = -7.25 } }), + mtimer.get_icon_button('reset_everything', { label = S('Reset Everything'), width = 4, container = { top = 0.25, left = -4 } }), + ' container_end[]', + 'container_end[]' + } + }) +end diff --git a/textures/mtimer_button_apply.png b/textures/buttons/mtimer_apply.png similarity index 100% rename from textures/mtimer_button_apply.png rename to textures/buttons/mtimer_apply.png diff --git a/textures/mtimer_button_back.png b/textures/buttons/mtimer_back.png similarity index 100% rename from textures/mtimer_button_back.png rename to textures/buttons/mtimer_back.png diff --git a/textures/buttons/mtimer_exit.png b/textures/buttons/mtimer_exit.png new file mode 100644 index 0000000000000000000000000000000000000000..a3963c1e0a5201a859d00ef1375b4951a8e473e8 GIT binary patch literal 12380 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