From f7a3e282f122c33820c6fa3475ca2bb1c725c3dd Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Mon, 8 Jun 2020 08:58:47 -0500 Subject: [PATCH] Stubbed preset manager state, added confirm and variable dialogs, variables can now be created, everything is now mutatable --- lib/backend.rb | 22 +++ lib/dialogs/confirm_dialog.rb | 21 +++ lib/dialogs/name_prompt_dialog.rb | 10 +- lib/dialogs/variable_dialog.rb | 119 ++++++++++++++++ lib/palette.rb | 4 +- lib/states/editor.rb | 211 +++++++++++++++++++++------- lib/states/manage_presets.rb | 45 ++++++ lib/window.rb | 16 +++ timecrafters_action_configurator.rb | 3 + 9 files changed, 393 insertions(+), 58 deletions(-) create mode 100644 lib/dialogs/confirm_dialog.rb create mode 100644 lib/dialogs/variable_dialog.rb create mode 100644 lib/states/manage_presets.rb diff --git a/lib/backend.rb b/lib/backend.rb index 55dc311..0e78b64 100644 --- a/lib/backend.rb +++ b/lib/backend.rb @@ -4,6 +4,17 @@ module TAC def initialize @config = load_config @tacnet = TACNET.new + + @config_changed = false + end + + def config_changed! + @config[:config][:updated_at] = Time.now + @config_changed = true + end + + def config_changed? + @config_changed end def load_config @@ -25,16 +36,27 @@ module TAC File.open(TAC::CONFIG_PATH, "w") { |f| f.write json } + @config_changed = false + end + + def upload_config if @tacnet.connected? @tacnet.puts(TAC::TACNET::PacketHandler.packet_dump_config(json)) end end + def download_config + if @tacnet.connected? + end + end + def write_default_config File.open(TAC::CONFIG_PATH, "w") do |f| f.write JSON.dump( { config: { + created_at: Time.now, + updated_at: Time.now, spec_version: TAC::CONFIG_SPEC_VERSION, hostname: TACNET::DEFAULT_HOSTNAME, port: TACNET::DEFAULT_PORT, diff --git a/lib/dialogs/confirm_dialog.rb b/lib/dialogs/confirm_dialog.rb new file mode 100644 index 0000000..771ed69 --- /dev/null +++ b/lib/dialogs/confirm_dialog.rb @@ -0,0 +1,21 @@ +module TAC + class Dialog + class ConfirmDialog < Dialog + def build + background Gosu::Color::GRAY + label @options[:message], text_size: 18 + + flow width: 1.0 do + button "Cancel", width: 0.475, text_size: 18 do + close + end + button "Okay", width: 0.475, text_size: 18 do + @options[:callback_method].call + + close + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/dialogs/name_prompt_dialog.rb b/lib/dialogs/name_prompt_dialog.rb index 97d5f57..1b81ca1 100644 --- a/lib/dialogs/name_prompt_dialog.rb +++ b/lib/dialogs/name_prompt_dialog.rb @@ -5,7 +5,7 @@ module TAC background Gosu::Color::GRAY flow width: 1.0 do label "Name", width: 0.25, text_size: 18 - @name = edit_line "", width: 0.70, text_size: 18 + @name = edit_line @options[:renaming] ? @options[:renaming].name : "", width: 0.70, text_size: 18 end flow width: 1.0 do @@ -13,11 +13,15 @@ module TAC close end - button @options[:submit_label], width: 0.475, text_size: 18 do + button @options[:renaming] ? "Update" : "Add", width: 0.475, text_size: 18 do if @name.value.strip.empty? push_state(TAC::Dialog::AlertDialog, title: "Error", message: "Name cannot be blank.\nName cannot only be whitespace.") else - @options[:callback_method].call(@name.value.strip) + if @options[:renaming] + @options[:callback_method].call(@options[:renaming], @name.value.strip) + else + @options[:callback_method].call(@name.value.strip) + end close end diff --git a/lib/dialogs/variable_dialog.rb b/lib/dialogs/variable_dialog.rb new file mode 100644 index 0000000..d3418fc --- /dev/null +++ b/lib/dialogs/variable_dialog.rb @@ -0,0 +1,119 @@ +module TAC + class Dialog + class VariableDialog < Dialog + def build + background Gosu::Color::GRAY + + @type = @options[:value].type if @options[:value] + + label "Name" + @name_error = label "Error", text_size: 18, color: TAC::Palette::TACNET_CONNECTION_ERROR + @name_error.hide + @name = edit_line @options[:value] ? @options[:value].name : "", text_size: 18 + + label "Type" + @type_error = label "Error", text_size: 18, color: TAC::Palette::TACNET_CONNECTION_ERROR + @type_error.hide + # TODO: Add dropdown menus to CyberarmEngine + flow width: 1.0 do + [:float, :double, :integer, :long, :string, :boolean].each do |btn| + button btn, text_size: 18 do + @type = btn + @value_container.show + end + end + end + + @value_container = stack width: 1.0 do + label "Value" + @value_error = label "Error", text_size: 18, color: TAC::Palette::TACNET_CONNECTION_ERROR + @value_error.hide + @value = edit_line @options[:value] ? @options[:value].value : "", text_size: 18 + end + + flow width: 1.0 do + button "Cancel", width: 0.475, text_size: 18 do + close + end + + button @options[:value] ? "Update" : "Add", width: 0.475, text_size: 18 do |b| + if valid? + if @options[:value] + @options[:callback_method].call(@options[:value], @name.value.strip, @type, @value.value.strip) + else + @options[:callback_method].call(@name.value.strip, @type, @value.value.strip) + end + + close + end + end + end + end + + def valid? + valid = true + + if @name.value.strip.empty? + @name_error.value = "Error: Name cannot be blank\n or only whitespace." + @name_error.show + valid = false + else + @name_error.value = "" + @name_error.hide + end + + if not @type + @type_error.value = "Error: Type not set." + @type_error.show + valid = false + else + @type_error.value = "" + @type_error.hide + end + + if [:integer, :float, :double, :long].include?(@type) + if @value.value.strip.empty? + @value_error.value = "Error: Value cannot be blank\n or only whitespace." + @value_error.show + valid = false + elsif [:integer, :long].include?(@type) + begin + Integer(@value.value.strip) + rescue + @value_error.value = "Error: Invalid value,\nexpected whole number." + @value_error.show + valid = false + end + elsif [:float, :double].include?(@type) + begin + Float(@value.value.strip) + rescue + @value_error.value = "Error: Invalid value,\nexpected decimal number." + @value_error.show + valid = false + end + else + @value_error.value = "" + @value_error.hide + end + elsif @type == :string + if @value.value.strip.empty? + @value_error.value = "Error: Value cannot be blank\n or only whitespace." + @value_error.show + valid = false + end + elsif @type == :boolean + @value_error.value = "Error: Boolean not yet supported." + @value_error.show + valid = false + else + @value_error.value = "Error: Type not set." + @value_error.show + valid = false + end + + return valid + end + end + end +end \ No newline at end of file diff --git a/lib/palette.rb b/lib/palette.rb index c88b09b..0156bf4 100644 --- a/lib/palette.rb +++ b/lib/palette.rb @@ -18,9 +18,9 @@ module TAC ACTIONS_SECONDARY = Gosu::Color.new(0xff040404) VALUES_PRIMARY = Gosu::Color.new(0xff660066) - VALUES_SECONDARY = Gosu::Color.new(0x040404ff) + VALUES_SECONDARY = Gosu::Color.new(0xff440044) EDITOR_PRIMARY = Gosu::Color.new(0xff446688) - EDITOR_SECONDARY = Gosu::Color.new(0x040404ff) + EDITOR_SECONDARY = Gosu::Color.new(0xff224466) end end \ No newline at end of file diff --git a/lib/states/editor.rb b/lib/states/editor.rb index f71acca..feea587 100644 --- a/lib/states/editor.rb +++ b/lib/states/editor.rb @@ -12,11 +12,38 @@ module TAC background [TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_SECONDARY] flow width: 1.0, height: 1.0 do - stack width: 0.70 do - label TAC::NAME, color: Gosu::Color.rgb(59, 200, 81), bold: true, text_size: 72 + stack width: 0.60 do + label TAC::NAME, color: Gosu::Color::BLACK, bold: true + flow width: 1.0 do + flow width: 0.3 do + label "Group: ", text_size: 18 + @active_group_label = label "", text_size: 18 + end + + flow width: 0.3 do + label "Action: ", text_size: 18 + @active_action_label = label "", text_size: 18 + end + + flow width: 0.395 do + button "►", text_size: 18, margin_left: 10, tip: "Simulate robot path" + button "Presets", text_size: 18, margin_left: 10, tip: "Manage presets" do + push_state(ManagePresets) + end + button "Save", text_size: 18, margin_left: 10, tip: "Save config to disk" do + window.backend.save_config + end + button "▲", text_size: 18, margin_left: 10, tip: "Upload local config to remote, if connected." do + window.backend.upload_config + end + button "▼", text_size: 18, margin_left: 10, tip: "Download remote config, if connected." do + push_state(Dialog::ConfirmDialog, title: "Are you sure?", message: "Replace local config with\n remote config?", callback_method: proc { window.backend.download_config }) + end + end + end end - flow width: 0.299 do + flow width: 0.399 do stack width: 0.5 do label "TACNET v#{TACNET::Packet::PROTOCOL_VERSION}", color: TAC::Palette::TACNET_PRIMARY @tacnet_ip_address = label "#{TACNET::DEFAULT_HOSTNAME}:#{TACNET::DEFAULT_PORT}", color: TAC::Palette::TACNET_SECONDARY @@ -33,41 +60,45 @@ module TAC end flow width: 1.0, height: 0.9 do - stack width: 0.2, height: 1.0 do + stack width: 0.333, height: 1.0 do background TAC::Palette::GROUPS_PRIMARY flow do label "Groups" - button "Add Group", text_size: 18 do - push_state(TAC::Dialog::NamePromptDialog, title: "Create Group", submit_label: "Add", callback_method: method(:create_group)) + button "+", text_size: 18 do + push_state(TAC::Dialog::NamePromptDialog, title: "Create Group", callback_method: method(:create_group)) end + button "Clone", text_size: 18 + button "Create Preset", text_size: 18 end @groups_list = stack width: 1.0 do end end - stack width: 0.2, height: 1.0 do + stack width: 0.333, height: 1.0 do background TAC::Palette::ACTIONS_PRIMARY flow do label "Actions" - button "Add Action", text_size: 18 do + button "+", text_size: 18 do if @active_group - push_state(TAC::Dialog::NamePromptDialog, title: "Create Action", submit_label: "Add", callback_method: method(:create_action)) + push_state(TAC::Dialog::NamePromptDialog, title: "Create Action", callback_method: method(:create_action)) else push_state(TAC::Dialog::AlertDialog, title: "Error", message: "Unable to create action,\nno group selected.") end end + button "Clone", text_size: 18 + button "Create Preset", text_size: 18 end @actions_list = stack width: 1.0 do end end - stack width: 0.2, height: 1.0 do + stack width: 0.333, height: 1.0 do background TAC::Palette::VALUES_PRIMARY flow do label "Values" - button "Add Value", text_size: 18 do + button "+", text_size: 18 do if @active_action - push_state(TAC::Dialog::NamePromptDialog, title: "Create Value", subtitle: "Add Value", submit_label: "Add", callback_method: method(:create_value)) + push_state(TAC::Dialog::VariableDialog, title: "Create Value", callback_method: method(:create_value)) else push_state(TAC::Dialog::AlertDialog, title: "Error", message: "Unable to create value,\nno action selected.") end @@ -77,13 +108,6 @@ module TAC @values_list = stack width: 1.0 do end end - stack width: 0.399, height: 1.0 do - background TAC::Palette::EDITOR_PRIMARY - label "Editor" - - @editor = stack width: 1.0 do - end - end end end @@ -92,21 +116,91 @@ module TAC def create_group(name) window.backend.config[:data][:groups] << {id: rand(100), name: name} - window.backend.save_config + window.backend.config_changed! + + populate_groups_list + end + + def update_group(group_struct, name) + group = window.backend.config[:data][:groups].find { |g| g[:id] == group_struct.id } + group[:name] = name + + window.backend.config_changed! + + populate_groups_list + end + + def delete_group(group_struct) + group = window.backend.config[:data][:groups].find { |a| a[:id] == group_struct.id } + window.backend.config[:data][:groups].delete(group) + + window.backend.config[:data][:actions].select { |a| a[:group_id] == group[:id] }.each do |action| + window.backend.config[:data][:actions].delete(action) + window.backend.config[:data][:values].delete_if { |v| v[:action_id] == action[:id] } + end + window.backend.config_changed! + + @active_group = nil + @active_group_label.value = "" + @active_action = nil + @active_action_label.value = "" + @actions_list.clear + @values_list.clear populate_groups_list end def create_action(name) window.backend.config[:data][:actions] << {id: rand(100), group_id: @active_group.id, name: name, enabled: true} - window.backend.save_config + window.backend.config_changed! populate_actions_list(@active_group.id) end - def create_value(name, type = :float, value = 45.0) + def update_action(action_struct, name) + action = window.backend.config[:data][:actions].find { |a| a[:id] == action_struct.id } + action[:name] = name + + window.backend.config_changed! + + populate_actions_list(@active_group.id) + end + + def delete_action(action_struct) + action = window.backend.config[:data][:actions].find { |a| a[:id] == actions_struct.id } + window.backend.config[:data][:actions].delete(action) + window.backend.config[:data][:values].delete_if { |v| v[:action_id] == action[:id] } + window.backend.config_changed! + + @active_action = nil + @active_action_label.value = "" + @values_list.clear + + populate_actions_list(@active_group.id) + end + + def create_value(name, type, value) window.backend.config[:data][:values] << {id: rand(100), action_id: @active_action.id, name: name, type: type, value: value} - window.backend.save_config + window.backend.config_changed! + + populate_values_list(@active_action.id) + end + + def update_value(value_struct, name, type, value) + _v = window.backend.config[:data][:values].find { |v| v[:id] == value_struct.id } + _v[:name] = name + _v[:type] = type + _v[:value] = value + + window.backend.config_changed! + + populate_values_list(@active_action.id) + end + + def delete_value(value_struct) + _v = window.backend.config[:data][:values].find { |v| v[:id] == value_struct.id } + window.backend.config[:data][:values].delete(_v) + window.backend.config_changed! populate_values_list(@active_action.id) end @@ -116,13 +210,23 @@ module TAC @groups_list.clear do groups.each do |group| - button group.name, text_size: 18, width: 1.0 do - @active_group = group - @active_action = nil + flow width: 1.0 do + button group.name, text_size: 18, width: 0.855 do + @active_group = group + @active_group_label.value = group.name + @active_action = nil + @active_action_label.value = "" - populate_actions_list(group.id) - @values_list.clear - @editor.clear + populate_actions_list(group.id) + @values_list.clear + end + + button "E", text_size: 18 do + push_state(Dialog::NamePromptDialog, title: "Rename Group", renaming: group, callback_method: method(:update_group)) + end + button "D", text_size: 18 do + push_state(Dialog::ConfirmDialog, title: "Are you sure?", message: "Delete group and all\nof its actions and values?", callback_method: proc { delete_group(group) }) + end end end end @@ -133,11 +237,20 @@ module TAC @actions_list.clear do actions.each do |action| - button action.name, text_size: 18, width: 1.0 do - @active_action = action + flow width: 1.0 do + button action.name, text_size: 18, width: 0.855 do + @active_action = action + @active_action_label.value = action.name - populate_values_list(action.id) - @editor.clear + populate_values_list(action.id) + end + + button "E", text_size: 18 do + push_state(Dialog::NamePromptDialog, title: "Rename Action", renaming: action, callback_method: method(:update_action)) + end + button "D", text_size: 18 do + push_state(Dialog::ConfirmDialog, title: "Are you sure?", message: "Delete action and all\nof its values?", callback_method: proc { delete_action(action) }) + end end end end @@ -147,30 +260,22 @@ module TAC values = TAC::Storage.values(action_id) @values_list.clear do - values.each do |value| - button value.name, text_size: 18, width: 1.0 do - populate_editor(value) + values.each_with_index do |value, i| + flow width: 1.0 do + background TAC::Palette::VALUES_SECONDARY if i.odd? + + label value.name, text_size: 18, width: 0.855 + + button "E", text_size: 18 do + push_state(Dialog::VariableDialog, title: "Edit Variable", value: value, callback_method: method(:update_value)) + end + button "D", text_size: 18 do + push_state(Dialog::ConfirmDialog, title: "Are you sure?", message: "Delete value?", callback_method: proc { delete_value(value) }) + end end end end end - - def populate_editor(value) - @editor.clear do - [:id, :action_id, :name, :type, :value].each do |m| - label "#{m}: #{value.send(m)}", text_size: 18 - end - - case value.type - when :double, :float, :integer, :string - edit_line "#{value.value}", width: 1.0 - when :boolean - toggle_button checked: value.value - else - label "Unsupported value type: #{value.type.inspect}", text_size: 18 - end - end - end end end end \ No newline at end of file diff --git a/lib/states/manage_presets.rb b/lib/states/manage_presets.rb new file mode 100644 index 0000000..818135b --- /dev/null +++ b/lib/states/manage_presets.rb @@ -0,0 +1,45 @@ +module TAC + class States + class ManagePresets < CyberarmEngine::GuiState + def setup + theme(THEME) + + stack width: 1.0, height: 0.1 do + background [TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_SECONDARY] + label "Manage Presets", text_size: 28 + button "Close", text_size: 18 do + pop_state + end + end + flow width: 1.0, height: 0.9 do + stack width: 0.33, height: 1.0 do + background TAC::Palette::GROUPS_PRIMARY + + label "Group Presets" + # TAC::Storage.group_presets.each do |preset| + %w{ Hello World How Are You }.each do |preset| + button preset, width:1.0, text_size: 18 + end + + label "Action Presets" + # TAC::Storage.action_presets.each do |preset| + %w{ Hello World How Are You }.each do |preset| + button preset, width:1.0, text_size: 18 + end + end + + stack width: 0.6698, height: 1.0 do + background TAC::Palette::EDITOR_PRIMARY + + label "Editor" + + @editor = stack width: 1.0, height: 1.0, margin: 10 do + background TAC::Palette::EDITOR_SECONDARY + label "HELLO WORLD" + end + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/window.rb b/lib/window.rb index 2b8a7bd..c5015bb 100644 --- a/lib/window.rb +++ b/lib/window.rb @@ -12,5 +12,21 @@ module TAC def needs_cursor? true end + + def close + if @backend.config_changed? + push_state(Dialog::ConfirmDialog, title: "Are you sure?", message: "Config has unsaved changes!", callback_method: proc { cleanup_and_close }) + else + cleanup_and_close + end + end + + def cleanup_and_close + if @backend.tacnet.connected? + @backend.tacnet.close + end + + close! + end end end \ No newline at end of file diff --git a/timecrafters_action_configurator.rb b/timecrafters_action_configurator.rb index f1465a9..c44e90e 100644 --- a/timecrafters_action_configurator.rb +++ b/timecrafters_action_configurator.rb @@ -10,11 +10,14 @@ require_relative "lib/version" require_relative "lib/storage" require_relative "lib/backend" require_relative "lib/states/editor" +require_relative "lib/states/manage_presets" require_relative "lib/theme" require_relative "lib/logger" require_relative "lib/dialog" require_relative "lib/dialogs/alert_dialog" +require_relative "lib/dialogs/confirm_dialog" require_relative "lib/dialogs/name_prompt_dialog" +require_relative "lib/dialogs/variable_dialog" require_relative "lib/tacnet" require_relative "lib/tacnet/packet" require_relative "lib/tacnet/packet_handler"