mirror of
https://github.com/TimeCrafters/timecrafters_configuration_tool_desktop.git
synced 2025-12-17 14:22:34 +00:00
Compare commits
46 Commits
v0.1.0_ind
...
v0.4.1_bet
| Author | SHA1 | Date | |
|---|---|---|---|
| 34c607d1a1 | |||
| 9d75d2c09b | |||
| 606032c81d | |||
| 6185728ea7 | |||
| 02b16bab33 | |||
| 0facd003f0 | |||
| 93c6430028 | |||
| 3ee8881d43 | |||
| 81a827f0ac | |||
| 9b53bd8a70 | |||
| 2969e3df9e | |||
| 64d4f08410 | |||
| c24a9d9673 | |||
| 27bae38291 | |||
| 5fd494bb47 | |||
| 75d6de0b00 | |||
| 3cc4c204a7 | |||
| 9999026969 | |||
| 4b1fdd9baf | |||
| b625f53b6e | |||
| 8934462c46 | |||
| e27f1d1dca | |||
| ab55e8db5b | |||
| 988ef506ce | |||
| d340e94d46 | |||
| 9f54d21932 | |||
| 4e9d3c0759 | |||
| 8659bdfe4a | |||
| 22ab122604 | |||
| a1aa9a3396 | |||
| 8a61b6263b | |||
| 4adcff577a | |||
| d36211cb5e | |||
| 691cafb697 | |||
| fe480202ac | |||
| 28c159cc10 | |||
| af074ad57f | |||
| 5d267746dc | |||
| df0d0df223 | |||
| b811a83c60 | |||
| 9dc3caca0f | |||
| 08ada79e5b | |||
| d3fdc2d7dd | |||
| 1f37be3604 | |||
| f85426780b | |||
| 5e996019a9 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
|
pkg/*
|
||||||
data/**/*.json
|
data/**/*.json
|
||||||
data/settings.json
|
data/settings.json
|
||||||
data/simulator.rb
|
data/simulator.rb
|
||||||
|
data/*.csv
|
||||||
8
.rubocop.yml
Normal file
8
.rubocop.yml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Style/StringLiterals:
|
||||||
|
EnforcedStyle: double_quotes
|
||||||
|
|
||||||
|
Metrics/MethodLength:
|
||||||
|
Max: 40
|
||||||
|
|
||||||
|
Style/EmptyMethod:
|
||||||
|
EnforcedStyle: expanded
|
||||||
7
Gemfile
7
Gemfile
@@ -1,3 +1,10 @@
|
|||||||
source "https://rubygems.org"
|
source "https://rubygems.org"
|
||||||
|
|
||||||
gem "cyberarm_engine"
|
gem "cyberarm_engine"
|
||||||
|
gem "gosu_notifications"
|
||||||
|
gem "ffi"
|
||||||
|
gem "clipboard"
|
||||||
|
|
||||||
|
group :packaging do
|
||||||
|
gem "ocra"
|
||||||
|
end
|
||||||
@@ -1 +1 @@
|
|||||||
# TimeCrafters Action Configurator for Desktop
|
# TimeCrafters Configuration Tool for Desktop
|
||||||
|
|||||||
19
Rakefile
Executable file
19
Rakefile
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
require "releasy"
|
||||||
|
require 'bundler/setup' # Releasy requires that your application uses bundler.
|
||||||
|
require_relative "lib/version"
|
||||||
|
|
||||||
|
Releasy::Project.new do
|
||||||
|
name TAC::NAME
|
||||||
|
version TAC::VERSION
|
||||||
|
|
||||||
|
executable "timecrafters_configuration_tool.rb"
|
||||||
|
files ["lib/**/*.*", "media/**/*.*", "data"]
|
||||||
|
exclude_encoding # Applications that don't use advanced encoding (e.g. Japanese characters) can save build size with this.
|
||||||
|
verbose
|
||||||
|
|
||||||
|
add_build :windows_folder do
|
||||||
|
icon "media/icon.ico"
|
||||||
|
executable_type :console # Assuming you don't want it to run with a console window.
|
||||||
|
add_package :exe # Windows self-extracting archive.
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -14,6 +14,12 @@ module TAC
|
|||||||
@config.configuration.updated_at = Time.now
|
@config.configuration.updated_at = Time.now
|
||||||
@config.configuration.revision += 1
|
@config.configuration.revision += 1
|
||||||
@config_changed = true
|
@config_changed = true
|
||||||
|
|
||||||
|
save_config
|
||||||
|
|
||||||
|
if @tacnet.connected?
|
||||||
|
upload_config(@config.name)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def config_changed?
|
def config_changed?
|
||||||
@@ -26,24 +32,49 @@ module TAC
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def save_config(name)
|
def save_config(name = nil, json = nil)
|
||||||
json = @config.to_json
|
name = @config.name unless name
|
||||||
|
json = @config.to_json unless name && json
|
||||||
|
|
||||||
File.open("#{TAC::CONFIGS_PATH}/#{name}.json", "w") { |f| f.write json }
|
File.open("#{TAC::CONFIGS_PATH}/#{name}.json", "w") { |f| f.write json }
|
||||||
|
|
||||||
@config_changed = false
|
@config_changed = false
|
||||||
end
|
end
|
||||||
|
|
||||||
def upload_config
|
def move_config(old_name, new_name)
|
||||||
if @config && @tacnet.connected?
|
if not File.exists?("#{TAC::CONFIGS_PATH}/#{old_name}.json") or
|
||||||
json = @config.to_json
|
File.directory?("#{TAC::CONFIGS_PATH}/#{old_name}.json")
|
||||||
@tacnet.puts(TAC::TACNET::PacketHandler.packet_upload_config(json))
|
# move_config: Can not move config file "#{old_name}" does not exist!
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if File.exists?("#{TAC::CONFIGS_PATH}/#{new_name}.json") &&
|
||||||
|
!File.directory?("#{TAC::CONFIGS_PATH}/#{old_name}.json")
|
||||||
|
# move_config: Config file "#{new_name}" already exist!
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return FileUtils.mv(
|
||||||
|
"#{TAC::CONFIGS_PATH}/#{old_name}.json",
|
||||||
|
"#{TAC::CONFIGS_PATH}/#{new_name}.json"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_config(config_name)
|
||||||
|
FileUtils.rm("#{TAC::CONFIGS_PATH}/#{config_name}.json") if File.exists?("#{TAC::CONFIGS_PATH}/#{config_name}.json")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def upload_config(config_name)
|
||||||
|
if @tacnet.connected?
|
||||||
|
json = Config.new(config_name).to_json
|
||||||
|
@tacnet.puts( TAC::TACNET::PacketHandler.packet_upload_config(config_name, json) )
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def download_config
|
def download_config(config_name)
|
||||||
if @config && @tacnet.connected?
|
if @tacnet.connected?
|
||||||
@tacnet.puts(TAC::TACNET::PacketHandler.packet_download_config)
|
@tacnet.puts( TAC::TACNET::PacketHandler.packet_download_config(config_name) )
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -106,7 +137,7 @@ module TAC
|
|||||||
data: {
|
data: {
|
||||||
hostname: TACNET::DEFAULT_HOSTNAME,
|
hostname: TACNET::DEFAULT_HOSTNAME,
|
||||||
port: TACNET::DEFAULT_PORT,
|
port: TACNET::DEFAULT_PORT,
|
||||||
config: nil,
|
config: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
module TAC
|
module TAC
|
||||||
class Config
|
class Config
|
||||||
attr_reader :configuration, :groups, :presets
|
attr_reader :name, :configuration, :groups, :presets
|
||||||
def initialize(name)
|
def initialize(name)
|
||||||
|
@name = name
|
||||||
@configuration = nil
|
@configuration = nil
|
||||||
@groups = nil
|
@groups = nil
|
||||||
@presets = nil
|
@presets = nil
|
||||||
@@ -109,23 +110,24 @@ module TAC
|
|||||||
end
|
end
|
||||||
|
|
||||||
class Action
|
class Action
|
||||||
attr_accessor :name, :enabled
|
attr_accessor :name, :comment, :enabled
|
||||||
attr_reader :variables
|
attr_reader :variables
|
||||||
def initialize(name:, enabled:, variables:)
|
def initialize(name:, comment:, enabled:, variables:)
|
||||||
@name, @enabled = name, enabled
|
@name, @comment, @enabled = name, comment, enabled
|
||||||
@variables = variables
|
@variables = variables
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_json(*args)
|
def to_json(*args)
|
||||||
{
|
{
|
||||||
name: @name,
|
name: @name,
|
||||||
|
comment: @comment,
|
||||||
enabled: @enabled,
|
enabled: @enabled,
|
||||||
variables: @variables
|
variables: @variables
|
||||||
}.to_json(*args)
|
}.to_json(*args)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.from_json(hash)
|
def self.from_json(hash)
|
||||||
Action.new(name: hash[:name], enabled: hash[:enabled], variables: hash[:variables].map { |h| Variable.from_json(h) })
|
Action.new(name: hash[:name], comment: hash[:comment], enabled: hash[:enabled], variables: hash[:variables].map { |h| Variable.from_json(h) })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -138,13 +140,48 @@ module TAC
|
|||||||
def to_json(*args)
|
def to_json(*args)
|
||||||
{
|
{
|
||||||
name: @name,
|
name: @name,
|
||||||
type: @type,
|
value: "#{Variable.encode_type(@type)}x#{@value}"
|
||||||
value: @value
|
|
||||||
}.to_json(*args)
|
}.to_json(*args)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.from_json(hash)
|
def self.from_json(hash)
|
||||||
Variable.new(name: hash[:name], type: hash[:type].to_sym, value: hash[:value])
|
type, value = hash[:value].split("x", 2)
|
||||||
|
type = Variable.decode_type(type)
|
||||||
|
Variable.new(name: hash[:name], type: type, value: value)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.encode_type(symbol)
|
||||||
|
symbol.to_s.chars.first.upcase
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.decode_type(character)
|
||||||
|
case character.upcase
|
||||||
|
when "I"
|
||||||
|
:integer
|
||||||
|
when "F"
|
||||||
|
:float
|
||||||
|
when "D"
|
||||||
|
:double
|
||||||
|
when "L"
|
||||||
|
:long
|
||||||
|
when "S"
|
||||||
|
:string
|
||||||
|
when "B"
|
||||||
|
:boolean
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.decode_value(type, string)
|
||||||
|
case type
|
||||||
|
when "I", "L", :integer, :long
|
||||||
|
Integer(string)
|
||||||
|
when "F", "D", :float, :double
|
||||||
|
Float(string)
|
||||||
|
when "S", :string
|
||||||
|
string
|
||||||
|
when "B", :boolean
|
||||||
|
string.downcase == "true"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,35 +2,42 @@ module TAC
|
|||||||
class Dialog < CyberarmEngine::GuiState
|
class Dialog < CyberarmEngine::GuiState
|
||||||
def setup
|
def setup
|
||||||
theme(THEME)
|
theme(THEME)
|
||||||
background Gosu::Color.new(0x88_000000)
|
background Gosu::Color.new(0xaa_000000)
|
||||||
|
|
||||||
@title = @options[:title] ? @options[:title] : "#{self.class}"
|
@title = @options[:title] ? @options[:title] : "#{self.class}"
|
||||||
@window_width, @window_height = window.width, window.height
|
@window_width, @window_height = window.width, window.height
|
||||||
@previous_state = window.previous_state
|
@previous_state = window.previous_state
|
||||||
|
|
||||||
@dialog_root = stack width: 250, height: 400, border_thickness: 2, border_color: [TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_SECONDARY] do
|
@dialog_root = stack width: 0.25, border_thickness: 2, border_color: [TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_SECONDARY] do
|
||||||
# Title bar
|
# Title bar
|
||||||
flow width: 1.0, height: 0.1 do
|
@titlebar = flow width: 1.0 do
|
||||||
background [TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_SECONDARY]
|
background [TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_SECONDARY]
|
||||||
|
|
||||||
# title
|
# title
|
||||||
flow width: 0.855 do
|
flow width: 0.9 do
|
||||||
label @title, text_size: THEME_SUBHEADING_TEXT_SIZE
|
label @title, text_size: THEME_SUBHEADING_TEXT_SIZE, width: 1.0, text_align: :center, text_border: true, text_border_color: 0xff_222222, text_border_size: 1
|
||||||
end
|
end
|
||||||
|
|
||||||
# Buttons
|
# Buttons
|
||||||
flow width: 0.145 do
|
flow width: 0.0999 do
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/cross.png"), image_width: 24, **THEME_DANGER_BUTTON do
|
button get_image("#{TAC::ROOT_PATH}/media/icons/cross.png"), image_width: 1.0, **THEME_DANGER_BUTTON do
|
||||||
close
|
close
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Dialog body
|
# Dialog body
|
||||||
stack width: 1.0, height: 0.9 do
|
@dialog_content = stack width: 1.0 do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@dialog_content.clear do
|
||||||
build
|
build
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
@root_container.recalculate
|
||||||
|
@root_container.recalculate
|
||||||
|
@root_container.recalculate
|
||||||
|
|
||||||
center_dialog
|
center_dialog
|
||||||
end
|
end
|
||||||
@@ -39,8 +46,47 @@ module TAC
|
|||||||
end
|
end
|
||||||
|
|
||||||
def center_dialog
|
def center_dialog
|
||||||
@dialog_root.style.x = window.width / 2 - @dialog_root.style.width / 2
|
@dialog_root.style.x = window.width / 2 - @dialog_root.width / 2
|
||||||
@dialog_root.style.y = window.height / 2 - @dialog_root.style.height / 2
|
@dialog_root.style.y = window.height / 2 - @dialog_root.height / 2
|
||||||
|
end
|
||||||
|
|
||||||
|
def name_filter(text)
|
||||||
|
text.match(/[A-Za-z0-9._\- ]/) ? text : ""
|
||||||
|
end
|
||||||
|
|
||||||
|
def try_commit
|
||||||
|
end
|
||||||
|
|
||||||
|
def focus_next_element
|
||||||
|
elements = []
|
||||||
|
|
||||||
|
_deep_dive_interactive_elements(@dialog_content, elements)
|
||||||
|
|
||||||
|
element_index = elements.find_index(self.focused)
|
||||||
|
|
||||||
|
if element_index && elements.size.positive?
|
||||||
|
element = elements[element_index + 1]
|
||||||
|
element ||= elements.first
|
||||||
|
|
||||||
|
if element
|
||||||
|
request_focus(element)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def _deep_dive_interactive_elements(element, list)
|
||||||
|
element.children.each do |child|
|
||||||
|
if child.visible? && child.is_a?(CyberarmEngine::Element::EditLine) ||
|
||||||
|
child.is_a?(CyberarmEngine::Element::EditBox) ||
|
||||||
|
child.is_a?(CyberarmEngine::Element::CheckBox) ||
|
||||||
|
child.is_a?(CyberarmEngine::Element::ToggleButton) ||
|
||||||
|
child.is_a?(CyberarmEngine::Element::ListBox)
|
||||||
|
|
||||||
|
list << child
|
||||||
|
elsif child.visible? && child.is_a?(CyberarmEngine::Element::Container)
|
||||||
|
_deep_dive_interactive_elements(child, list)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def draw
|
def draw
|
||||||
@@ -54,10 +100,25 @@ module TAC
|
|||||||
super
|
super
|
||||||
|
|
||||||
if window.width != @window_width or window.height != @window_height
|
if window.width != @window_width or window.height != @window_height
|
||||||
center_dialog
|
request_recalculate
|
||||||
|
|
||||||
@window_width, @window_height = window.width, window.height
|
@window_width, @window_height = window.width, window.height
|
||||||
end
|
end
|
||||||
|
|
||||||
|
center_dialog
|
||||||
|
end
|
||||||
|
|
||||||
|
def button_down(id)
|
||||||
|
super
|
||||||
|
|
||||||
|
case id
|
||||||
|
when Gosu::KB_ENTER, Gosu::KB_RETURN
|
||||||
|
try_commit
|
||||||
|
when Gosu::KB_ESCAPE
|
||||||
|
close
|
||||||
|
when Gosu::KB_TAB
|
||||||
|
focus_next_element
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def close
|
def close
|
||||||
|
|||||||
69
lib/dialogs/action_dialog.rb
Normal file
69
lib/dialogs/action_dialog.rb
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
module TAC
|
||||||
|
class Dialog
|
||||||
|
class ActionDialog < Dialog
|
||||||
|
def build
|
||||||
|
background Gosu::Color::GRAY
|
||||||
|
|
||||||
|
label "Name", width: 1.0, text_align: :center
|
||||||
|
@name_error = label "Error", color: TAC::Palette::TACNET_CONNECTION_ERROR
|
||||||
|
@name_error.hide
|
||||||
|
@name = edit_line @options[:action] ? @options[:action].name : "", filter: method(:name_filter), width: 1.0, autofocus: true
|
||||||
|
@name.subscribe(:changed) do |sender, value|
|
||||||
|
valid?
|
||||||
|
end
|
||||||
|
|
||||||
|
label "Comment", width: 1.0, text_align: :center
|
||||||
|
@comment = edit_line @options[:action] ? @options[:action].comment : "", width: 1.0
|
||||||
|
|
||||||
|
flow width: 1.0, margin_top: THEME_DIALOG_BUTTON_PADDING do
|
||||||
|
button "Cancel", width: 0.5 do
|
||||||
|
close
|
||||||
|
end
|
||||||
|
|
||||||
|
button @options[:action] ? @options[:accept_label] ? @options[:accept_label] : "Update" : "Add", width: 0.5 do |b|
|
||||||
|
try_commit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def try_commit
|
||||||
|
if valid?
|
||||||
|
if @options[:action]
|
||||||
|
@options[:callback_method].call(@options[:action], @name.value.strip, @comment.value.strip)
|
||||||
|
else
|
||||||
|
@options[:callback_method].call(@name.value.strip, @comment.value.strip)
|
||||||
|
end
|
||||||
|
|
||||||
|
close
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid?
|
||||||
|
valid = true
|
||||||
|
name = @name.value.strip
|
||||||
|
|
||||||
|
if name.empty?
|
||||||
|
@name_error.value = "Error: Name cannot be blank or only whitespace."
|
||||||
|
@name_error.show
|
||||||
|
valid = false
|
||||||
|
|
||||||
|
### TODO: Handle case when renaming a cloned Action
|
||||||
|
elsif !@options[:cloning] && @options[:action] && @options[:action].name == name
|
||||||
|
@name_error.value = ""
|
||||||
|
@name_error.hide
|
||||||
|
|
||||||
|
elsif @options[:list].find { |action| action.name == name}
|
||||||
|
@name_error.value = "Error: Name is not unique!"
|
||||||
|
@name_error.show
|
||||||
|
valid = false
|
||||||
|
|
||||||
|
else
|
||||||
|
@name_error.value = ""
|
||||||
|
@name_error.hide
|
||||||
|
end
|
||||||
|
|
||||||
|
return valid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -5,10 +5,14 @@ module TAC
|
|||||||
background Gosu::Color::GRAY
|
background Gosu::Color::GRAY
|
||||||
label @options[:message]
|
label @options[:message]
|
||||||
|
|
||||||
button "Close", width: 1.0 do
|
button "Close", width: 1.0, margin_top: THEME_DIALOG_BUTTON_PADDING do
|
||||||
|
try_commit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def try_commit
|
||||||
close
|
close
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
end
|
||||||
@@ -2,14 +2,34 @@ module TAC
|
|||||||
class Dialog
|
class Dialog
|
||||||
class ConfirmDialog < Dialog
|
class ConfirmDialog < Dialog
|
||||||
def build
|
def build
|
||||||
|
@dangerous = @options[:dangerous]
|
||||||
|
@dangerous ||= false
|
||||||
|
|
||||||
|
color = @dangerous ? Palette::DANGEROUS : Palette::ALERT
|
||||||
|
|
||||||
|
|
||||||
|
@dialog_root.style.border_color = [ color, darken(color, 50) ]
|
||||||
|
@titlebar.style.background = [ color, darken(color, 50) ]
|
||||||
|
|
||||||
background Gosu::Color::GRAY
|
background Gosu::Color::GRAY
|
||||||
label @options[:message]
|
label @options[:message]
|
||||||
|
|
||||||
flow width: 1.0 do
|
flow width: 1.0, margin_top: THEME_DIALOG_BUTTON_PADDING do
|
||||||
button "Cancel", width: 0.475 do
|
button "Cancel", width: 0.5 do
|
||||||
close
|
close
|
||||||
end
|
end
|
||||||
button "Okay", width: 0.475 do
|
|
||||||
|
button "Proceed", width: 0.5, **TAC::THEME_DANGER_BUTTON do
|
||||||
|
try_commit(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def try_commit(force = false)
|
||||||
|
if !@dangerous
|
||||||
|
close
|
||||||
|
|
||||||
|
@options[:callback_method].call
|
||||||
|
elsif @dangerous && force
|
||||||
close
|
close
|
||||||
|
|
||||||
@options[:callback_method].call
|
@options[:callback_method].call
|
||||||
|
|||||||
@@ -5,28 +5,31 @@ module TAC
|
|||||||
|
|
||||||
def build
|
def build
|
||||||
background Gosu::Color::GRAY
|
background Gosu::Color::GRAY
|
||||||
flow width: 1.0 do
|
label "Name", width: 1.0, text_align: :center
|
||||||
label "Name", width: 0.25
|
|
||||||
@name = edit_line @options[:renaming] ? @options[:renaming].name : "", filter: method(:filter), width: 0.70
|
|
||||||
end
|
|
||||||
@name_error = label "", color: TAC::Palette::TACNET_CONNECTION_ERROR
|
@name_error = label "", color: TAC::Palette::TACNET_CONNECTION_ERROR
|
||||||
@name_error.hide
|
@name_error.hide
|
||||||
|
@name = edit_line @options[:renaming] ? @options[:renaming].name : "", filter: method(:name_filter), width: 1.0, autofocus: true
|
||||||
|
|
||||||
@name.subscribe(:changed) do |sender, value|
|
@name.subscribe(:changed) do |sender, value|
|
||||||
valid?
|
valid?
|
||||||
end
|
end
|
||||||
|
|
||||||
flow width: 1.0 do
|
flow width: 1.0, margin_top: THEME_DIALOG_BUTTON_PADDING do
|
||||||
button "Cancel", width: 0.475 do
|
button "Cancel", width: 0.5 do
|
||||||
close
|
close
|
||||||
end
|
end
|
||||||
|
|
||||||
accept_label = @options[:renaming] ? "Update" : "Add"
|
accept_label = @options[:renaming] ? "Update" : "Add"
|
||||||
accept_label = @options[:accept_label] if @options[:accept_label]
|
accept_label = @options[:accept_label] if @options[:accept_label]
|
||||||
|
|
||||||
button accept_label, width: 0.475 do
|
button accept_label, width: 0.5 do
|
||||||
unless valid?
|
try_commit
|
||||||
else
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def try_commit
|
||||||
|
if valid?
|
||||||
if @options[:renaming]
|
if @options[:renaming]
|
||||||
@options[:callback_method].call(@options[:renaming], @name.value.strip)
|
@options[:callback_method].call(@options[:renaming], @name.value.strip)
|
||||||
else
|
else
|
||||||
@@ -36,16 +39,24 @@ module TAC
|
|||||||
close
|
close
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def valid?
|
def valid?
|
||||||
|
name = @name.value.strip
|
||||||
|
|
||||||
if @name.value.strip.empty?
|
if @name.value.strip.empty?
|
||||||
@name_error.value = "Name cannot be blank.\nName cannot only be whitespace."
|
@name_error.value = "Name cannot be blank. Name cannot only be whitespace."
|
||||||
@name_error.show
|
@name_error.show
|
||||||
|
|
||||||
return false
|
return false
|
||||||
elsif @options[:list] && @options[:list].find { |i| i.name == @name.value.strip }
|
|
||||||
|
### TODO: Handle case when renaming a cloned Group
|
||||||
|
# elsif @options[:renaming] && @options[:renaming].name == name
|
||||||
|
# @name_error.value = ""
|
||||||
|
# @name_error.hide
|
||||||
|
|
||||||
|
# return true
|
||||||
|
|
||||||
|
elsif @options[:list] && @options[:list].find { |i| i.name == name }
|
||||||
@name_error.value = "Name is not unique!"
|
@name_error.value = "Name is not unique!"
|
||||||
@name_error.show
|
@name_error.show
|
||||||
|
|
||||||
@@ -57,10 +68,6 @@ module TAC
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter(text)
|
|
||||||
text.match(/[A-Za-z0-9._\- ]/) ? text : ""
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
26
lib/dialogs/pick_preset_dialog.rb
Normal file
26
lib/dialogs/pick_preset_dialog.rb
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
module TAC
|
||||||
|
class Dialog
|
||||||
|
class PickPresetDialog < Dialog
|
||||||
|
def build
|
||||||
|
@limit = @options[:limit]
|
||||||
|
|
||||||
|
list = window.backend.config.presets.groups if @limit == :groups
|
||||||
|
list = window.backend.config.presets.actions if @limit == :actions
|
||||||
|
|
||||||
|
background Gosu::Color::GRAY
|
||||||
|
|
||||||
|
stack(width: 1.0, height: 512, scroll: true) do
|
||||||
|
list.each do |item|
|
||||||
|
button item.name, width: 1.0 do
|
||||||
|
close
|
||||||
|
@options[:callback_method].call(item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def try_commit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
29
lib/dialogs/tacnet_dialog.rb
Normal file
29
lib/dialogs/tacnet_dialog.rb
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
module TAC
|
||||||
|
class Dialog
|
||||||
|
class TACNETDialog < Dialog
|
||||||
|
def build
|
||||||
|
@dialog_root.style.border_color = [ Palette::TACNET_PRIMARY, Palette::TACNET_SECONDARY ]
|
||||||
|
@titlebar.style.background = [ Palette::TACNET_PRIMARY, Palette::TACNET_SECONDARY ]
|
||||||
|
|
||||||
|
background Gosu::Color::GRAY
|
||||||
|
label @options[:message], width: 1.0
|
||||||
|
|
||||||
|
@sound = Gosu::Sample.new("#{TAC::ROOT_PATH}/media/error_alarm.ogg").play(1, 1, true)
|
||||||
|
|
||||||
|
button "Close", width: 1.0, margin_top: THEME_DIALOG_BUTTON_PADDING do
|
||||||
|
try_commit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def try_commit
|
||||||
|
close
|
||||||
|
end
|
||||||
|
|
||||||
|
def close
|
||||||
|
super
|
||||||
|
|
||||||
|
@sound.stop
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
28
lib/dialogs/tacnet_status_dialog.rb
Normal file
28
lib/dialogs/tacnet_status_dialog.rb
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
module TAC
|
||||||
|
class Dialog
|
||||||
|
class TACNETStatusDialog < Dialog
|
||||||
|
def build
|
||||||
|
background Gosu::Color::GRAY
|
||||||
|
@message_label = label $window.backend.tacnet.full_status
|
||||||
|
|
||||||
|
button "Close", width: 1.0, margin_top: THEME_DIALOG_BUTTON_PADDING do
|
||||||
|
try_commit
|
||||||
|
end
|
||||||
|
|
||||||
|
@timer = CyberarmEngine::Timer.new(1000.0) do
|
||||||
|
@message_label.value = $window.backend.tacnet.full_status
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def try_commit
|
||||||
|
close
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
super
|
||||||
|
|
||||||
|
@timer.update
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -6,55 +6,82 @@ module TAC
|
|||||||
|
|
||||||
@type = @options[:variable].type if @options[:variable]
|
@type = @options[:variable].type if @options[:variable]
|
||||||
|
|
||||||
label "Name"
|
label "Name", width: 1.0, text_align: :center
|
||||||
@name_error = label "Error", color: TAC::Palette::TACNET_CONNECTION_ERROR
|
@name_error = label "Error", color: TAC::Palette::TACNET_CONNECTION_ERROR
|
||||||
@name_error.hide
|
@name_error.hide
|
||||||
@name = edit_line @options[:variable] ? @options[:variable].name : ""
|
@name = edit_line @options[:variable] ? @options[:variable].name : "", filter: method(:name_filter), width: 1.0, autofocus: true
|
||||||
|
@name.subscribe(:changed) do |sender, value|
|
||||||
|
valid?
|
||||||
|
end
|
||||||
|
|
||||||
label "Type"
|
label "Type", width: 1.0, text_align: :center
|
||||||
@type_error = label "Error", color: TAC::Palette::TACNET_CONNECTION_ERROR
|
@type_error = label "Error", color: TAC::Palette::TACNET_CONNECTION_ERROR
|
||||||
@type_error.hide
|
@type_error.hide
|
||||||
# TODO: Add dropdown menus to CyberarmEngine
|
|
||||||
flow width: 1.0 do
|
@var_type = list_box items: [:float, :double, :integer, :long, :string, :boolean], choose: @type ? @type : :double, width: 1.0 do |item|
|
||||||
[:float, :double, :integer, :long, :string, :boolean].each do |btn|
|
@type = item.value.to_sym
|
||||||
button btn do
|
|
||||||
@type = btn
|
if @type == :boolean
|
||||||
@value_container.show
|
@value.hide
|
||||||
end
|
@value_boolean.show
|
||||||
|
else
|
||||||
|
@value.show
|
||||||
|
@value_boolean.hide
|
||||||
end
|
end
|
||||||
|
|
||||||
|
valid?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@type ||= @var_type.value.to_sym
|
||||||
|
|
||||||
@value_container = stack width: 1.0 do
|
@value_container = stack width: 1.0 do
|
||||||
label "Value"
|
label "Value", width: 1.0, text_align: :center
|
||||||
@value_error = label "Error", color: TAC::Palette::TACNET_CONNECTION_ERROR
|
@value_error = label "Error", color: TAC::Palette::TACNET_CONNECTION_ERROR
|
||||||
@value_error.hide
|
@value_error.hide
|
||||||
@value = edit_line @options[:variable] ? @options[:variable].value : ""
|
@value = edit_line @options[:variable] ? @options[:variable].value : "", width: 1.0
|
||||||
|
@value_boolean = check_box "Boolean", checked: @options[:variable] ? @options[:variable].value == "true" : false
|
||||||
|
|
||||||
|
@value.subscribe(:changed) do |sender, value|
|
||||||
|
valid?
|
||||||
end
|
end
|
||||||
|
|
||||||
flow width: 1.0 do
|
unless @options[:variable] && @options[:variable].type == :boolean
|
||||||
button "Cancel", width: 0.475 do
|
@value_boolean.hide
|
||||||
close
|
|
||||||
end
|
|
||||||
|
|
||||||
button @options[:variable] ? "Update" : "Add", width: 0.475 do |b|
|
|
||||||
if valid?
|
|
||||||
if @options[:variable]
|
|
||||||
@options[:callback_method].call(@options[:variable], @name.value.strip, @type, @value.value.strip)
|
|
||||||
else
|
else
|
||||||
@options[:callback_method].call(@name.value.strip, @type, @value.value.strip)
|
@value.hide
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
flow width: 1.0, margin_top: THEME_DIALOG_BUTTON_PADDING do
|
||||||
|
button "Cancel", width: 0.5 do
|
||||||
|
close
|
||||||
|
end
|
||||||
|
|
||||||
|
button @options[:variable] ? "Update" : "Add", width: 0.5 do |b|
|
||||||
|
try_commit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def try_commit
|
||||||
|
if valid?
|
||||||
|
value = @type == :boolean ? @value_boolean.value.to_s : @value.value.strip
|
||||||
|
|
||||||
|
if @options[:variable]
|
||||||
|
@options[:callback_method].call(@options[:variable], @name.value.strip, @type, value)
|
||||||
|
else
|
||||||
|
@options[:callback_method].call(@name.value.strip, @type, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
close
|
close
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def valid?
|
def valid?
|
||||||
valid = true
|
valid = true
|
||||||
|
|
||||||
if @name.value.strip.empty?
|
if @name.value.strip.empty?
|
||||||
@name_error.value = "Error: Name cannot be blank\n or only whitespace."
|
@name_error.value = "Error: Name cannot be blank or only whitespace."
|
||||||
@name_error.show
|
@name_error.show
|
||||||
valid = false
|
valid = false
|
||||||
else
|
else
|
||||||
@@ -73,22 +100,24 @@ module TAC
|
|||||||
|
|
||||||
if [:integer, :float, :double, :long].include?(@type)
|
if [:integer, :float, :double, :long].include?(@type)
|
||||||
if @value.value.strip.empty?
|
if @value.value.strip.empty?
|
||||||
@value_error.value = "Error: Value cannot be blank\n or only whitespace."
|
@value_error.value = "Error: Numeric value cannot be blank or only whitespace."
|
||||||
@value_error.show
|
@value_error.show
|
||||||
valid = false
|
valid = false
|
||||||
|
|
||||||
elsif [:integer, :long].include?(@type)
|
elsif [:integer, :long].include?(@type)
|
||||||
begin
|
begin
|
||||||
Integer(@value.value.strip)
|
Integer(@value.value.strip)
|
||||||
rescue
|
rescue
|
||||||
@value_error.value = "Error: Invalid value,\nexpected whole number."
|
@value_error.value = "Error: Invalid value, expected whole number."
|
||||||
@value_error.show
|
@value_error.show
|
||||||
valid = false
|
valid = false
|
||||||
end
|
end
|
||||||
|
|
||||||
elsif [:float, :double].include?(@type)
|
elsif [:float, :double].include?(@type)
|
||||||
begin
|
begin
|
||||||
Float(@value.value.strip)
|
Float(@value.value.strip)
|
||||||
rescue
|
rescue
|
||||||
@value_error.value = "Error: Invalid value,\nexpected decimal number."
|
@value_error.value = "Error: Invalid value, expected decimal number."
|
||||||
@value_error.show
|
@value_error.show
|
||||||
valid = false
|
valid = false
|
||||||
end
|
end
|
||||||
@@ -96,18 +125,18 @@ module TAC
|
|||||||
@value_error.value = ""
|
@value_error.value = ""
|
||||||
@value_error.hide
|
@value_error.hide
|
||||||
end
|
end
|
||||||
|
|
||||||
elsif @type == :string
|
elsif @type == :string
|
||||||
if @value.value.strip.empty?
|
if @value.value.strip.empty?
|
||||||
@value_error.value = "Error: Value cannot be blank\n or only whitespace."
|
@value_error.value = "Error: Value cannot be blank or only whitespace."
|
||||||
@value_error.show
|
@value_error.show
|
||||||
valid = false
|
valid = false
|
||||||
end
|
end
|
||||||
|
|
||||||
elsif @type == :boolean
|
elsif @type == :boolean
|
||||||
@value_error.value = "Error: Boolean not yet supported."
|
|
||||||
@value_error.show
|
|
||||||
valid = false
|
|
||||||
else
|
else
|
||||||
@value_error.value = "Error: Type not set."
|
@value_error.value = "Error: Type not set or type #{@type.inspect} is not valid."
|
||||||
@value_error.show
|
@value_error.show
|
||||||
valid = false
|
valid = false
|
||||||
end
|
end
|
||||||
|
|||||||
51
lib/page.rb
Normal file
51
lib/page.rb
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
module TAC
|
||||||
|
class Page
|
||||||
|
include CyberarmEngine::DSL
|
||||||
|
include CyberarmEngine::Common
|
||||||
|
|
||||||
|
attr_reader :menu_bar, :status_bar, :body
|
||||||
|
|
||||||
|
def initialize(host:)
|
||||||
|
@host = host
|
||||||
|
@header_bar_label = host.header_bar_label
|
||||||
|
@menu_bar = host.menu_bar
|
||||||
|
@status_bar = host.status_bar
|
||||||
|
@body = host.body
|
||||||
|
|
||||||
|
@options = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def options=(options)
|
||||||
|
@options = options
|
||||||
|
end
|
||||||
|
|
||||||
|
def page(klass, options = {})
|
||||||
|
@host.page(klass, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def header_bar(text)
|
||||||
|
@header_bar_label.value = text
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup
|
||||||
|
end
|
||||||
|
|
||||||
|
def focus
|
||||||
|
end
|
||||||
|
|
||||||
|
def blur
|
||||||
|
end
|
||||||
|
|
||||||
|
def draw
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
end
|
||||||
|
|
||||||
|
def button_down(id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def button_up(id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,34 +1,11 @@
|
|||||||
module TAC
|
module TAC
|
||||||
class States
|
class Pages
|
||||||
class ManageConfigurations < CyberarmEngine::GuiState
|
class Configurations < Page
|
||||||
def setup
|
def setup
|
||||||
theme(THEME)
|
header_bar("Manage Configurations")
|
||||||
stack width: 1.0, height: 0.1 do
|
|
||||||
background THEME_HEADER_BACKGROUND
|
|
||||||
label "#{TAC::NAME} ― Manage Configurations", bold: true, text_size: THEME_HEADING_TEXT_SIZE
|
|
||||||
flow do
|
|
||||||
button "Close" do
|
|
||||||
if window.backend.settings.config
|
|
||||||
window.backend.load_config(window.backend.settings.config)
|
|
||||||
|
|
||||||
pop_state
|
menu_bar.clear do
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_height: 1.0, tip: "Add configuration" do
|
||||||
window.current_state.refresh_config
|
|
||||||
else
|
|
||||||
push_state(Dialog::AlertDialog, title: "No Config Loaded", message: "A config must be loaded.")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
label "Current Configuration: "
|
|
||||||
@config_label = label window.backend.settings.config
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
stack width: 1.0, height: 0.9 do
|
|
||||||
background THEME_CONTENT_BACKGROUND
|
|
||||||
flow do
|
|
||||||
label "Configurations", text_size: THEME_SUBHEADING_TEXT_SIZE
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_width: 18, tip: "Add configuration" do
|
|
||||||
push_state(Dialog::NamePromptDialog, title: "Config Name", callback_method: proc { |name|
|
push_state(Dialog::NamePromptDialog, title: "Config Name", callback_method: proc { |name|
|
||||||
window.backend.write_new_config(name)
|
window.backend.write_new_config(name)
|
||||||
|
|
||||||
@@ -38,7 +15,13 @@ module TAC
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@configs_list = stack width: 1.0 do
|
status_bar.clear do
|
||||||
|
label "Current Configuration: "
|
||||||
|
@config_label = label window.backend.settings.config
|
||||||
|
end
|
||||||
|
|
||||||
|
body.clear do
|
||||||
|
@configs_list = stack width: 1.0, height: 1.0, scroll: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -58,6 +41,10 @@ module TAC
|
|||||||
|
|
||||||
button "#{name}", width: 0.94 do
|
button "#{name}", width: 0.94 do
|
||||||
change_config(name)
|
change_config(name)
|
||||||
|
|
||||||
|
if window.backend.tacnet.connected?
|
||||||
|
window.backend.tacnet.puts(TAC::TACNET::PacketHandler.packet_select_config(name))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/gear.png"), image_width: THEME_ICON_SIZE, tip: "Rename configuration" do
|
button get_image("#{TAC::ROOT_PATH}/media/icons/gear.png"), image_width: THEME_ICON_SIZE, tip: "Rename configuration" do
|
||||||
@@ -74,17 +61,21 @@ module TAC
|
|||||||
|
|
||||||
populate_configs
|
populate_configs
|
||||||
else
|
else
|
||||||
push_state(Dialog::AlertDialog, title: "Config Rename Failed", message: "File already exists at\n#{TAC::CONFIGS_PATH}/#{new_name}.json}")
|
push_state(Dialog::AlertDialog, title: "Config Rename Failed", message: "File already exists at #{TAC::CONFIGS_PATH}/#{new_name}.json}")
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/trashcan.png"), image_width: THEME_ICON_SIZE, **THEME_DANGER_BUTTON, tip: "Delete configuration" do
|
button get_image("#{TAC::ROOT_PATH}/media/icons/trashcan.png"), image_width: THEME_ICON_SIZE, **THEME_DANGER_BUTTON, tip: "Delete configuration" do
|
||||||
push_state(Dialog::ConfirmDialog, title: "Delete Config?", callback_method: proc {
|
push_state(Dialog::ConfirmDialog, title: "Delete Config?", dangerous: true, callback_method: proc {
|
||||||
File.delete("#{TAC::CONFIGS_PATH}/#{name}.json")
|
File.delete("#{TAC::CONFIGS_PATH}/#{name}.json")
|
||||||
|
|
||||||
if window.backend.settings.config == name
|
if window.backend.settings.config == name
|
||||||
change_config(nil)
|
change_config("")
|
||||||
|
end
|
||||||
|
|
||||||
|
if window.backend.tacnet.connected?
|
||||||
|
window.backend.tacnet.puts(TAC::TACNET::PacketHandler.packet_delete_config(name))
|
||||||
end
|
end
|
||||||
|
|
||||||
populate_configs
|
populate_configs
|
||||||
@@ -98,6 +89,7 @@ module TAC
|
|||||||
def change_config(name)
|
def change_config(name)
|
||||||
window.backend.settings.config = name
|
window.backend.settings.config = name
|
||||||
window.backend.save_settings
|
window.backend.save_settings
|
||||||
|
window.backend.load_config(name)
|
||||||
|
|
||||||
@config_label.value = name.to_s
|
@config_label.value = name.to_s
|
||||||
end
|
end
|
||||||
204
lib/pages/drive_team_rotation_generator.rb
Normal file
204
lib/pages/drive_team_rotation_generator.rb
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
module TAC
|
||||||
|
class Pages
|
||||||
|
class DriveTeamRotationGenerator < Page
|
||||||
|
FILENAME = "#{TAC::ROOT_PATH}/data/drive_team_rotation.csv"
|
||||||
|
|
||||||
|
def setup
|
||||||
|
header_bar("Drive Team Rotation Generator")
|
||||||
|
|
||||||
|
@roster ||= [
|
||||||
|
"Alexander",
|
||||||
|
"Aubrey",
|
||||||
|
"Cayden",
|
||||||
|
"Gabe",
|
||||||
|
"Spencer",
|
||||||
|
"Olivia"
|
||||||
|
]
|
||||||
|
|
||||||
|
@roles ||= [
|
||||||
|
"Coach",
|
||||||
|
"Driver A",
|
||||||
|
"Driver B"
|
||||||
|
]
|
||||||
|
|
||||||
|
menu_bar.clear do
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/save.png"), image_height: 1.0, tip: "Export rotation as Comma-Seperated Values" do
|
||||||
|
export_rotation
|
||||||
|
|
||||||
|
@status_bar.clear do
|
||||||
|
tagline "Saved to: #{FILENAME}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/target.png"), margin_right: 10, image_height: 1.0, tip: "Generate rotation" do
|
||||||
|
populate_rotation
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
body.clear do
|
||||||
|
flow(margin_left: 20, width: 1.0, height: 1.0) do
|
||||||
|
stack(width: 0.25) do
|
||||||
|
title "Roles", width: 1.0, margin_bottom: 4, text_align: :center
|
||||||
|
|
||||||
|
flow(width: 1.0, margin_bottom: 20) do
|
||||||
|
@role_name = edit_line "", placeholder: "Add role", width: 0.9
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_width: 0.1 do
|
||||||
|
if @role_name.value.strip.length.positive?
|
||||||
|
@roles.push(@role_name.value.strip)
|
||||||
|
@role_name.value = ""
|
||||||
|
|
||||||
|
populate_roles
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@roles_container = stack(width: 1.0, height: 0.835, scroll: true) do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
stack(margin_left: 20, width: 0.25, height: 1.0) do
|
||||||
|
title "Roster", width: 1.0, margin_bottom: 4, text_align: :center
|
||||||
|
|
||||||
|
flow(width: 1.0, margin_bottom: 20) do
|
||||||
|
@roster_name = edit_line "", placeholder: "Add name", width: 0.9
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_width: 0.1 do
|
||||||
|
if @roster_name.value.strip.length.positive?
|
||||||
|
@roster.push(@roster_name.value.strip)
|
||||||
|
@roster_name.value = ""
|
||||||
|
|
||||||
|
populate_roster
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@roster_container = stack(width: 1.0, height: 0.835, scroll: true) do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
stack(margin_left: 20, margin_right: 20, width: 0.5, height: 1.0) do
|
||||||
|
title "Rotation", width: 1.0, margin_bottom: 4, text_align: :center
|
||||||
|
|
||||||
|
@rotation_container = stack(width: 1.0, height: 0.835, scroll: true) do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
populate_roles
|
||||||
|
populate_roster
|
||||||
|
populate_rotation
|
||||||
|
end
|
||||||
|
|
||||||
|
def populate_roles
|
||||||
|
@roles_container.clear do
|
||||||
|
@roles.each_with_index do |name, i|
|
||||||
|
flow(width: 1.0, padding: 2) do
|
||||||
|
background i.even? ? 0xff_007000 : 0xff_006000
|
||||||
|
|
||||||
|
tagline name, width: 0.9
|
||||||
|
button "<b>X</b>", width: 0.1, text_size: 18, **THEME_DANGER_BUTTON do
|
||||||
|
@roles.delete(name)
|
||||||
|
populate_roles
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def populate_roster
|
||||||
|
@roster_container.clear do
|
||||||
|
@roster.each_with_index do |name, i|
|
||||||
|
flow(width: 1.0, padding: 2) do
|
||||||
|
background i.even? ? 0xff_007000 : 0xff_006000
|
||||||
|
|
||||||
|
tagline name, width: 0.9
|
||||||
|
button "<b>X</b>", width: 0.1, text_size: 18, **THEME_DANGER_BUTTON do
|
||||||
|
@roster.delete(name)
|
||||||
|
populate_roster
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def populate_rotation
|
||||||
|
@rotation = Generator.new(roster: @roster, team_size: @roles.count)
|
||||||
|
|
||||||
|
@rotation_container.clear do
|
||||||
|
fraction = (1.0 / @rotation.team_size) - 0.02
|
||||||
|
|
||||||
|
flow(width: 1.0, padding: 2) do
|
||||||
|
background Gosu::Color::BLACK
|
||||||
|
|
||||||
|
@roles.each do |role|
|
||||||
|
tagline "<b>#{role}</b>", width: fraction
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
teams = @rotation.teams unless @shuffle_teams&.value
|
||||||
|
teams = @rotation.teams.shuffle if @shuffle_teams&.value
|
||||||
|
|
||||||
|
teams.each_with_index do |team, i|
|
||||||
|
flow(width: 1.0, padding: 2) do
|
||||||
|
background i.even? ? 0xff_007000 : 0xff_006000
|
||||||
|
|
||||||
|
team.each do |player|
|
||||||
|
tagline player, width: fraction
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def export_rotation
|
||||||
|
return unless @rotation
|
||||||
|
|
||||||
|
buff = "#{@roles.join(',')}\n"
|
||||||
|
|
||||||
|
@rotation.teams.each do |team|
|
||||||
|
buff += "#{team.join(",")}\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
buff.strip
|
||||||
|
|
||||||
|
File.write(FILENAME, buff)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Generator
|
||||||
|
attr_reader :roster, :team_size, :rounds, :teams, :schedule
|
||||||
|
|
||||||
|
def initialize(roster:, team_size: 4, rounds: 6)
|
||||||
|
@roster = roster.clone
|
||||||
|
@roster.freeze
|
||||||
|
@team_size = team_size
|
||||||
|
@rounds = rounds
|
||||||
|
|
||||||
|
@teams = []
|
||||||
|
@schedule = []
|
||||||
|
|
||||||
|
generate
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate
|
||||||
|
generate_teams
|
||||||
|
generate_round_robin
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_teams
|
||||||
|
return unless @roster.size >= @team_size
|
||||||
|
|
||||||
|
list = @roster.dup
|
||||||
|
|
||||||
|
list.size.times do
|
||||||
|
list.rotate!
|
||||||
|
|
||||||
|
@teams << list[0..@team_size - 1]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_round_robin
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
420
lib/pages/editor.rb
Normal file
420
lib/pages/editor.rb
Normal file
@@ -0,0 +1,420 @@
|
|||||||
|
module TAC
|
||||||
|
class Pages
|
||||||
|
class Editor < Page
|
||||||
|
def setup
|
||||||
|
header_bar("Editor")
|
||||||
|
@active_group = nil
|
||||||
|
@active_action = nil
|
||||||
|
|
||||||
|
menu_bar.clear do
|
||||||
|
if @options[:group_is_preset]
|
||||||
|
title "Editing group preset: #{@options[:group].name}"
|
||||||
|
elsif @options[:action_is_preset]
|
||||||
|
title "Editing action preset: #{@options[:action].name}"
|
||||||
|
else
|
||||||
|
title "Editing configuration: #{window.backend.config.name}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
status_bar.clear do
|
||||||
|
flow(width: 0.3333) do
|
||||||
|
label "Active group:", margin_right: 20
|
||||||
|
@active_group_label = label ""
|
||||||
|
end
|
||||||
|
|
||||||
|
flow(width: 0.3333) do
|
||||||
|
label "Active action:", margin_right: 20
|
||||||
|
@active_action_label = label ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
body.clear do
|
||||||
|
flow(width: 1.0, height: 1.0) do
|
||||||
|
stack width: 0.33333, height: 1.0, border_thickness_right: 1, border_color: [0, Gosu::Color::BLACK, 0, 0] do
|
||||||
|
@groups_menu = flow(width: 1.0) do
|
||||||
|
label "Groups", text_size: THEME_SUBHEADING_TEXT_SIZE
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_width: THEME_ICON_SIZE, tip: "Add group" do
|
||||||
|
push_state(TAC::Dialog::NamePromptDialog, title: "Create Group", list: window.backend.config.groups, callback_method: method(:create_group))
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/button2.png"), image_width: THEME_ICON_SIZE, tip: "Clone currently selected group" do
|
||||||
|
if @active_group
|
||||||
|
push_state(Dialog::NamePromptDialog, title: "Clone Group", renaming: @active_group, accept_label: "Clone", list: window.backend.config.groups, callback_method: proc { |group, name|
|
||||||
|
clone = TAC::Config::Group.from_json( JSON.parse( @active_group.to_json, symbolize_names: true ))
|
||||||
|
clone.name = "#{name}"
|
||||||
|
window.backend.config.groups << clone
|
||||||
|
window.backend.config_changed!
|
||||||
|
|
||||||
|
populate_groups_list
|
||||||
|
})
|
||||||
|
else
|
||||||
|
push_state(TAC::Dialog::AlertDialog, title: "Error", message: "Unable to clone group, no group selected.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/save.png"), image_width: THEME_ICON_SIZE, tip: "Save group as preset" do
|
||||||
|
if @active_group
|
||||||
|
push_state(Dialog::NamePromptDialog, title: "Save Group Preset", renaming: @active_group, accept_label: "Save", list: window.backend.config.presets.groups, callback_method: proc { |group, name|
|
||||||
|
clone = TAC::Config::Group.from_json( JSON.parse( @active_group.to_json, symbolize_names: true ))
|
||||||
|
clone.name = "#{name}"
|
||||||
|
window.backend.config.presets.groups << clone
|
||||||
|
window.backend.config.presets.groups.sort_by! { |g| g.name.downcase }
|
||||||
|
window.backend.config_changed!
|
||||||
|
|
||||||
|
window.toast("Saved Group Preset", "Saved preset: #{name}")
|
||||||
|
})
|
||||||
|
else
|
||||||
|
push_state(TAC::Dialog::AlertDialog, title: "Error", message: "Unable to create group preset, no group selected.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/import.png"), image_width: THEME_ICON_SIZE, tip: "Import group from preset" do
|
||||||
|
push_state(Dialog::PickPresetDialog, title: "Pick Group Preset", limit: :groups, callback_method: proc { |preset|
|
||||||
|
push_state(Dialog::NamePromptDialog, title: "Name Group", renaming: preset, accept_label: "Add", list: window.backend.config.groups, callback_method: proc { |group, name|
|
||||||
|
clone = TAC::Config::Group.from_json( JSON.parse( group.to_json, symbolize_names: true ))
|
||||||
|
clone.name = "#{name}"
|
||||||
|
window.backend.config.groups << clone
|
||||||
|
window.backend.config.groups.sort_by! { |g| g.name.downcase }
|
||||||
|
window.backend.config_changed!
|
||||||
|
|
||||||
|
populate_groups_list
|
||||||
|
})
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@groups_list = stack width: 1.0, scroll: true do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
stack width: 0.33333, height: 1.0, border_thickness_right: 1, border_color: [0, Gosu::Color::BLACK, 0, 0] do
|
||||||
|
@actions_menu = flow(width: 1.0) do
|
||||||
|
label "Actions", text_size: THEME_SUBHEADING_TEXT_SIZE
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_width: THEME_ICON_SIZE, tip: "Add action" do
|
||||||
|
if @active_group
|
||||||
|
push_state(TAC::Dialog::ActionDialog, title: "Create Action", list: @active_group.actions, callback_method: method(:create_action))
|
||||||
|
else
|
||||||
|
push_state(TAC::Dialog::AlertDialog, title: "Error", message: "Unable to create action, no group selected.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/button2.png"), image_width: THEME_ICON_SIZE, tip: "Clone currently selected action" do
|
||||||
|
if @active_group && @active_action
|
||||||
|
push_state(Dialog::ActionDialog, title: "Clone Action", action: @active_action, cloning: true, accept_label: "Clone", list: @active_group.actions, callback_method: proc { |action, name, comment|
|
||||||
|
clone = TAC::Config::Action.from_json( JSON.parse( @active_action.to_json, symbolize_names: true ))
|
||||||
|
clone.name = name
|
||||||
|
clone.comment = comment
|
||||||
|
@active_group.actions << clone
|
||||||
|
window.backend.config_changed!
|
||||||
|
|
||||||
|
populate_actions_list(@active_group)
|
||||||
|
})
|
||||||
|
else
|
||||||
|
push_state(TAC::Dialog::AlertDialog, title: "Error", message: "Unable to clone action, no action selected.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/save.png"), image_width: THEME_ICON_SIZE, tip: "Save action as preset" do
|
||||||
|
if @active_action
|
||||||
|
push_state(Dialog::NamePromptDialog, title: "Save Action Preset", renaming: @active_action, accept_label: "Save", list: window.backend.config.presets.actions, callback_method: proc { |action, name|
|
||||||
|
clone = TAC::Config::Action.from_json( JSON.parse( @active_action.to_json, symbolize_names: true ))
|
||||||
|
clone.name = "#{name}"
|
||||||
|
window.backend.config.presets.actions << clone
|
||||||
|
window.backend.config.presets.actions.sort_by! { |a| a.name.downcase }
|
||||||
|
window.backend.config_changed!
|
||||||
|
|
||||||
|
window.toast("Saved Action Preset", "Saved preset: #{name}")
|
||||||
|
})
|
||||||
|
else
|
||||||
|
push_state(TAC::Dialog::AlertDialog, title: "Error", message: "Unable to create action preset, no action selected.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/import.png"), image_width: THEME_ICON_SIZE, tip: "Import action from preset" do
|
||||||
|
if @active_group
|
||||||
|
push_state(Dialog::PickPresetDialog, title: "Pick Action Preset", limit: :actions, callback_method: proc { |preset|
|
||||||
|
push_state(Dialog::ActionDialog, title: "Name Action", action: preset, accept_label: "Add", list: @active_group.actions, callback_method: proc { |action, name|
|
||||||
|
clone = TAC::Config::Action.from_json( JSON.parse( action.to_json, symbolize_names: true ))
|
||||||
|
clone.name = "#{name}"
|
||||||
|
@active_group.actions << clone
|
||||||
|
@active_group.actions.sort_by! { |a| a.name.downcase }
|
||||||
|
window.backend.config_changed!
|
||||||
|
|
||||||
|
populate_actions_list(@active_group)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
else
|
||||||
|
push_state(TAC::Dialog::AlertDialog, title: "Error", message: "Unable to import action preset, no group selected.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@actions_list = stack width: 1.0, scroll: true do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
stack width: 0.331, height: 1.0 do
|
||||||
|
@variables_menu = flow(width: 1.0) do
|
||||||
|
label "Variables", text_size: THEME_SUBHEADING_TEXT_SIZE
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_width: THEME_ICON_SIZE, tip: "Add variable" do
|
||||||
|
if @active_action
|
||||||
|
push_state(TAC::Dialog::VariableDialog, title: "Create Variable", callback_method: method(:create_variable))
|
||||||
|
else
|
||||||
|
push_state(TAC::Dialog::AlertDialog, title: "Error", message: "Unable to create variable, no action selected.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@variables_list = stack width: 1.0, scroll: true do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
populate_groups_list
|
||||||
|
|
||||||
|
if @options[:group_is_preset]
|
||||||
|
@active_group = @options[:group]
|
||||||
|
@active_group_label.value = @active_group.name
|
||||||
|
populate_actions_list(@active_group)
|
||||||
|
|
||||||
|
@groups_menu.hide
|
||||||
|
|
||||||
|
elsif @options[:action_is_preset]
|
||||||
|
@active_action = @options[:action]
|
||||||
|
@active_action_label.value = @active_action.name
|
||||||
|
|
||||||
|
populate_variables_list(@options[:action])
|
||||||
|
|
||||||
|
@groups_menu.hide
|
||||||
|
@actions_menu.hide
|
||||||
|
|
||||||
|
else
|
||||||
|
if @options[:group]
|
||||||
|
@active_group = @options[:group]
|
||||||
|
@active_group_label.value = @active_group.name
|
||||||
|
|
||||||
|
populate_actions_list(@active_group)
|
||||||
|
|
||||||
|
if @options[:action]
|
||||||
|
@active_action = @options[:action]
|
||||||
|
@active_action_label.value = @active_action.name
|
||||||
|
|
||||||
|
populate_variables_list(@active_action)
|
||||||
|
|
||||||
|
if @options[:variable]
|
||||||
|
# Scroll into view?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
body.root.subscribe(:window_size_changed) do
|
||||||
|
set_list_heights
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_list_heights
|
||||||
|
@groups_list.style.height = body.height - @groups_menu.height
|
||||||
|
@actions_list.style.height = body.height - @actions_menu.height
|
||||||
|
@variables_list.style.height = body.height - @variables_menu.height
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_group(name)
|
||||||
|
window.backend.config.groups << TAC::Config::Group.new(name: name, actions: [])
|
||||||
|
window.backend.config.groups.sort_by! { |g| g.name.downcase }
|
||||||
|
window.backend.config_changed!
|
||||||
|
|
||||||
|
populate_groups_list
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_group(group, name)
|
||||||
|
group.name = name
|
||||||
|
window.backend.config.groups.sort_by! { |g| g.name.downcase }
|
||||||
|
window.backend.config_changed!
|
||||||
|
|
||||||
|
populate_groups_list
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_group(group)
|
||||||
|
window.backend.config.groups.delete(group)
|
||||||
|
window.backend.config.groups.sort_by! { |g| g.name.downcase }
|
||||||
|
window.backend.config_changed!
|
||||||
|
|
||||||
|
@active_group = nil
|
||||||
|
@active_group_label.value = ""
|
||||||
|
@active_action = nil
|
||||||
|
@active_action_label.value = ""
|
||||||
|
@actions_list.clear
|
||||||
|
@variables_list.clear
|
||||||
|
|
||||||
|
populate_groups_list
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_action(name, comment)
|
||||||
|
@active_group.actions << TAC::Config::Action.new(name: name, comment: comment, enabled: true, variables: [])
|
||||||
|
@active_group.actions.sort_by! { |a| a.name.downcase }
|
||||||
|
window.backend.config_changed!
|
||||||
|
|
||||||
|
populate_actions_list(@active_group)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_action(action, name, comment)
|
||||||
|
action.name = name
|
||||||
|
action.comment = comment
|
||||||
|
window.backend.config_changed!
|
||||||
|
|
||||||
|
populate_actions_list(@active_group)
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_action(action)
|
||||||
|
@active_group.actions.delete(action)
|
||||||
|
@active_group.actions.sort_by! { |a| a.name.downcase }
|
||||||
|
window.backend.config_changed!
|
||||||
|
|
||||||
|
@active_action = nil
|
||||||
|
@active_action_label.value = ""
|
||||||
|
@variables_list.clear
|
||||||
|
|
||||||
|
populate_actions_list(@active_group)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_variable(name, type, value)
|
||||||
|
@active_action.variables << TAC::Config::Variable.new(name: name, type: type, value: value)
|
||||||
|
@active_action.variables.sort_by! { |v| v.name.downcase }
|
||||||
|
window.backend.config_changed!
|
||||||
|
|
||||||
|
populate_variables_list(@active_action)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_variable(variable, name, type, value)
|
||||||
|
variable.name = name
|
||||||
|
variable.type = type
|
||||||
|
variable.value = value
|
||||||
|
|
||||||
|
@active_action.variables.sort_by! { |v| v.name.downcase }
|
||||||
|
|
||||||
|
window.backend.config_changed!
|
||||||
|
|
||||||
|
populate_variables_list(@active_action)
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_variable(variable)
|
||||||
|
@active_action.variables.delete(variable)
|
||||||
|
@active_action.variables.sort_by! { |v| v.name.downcase }
|
||||||
|
window.backend.config_changed!
|
||||||
|
|
||||||
|
populate_variables_list(@active_action)
|
||||||
|
end
|
||||||
|
|
||||||
|
def populate_groups_list
|
||||||
|
@groups_list.scroll_top = 0
|
||||||
|
|
||||||
|
groups = []
|
||||||
|
|
||||||
|
unless @options[:group_is_preset] || @options[:action_is_preset]
|
||||||
|
groups = window.backend.config.groups
|
||||||
|
end
|
||||||
|
|
||||||
|
@groups_list.clear do
|
||||||
|
groups.each_with_index do |group, i|
|
||||||
|
flow width: 1.0, **THEME_ITEM_CONTAINER_PADDING do
|
||||||
|
background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
||||||
|
|
||||||
|
button group.name, width: 0.8 do
|
||||||
|
@active_group = group
|
||||||
|
@active_group_label.value = group.name
|
||||||
|
@active_action = nil
|
||||||
|
@active_action_label.value = ""
|
||||||
|
|
||||||
|
populate_actions_list(group)
|
||||||
|
@variables_list.clear
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/gear.png"), image_width: THEME_ICON_SIZE, tip: "Edit group" do
|
||||||
|
push_state(Dialog::NamePromptDialog, title: "Rename Group", renaming: group, list: window.backend.config.groups, callback_method: method(:update_group))
|
||||||
|
end
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/trashcan.png"), image_width: THEME_ICON_SIZE, tip: "Delete group", **THEME_DANGER_BUTTON do
|
||||||
|
push_state(Dialog::ConfirmDialog, dangerous: true, title: "Are you sure?", message: "Delete group and all of its actions and variables?", callback_method: proc { delete_group(group) })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
set_list_heights
|
||||||
|
end
|
||||||
|
|
||||||
|
def populate_actions_list(group)
|
||||||
|
@actions_list.scroll_top = 0
|
||||||
|
|
||||||
|
actions = group.actions
|
||||||
|
|
||||||
|
@actions_list.clear do
|
||||||
|
actions.each_with_index do |action, i|
|
||||||
|
stack width: 1.0, **THEME_ITEM_CONTAINER_PADDING do
|
||||||
|
background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
||||||
|
|
||||||
|
flow width: 1.0 do
|
||||||
|
button action.name, width: 0.72 do
|
||||||
|
@active_action = action
|
||||||
|
@active_action_label.value = action.name
|
||||||
|
|
||||||
|
populate_variables_list(action)
|
||||||
|
end
|
||||||
|
|
||||||
|
action_enabled_toggle = toggle_button tip: "Enable action", checked: action.enabled
|
||||||
|
action_enabled_toggle.subscribe(:changed) do |sender, value|
|
||||||
|
action.enabled = value
|
||||||
|
window.backend.config_changed!
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/gear.png"), image_width: THEME_ICON_SIZE, tip: "Edit action" do
|
||||||
|
push_state(Dialog::ActionDialog, title: "Rename Action", action: action, list: @active_group.actions, callback_method: method(:update_action))
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/trashcan.png"), image_width: THEME_ICON_SIZE, tip: "Delete action", **THEME_DANGER_BUTTON do
|
||||||
|
push_state(Dialog::ConfirmDialog, dangerous: true, title: "Are you sure?", message: "Delete action and all of its variables?", callback_method: proc { delete_action(action) })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
caption "#{action.comment}", width: 1.0, text_wrap: :word_wrap unless action.comment.empty?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
set_list_heights
|
||||||
|
end
|
||||||
|
|
||||||
|
def populate_variables_list(action)
|
||||||
|
@variables_list.scroll_top = 0
|
||||||
|
|
||||||
|
variables = action.variables
|
||||||
|
|
||||||
|
@variables_list.clear do
|
||||||
|
variables.each_with_index do |variable, i|
|
||||||
|
stack width: 1.0, **THEME_ITEM_CONTAINER_PADDING do
|
||||||
|
background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
||||||
|
|
||||||
|
flow(width: 1.0) do
|
||||||
|
button "#{variable.name}", width: 0.89, tip: "Edit variable" do
|
||||||
|
push_state(Dialog::VariableDialog, title: "Edit Variable", variable: variable, callback_method: method(:update_variable))
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/trashcan.png"), image_width: THEME_ICON_SIZE, tip: "Delete variable", **THEME_DANGER_BUTTON do
|
||||||
|
push_state(Dialog::ConfirmDialog, title: "Are you sure?", message: "Delete variable?", callback_method: proc { delete_variable(variable) })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
caption "Type: #{variable.type}"
|
||||||
|
caption "Value: #{variable.value}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
set_list_heights
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
226
lib/pages/field_planner.rb
Normal file
226
lib/pages/field_planner.rb
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
module TAC
|
||||||
|
class Pages
|
||||||
|
class FieldPlanner < Page
|
||||||
|
def setup
|
||||||
|
header_bar("Field Planner")
|
||||||
|
|
||||||
|
menu_bar.clear do
|
||||||
|
flow(width: 1.0, height: 1.0) do
|
||||||
|
button "Inches", text_size: THEME_HEADING_TEXT_SIZE do
|
||||||
|
@unit = :inches
|
||||||
|
refresh_panel
|
||||||
|
end
|
||||||
|
|
||||||
|
button "Feet", text_size: THEME_HEADING_TEXT_SIZE do
|
||||||
|
@unit = :feet
|
||||||
|
refresh_panel
|
||||||
|
end
|
||||||
|
|
||||||
|
button "Millimeters", text_size: THEME_HEADING_TEXT_SIZE do
|
||||||
|
@unit = :millimeters
|
||||||
|
refresh_panel
|
||||||
|
end
|
||||||
|
|
||||||
|
button "Centimeters", text_size: THEME_HEADING_TEXT_SIZE do
|
||||||
|
@unit = :centimeters
|
||||||
|
refresh_panel
|
||||||
|
end
|
||||||
|
|
||||||
|
button "Meters", text_size: THEME_HEADING_TEXT_SIZE do
|
||||||
|
@unit = :meters
|
||||||
|
refresh_panel
|
||||||
|
end
|
||||||
|
|
||||||
|
button "Reset", text_size: THEME_HEADING_TEXT_SIZE, **THEME_DANGER_BUTTON do
|
||||||
|
@nodes.clear
|
||||||
|
refresh_panel
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
status_bar.clear do
|
||||||
|
flow(width: 1.0, height: 1.0) do
|
||||||
|
tagline "Nodes:"
|
||||||
|
@nodes_count_label = tagline "0"
|
||||||
|
|
||||||
|
tagline "Total Distance:"
|
||||||
|
@total_distance_label = tagline "0"
|
||||||
|
|
||||||
|
@units_label = tagline "Inches"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
body.clear do
|
||||||
|
flow(width: 1.0, height: 1.0) do
|
||||||
|
@field_container = stack width: 0.5, height: 1.0 do
|
||||||
|
background 0xff_111111
|
||||||
|
end
|
||||||
|
|
||||||
|
@points_container = stack width: 0.5, height: 1.0 do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@field = TAC::Simulator::Field.new(container: @field_container, season: :freight_frenzy, simulation: nil)
|
||||||
|
@nodes ||= []
|
||||||
|
@unit = :inches
|
||||||
|
@total_distance = 0
|
||||||
|
|
||||||
|
@node_color = 0xff_00f000
|
||||||
|
@node_hover_color = Gosu::Color::YELLOW
|
||||||
|
@segment_color = 0xaa_00f000
|
||||||
|
@node_radius = 6
|
||||||
|
@segment_thickness = 2
|
||||||
|
|
||||||
|
measure_path
|
||||||
|
refresh_panel
|
||||||
|
end
|
||||||
|
|
||||||
|
def draw
|
||||||
|
super
|
||||||
|
|
||||||
|
@field.draw
|
||||||
|
|
||||||
|
display_path
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
super
|
||||||
|
|
||||||
|
@field.update
|
||||||
|
|
||||||
|
measure_path
|
||||||
|
end
|
||||||
|
|
||||||
|
def button_down(id)
|
||||||
|
super
|
||||||
|
|
||||||
|
if @field_container.hit?(window.mouse_x, window.mouse_y)
|
||||||
|
x = (window.mouse_x - @field_container.x) / @field.scale
|
||||||
|
y = (window.mouse_y - @field_container.y) / @field.scale
|
||||||
|
|
||||||
|
case id
|
||||||
|
when Gosu::MS_LEFT # Add Node
|
||||||
|
@nodes << CyberarmEngine::Vector.new(x, y, 0)
|
||||||
|
|
||||||
|
measure_path
|
||||||
|
|
||||||
|
refresh_panel
|
||||||
|
|
||||||
|
when Gosu::MS_RIGHT # Delete Node
|
||||||
|
result = @nodes.find do |node|
|
||||||
|
Gosu.distance(node.x, node.y, x, y) <= @node_radius * 0.25
|
||||||
|
end
|
||||||
|
|
||||||
|
@nodes.delete(result) if result
|
||||||
|
|
||||||
|
measure_path
|
||||||
|
|
||||||
|
refresh_panel
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def display_path
|
||||||
|
x = (window.mouse_x - @field_container.x) / @field.scale
|
||||||
|
y = (window.mouse_y - @field_container.y) / @field.scale
|
||||||
|
|
||||||
|
last_node = @nodes.first
|
||||||
|
|
||||||
|
@nodes.each_with_index do |current_node, i|
|
||||||
|
mouse_near = Gosu.distance(current_node.x, current_node.y, x, y) <= @node_radius * 0.25
|
||||||
|
|
||||||
|
Gosu.draw_circle(
|
||||||
|
current_node.x * @field.scale + @field_container.x,
|
||||||
|
current_node.y * @field.scale + @field_container.y,
|
||||||
|
@node_radius, 7, mouse_near ? @node_hover_color : @node_color, 10
|
||||||
|
)
|
||||||
|
|
||||||
|
next if i.zero?
|
||||||
|
|
||||||
|
angle = Gosu.angle(
|
||||||
|
last_node.x * @field.scale,
|
||||||
|
last_node.y * @field.scale,
|
||||||
|
current_node.x * @field.scale,
|
||||||
|
current_node.y * @field.scale
|
||||||
|
)
|
||||||
|
|
||||||
|
distance = Gosu.distance(last_node.x, last_node.y, current_node.x, current_node.y) * @field.scale
|
||||||
|
|
||||||
|
Gosu.rotate(angle, last_node.x * @field.scale + @field_container.x, last_node.y * @field.scale + @field_container.y) do
|
||||||
|
Gosu.draw_rect(
|
||||||
|
(@field_container.x + last_node.x * @field.scale) - (@segment_thickness / 2.0),
|
||||||
|
(@field_container.y + last_node.y * @field.scale) - distance,
|
||||||
|
@segment_thickness,
|
||||||
|
distance,
|
||||||
|
@segment_color
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
last_node = current_node
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def measure_path
|
||||||
|
@total_distance = 0
|
||||||
|
|
||||||
|
v1 = @nodes.first
|
||||||
|
@nodes.each_with_index do |v2, i|
|
||||||
|
next if i.zero?
|
||||||
|
|
||||||
|
@total_distance += Gosu.distance(
|
||||||
|
v1.x + @field_container.x,
|
||||||
|
v1.y + @field_container.y,
|
||||||
|
v2.x + @field_container.x,
|
||||||
|
v2.y + @field_container.y
|
||||||
|
)
|
||||||
|
|
||||||
|
v1 = v2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def refresh_panel
|
||||||
|
@nodes_count_label.value = "#{@nodes.count}"
|
||||||
|
@total_distance_label.value = "#{inches_to_unit(@total_distance).round(2)}"
|
||||||
|
@units_label.value = @unit.to_s.capitalize
|
||||||
|
|
||||||
|
# @points_container.clear do
|
||||||
|
# v1 = @nodes.first
|
||||||
|
# break unless v1
|
||||||
|
|
||||||
|
# para "Vector #{inches_to_unit(v1.x).round}:#{inches_to_unit(v1.y).round} - 0 #{@unit.to_s.capitalize}"
|
||||||
|
|
||||||
|
# @nodes.each_with_index do |v2, i|
|
||||||
|
# next if i.zero?
|
||||||
|
|
||||||
|
# distance = Gosu.distance(
|
||||||
|
# v1.x + @field_container.x,
|
||||||
|
# v1.y + @field_container.y,
|
||||||
|
# v2.x + @field_container.x,
|
||||||
|
# v2.y + @field_container.y
|
||||||
|
# )
|
||||||
|
|
||||||
|
# para "Vector #{inches_to_unit(v1.x).round}:#{inches_to_unit(v1.y).round} - #{inches_to_unit(distance).round(2)} #{@unit.to_s.capitalize}"
|
||||||
|
|
||||||
|
# v1 = v2
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
end
|
||||||
|
|
||||||
|
def inches_to_unit(inches)
|
||||||
|
case @unit
|
||||||
|
when :inches
|
||||||
|
inches
|
||||||
|
when :feet
|
||||||
|
inches / 12.0
|
||||||
|
when :millimeters
|
||||||
|
inches / 0.254
|
||||||
|
when :centimeters
|
||||||
|
inches / 2.54
|
||||||
|
when :meters
|
||||||
|
inches / 25.4
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
42
lib/pages/home.rb
Normal file
42
lib/pages/home.rb
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
module TAC
|
||||||
|
class Pages
|
||||||
|
class Home < Page
|
||||||
|
def setup
|
||||||
|
header_bar(TAC::NAME)
|
||||||
|
|
||||||
|
body.clear do
|
||||||
|
stack(width: 1.0, height: 1.0) do
|
||||||
|
label TAC::NAME, width: 1.0, text_size: 48, text_align: :center
|
||||||
|
|
||||||
|
stack(width: 1.0, height: 8) do
|
||||||
|
background 0xff_006000
|
||||||
|
end
|
||||||
|
|
||||||
|
if window.backend.settings.config.empty?
|
||||||
|
label "TODO: Introduction"
|
||||||
|
label "Get Started", text_size: 28
|
||||||
|
button "1. Create a configuration" do
|
||||||
|
page(TAC::Pages::Configurations)
|
||||||
|
end
|
||||||
|
label "2. Add a group"
|
||||||
|
label "3. Add an action"
|
||||||
|
label "4. Add a variable"
|
||||||
|
label "5. Profit?"
|
||||||
|
else
|
||||||
|
label "Display config stats or something?"
|
||||||
|
|
||||||
|
config = window.backend.config
|
||||||
|
groups = config.groups
|
||||||
|
actions = config.groups.map { |g| g.actions }.flatten
|
||||||
|
variables = actions.map { |a| a.variables }.flatten
|
||||||
|
|
||||||
|
label "Total groups: #{groups.size}"
|
||||||
|
label "Total actions: #{actions.size}"
|
||||||
|
label "Total variables: #{variables.size}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
125
lib/pages/presets.rb
Normal file
125
lib/pages/presets.rb
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
module TAC
|
||||||
|
class Pages
|
||||||
|
class Presets < Page
|
||||||
|
def setup
|
||||||
|
header_bar("Manage Presets")
|
||||||
|
|
||||||
|
status_bar.clear do
|
||||||
|
tagline "Group Presets", width: 0.495
|
||||||
|
tagline "Action Presets", width: 0.495
|
||||||
|
end
|
||||||
|
|
||||||
|
body.clear do
|
||||||
|
flow(width: 1.0, height: 1.0) do
|
||||||
|
@group_presets = stack(width: 0.49995, height: 1.0, scroll: true, border_thickness_right: 1, border_color: [0, Gosu::Color::BLACK, 0, 0]) do
|
||||||
|
end
|
||||||
|
|
||||||
|
@action_presets = stack(width: 0.49995, height: 1.0, scroll: true) do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
populate_group_presets
|
||||||
|
populate_action_presets
|
||||||
|
end
|
||||||
|
|
||||||
|
def populate_group_presets
|
||||||
|
@group_presets.clear do
|
||||||
|
window.backend.config.presets.groups.each_with_index do |group, i|
|
||||||
|
flow(width: 1.0, **THEME_ITEM_CONTAINER_PADDING) do
|
||||||
|
background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
||||||
|
|
||||||
|
button group.name, width: 0.895 do
|
||||||
|
page(TAC::Pages::Editor,{ group: group, group_is_preset: true })
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/gear.png"), image_width: THEME_ICON_SIZE, tip: "Edit group preset" do
|
||||||
|
push_state(
|
||||||
|
Dialog::NamePromptDialog,
|
||||||
|
title: "Rename Group Preset",
|
||||||
|
renaming: group,
|
||||||
|
list: window.backend.config.presets.groups,
|
||||||
|
callback_method: method(:update_group_preset)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/trashcan.png"), image_width: THEME_ICON_SIZE, tip: "Delete group preset", **THEME_DANGER_BUTTON do
|
||||||
|
push_state(
|
||||||
|
Dialog::ConfirmDialog,
|
||||||
|
title: "Are you sure?",
|
||||||
|
message: "Delete group preset and all of its actions and variables?",
|
||||||
|
callback_method: proc { delete_group_preset(group) }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def populate_action_presets
|
||||||
|
@action_presets.clear do
|
||||||
|
window.backend.config.presets.actions.each_with_index do |action, i|
|
||||||
|
flow(width: 1.0, **THEME_ITEM_CONTAINER_PADDING) do
|
||||||
|
background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
||||||
|
|
||||||
|
button action.name, width: 0.895 do
|
||||||
|
page(TAC::Pages::Editor,{ action: action, action_is_preset: true })
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/gear.png"), image_width: THEME_ICON_SIZE, tip: "Edit action preset" do
|
||||||
|
push_state(
|
||||||
|
Dialog::NamePromptDialog,
|
||||||
|
title: "Rename Action Preset",
|
||||||
|
renaming: action,
|
||||||
|
list: window.backend.config.presets.actions,
|
||||||
|
callback_method: method(:update_action_preset)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/trashcan.png"), image_width: THEME_ICON_SIZE, tip: "Delete action preset", **THEME_DANGER_BUTTON do
|
||||||
|
push_state(
|
||||||
|
Dialog::ConfirmDialog,
|
||||||
|
title: "Are you sure?",
|
||||||
|
message: "Delete action preset and all of its actions and variables?",
|
||||||
|
callback_method: proc { delete_action_preset(action) }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_group_preset(group, name)
|
||||||
|
group.name = name
|
||||||
|
window.backend.config.presets.groups.sort_by! { |g| g.name.downcase }
|
||||||
|
window.backend.config_changed!
|
||||||
|
|
||||||
|
populate_group_presets
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_group_preset(group)
|
||||||
|
window.backend.config.presets.groups.delete(group)
|
||||||
|
window.backend.config.presets.groups.sort_by! { |g| g.name.downcase }
|
||||||
|
window.backend.config_changed!
|
||||||
|
|
||||||
|
populate_group_presets
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_action_preset(action, name)
|
||||||
|
action.name = name
|
||||||
|
window.backend.config.presets.actions.sort_by! { |a| a.name.downcase }
|
||||||
|
window.backend.config_changed!
|
||||||
|
|
||||||
|
populate_action_presets
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_action_preset(action)
|
||||||
|
window.backend.config.presets.actions.delete(action)
|
||||||
|
window.backend.config.presets.actions.sort_by! { |a| a.name.downcase }
|
||||||
|
window.backend.config_changed!
|
||||||
|
|
||||||
|
populate_action_presets
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
340
lib/pages/search.rb
Normal file
340
lib/pages/search.rb
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
module TAC
|
||||||
|
class Pages
|
||||||
|
class Search < Page
|
||||||
|
def setup
|
||||||
|
header_bar("Search")
|
||||||
|
|
||||||
|
menu_bar.clear do
|
||||||
|
search = edit_line "", width: 0.9, height: 1.0
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/zoom.png"), image_height: 1.0 do
|
||||||
|
unless search.value.strip.empty?
|
||||||
|
search_results = search_config(search.value.downcase.strip)
|
||||||
|
|
||||||
|
status_bar.clear do
|
||||||
|
if search_results.results.size.zero?
|
||||||
|
subtitle "No results for: \"#{search.value.strip}\""
|
||||||
|
else
|
||||||
|
subtitle "Search results for: \"#{search.value.strip}\""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
body.clear do
|
||||||
|
shared_index = 0
|
||||||
|
|
||||||
|
flow(width: 1.0, height: 1.0) do
|
||||||
|
stack(width: 0.495, height: 1.0, scroll: true) do
|
||||||
|
if search_results.groups.size.positive?
|
||||||
|
title "Groups"
|
||||||
|
|
||||||
|
search_results.groups.each do |result|
|
||||||
|
stack(width: 1.0, **THEME_ITEM_CONTAINER_PADDING) do
|
||||||
|
background shared_index.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
||||||
|
button result.highlight(result.group.name), width: 1.0 do
|
||||||
|
page(TAC::Pages::Editor, { group: result.group, is_search: true })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_index += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if search_results.actions.size.positive?
|
||||||
|
title "Actions"
|
||||||
|
|
||||||
|
search_results.actions.each do |result|
|
||||||
|
stack(width: 1.0, **THEME_ITEM_CONTAINER_PADDING) do
|
||||||
|
background shared_index.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
||||||
|
button result.highlight(result.action.name), width: 1.0 do
|
||||||
|
page(TAC::Pages::Editor, { group: result.group, action: result.action, is_search: true })
|
||||||
|
end
|
||||||
|
|
||||||
|
if result.from_comment?
|
||||||
|
para result.highlight(result.action.comment), width: 1.0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_index += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if search_results.variables.size.positive?
|
||||||
|
title "Variables"
|
||||||
|
|
||||||
|
search_results.variables.each do |result|
|
||||||
|
stack(width: 1.0, **THEME_ITEM_CONTAINER_PADDING) do
|
||||||
|
background shared_index.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
||||||
|
button "#{result.highlight(result.variable.name)} [#{result.highlight(result.variable.value)}]", width: 1.0 do
|
||||||
|
page(TAC::Pages::Editor, { group: result.group, action: result.action, variable: result.variable, is_search: true })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_index += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
stack(width: 0.495, height: 1.0, scroll: true) do
|
||||||
|
if search_results.group_presets.size.positive?
|
||||||
|
title "Group Presets"
|
||||||
|
|
||||||
|
search_results.group_presets.each do |result|
|
||||||
|
stack(width: 1.0, **THEME_ITEM_CONTAINER_PADDING) do
|
||||||
|
background shared_index.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
||||||
|
button result.highlight(result.group.name), width: 1.0 do
|
||||||
|
page(TAC::Pages::Editor, { group: result.group, group_is_preset: true, is_search: true })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_index += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
if search_results.action_presets.size.positive?
|
||||||
|
title "Action Presets"
|
||||||
|
|
||||||
|
search_results.action_presets.each do |result|
|
||||||
|
stack(width: 1.0, **THEME_ITEM_CONTAINER_PADDING) do
|
||||||
|
background shared_index.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
||||||
|
button result.highlight(result.action.name), width: 1.0 do
|
||||||
|
if result.group.nil?
|
||||||
|
page(TAC::Pages::Editor, { action: result.action, action_is_preset: true, is_search: true })
|
||||||
|
else
|
||||||
|
page(TAC::Pages::Editor, { group: result.group, action: result.action, group_is_preset: true, is_search: true })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if result.from_comment?
|
||||||
|
para result.highlight(result.action.comment), width: 1.0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_index += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if search_results.variables_from_presets.size.positive?
|
||||||
|
title "Variables from Presets"
|
||||||
|
|
||||||
|
search_results.variables_from_presets.each do |result|
|
||||||
|
stack(width: 1.0, **THEME_ITEM_CONTAINER_PADDING) do
|
||||||
|
background shared_index.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
||||||
|
button "#{result.highlight(result.variable.name)} [#{result.highlight(result.variable.value)}]", width: 1.0 do
|
||||||
|
if result.group.nil?
|
||||||
|
page(TAC::Pages::Editor, { action: result.action, variable: result.variable, action_is_preset: true, is_search: true })
|
||||||
|
else
|
||||||
|
page(TAC::Pages::Editor, { group: result.group, action: result.action, variable: result.variable, group_is_preset: true, is_search: true })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_index += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def search_config(query)
|
||||||
|
search_results = SearchResults.new
|
||||||
|
|
||||||
|
search_groups(query, search_results)
|
||||||
|
search_actions(query, search_results)
|
||||||
|
search_variables(query, search_results)
|
||||||
|
search_presets(query, search_results)
|
||||||
|
|
||||||
|
return search_results
|
||||||
|
end
|
||||||
|
|
||||||
|
def search_groups(query, search_results)
|
||||||
|
window.backend.config.groups.each do |group|
|
||||||
|
if group.name.downcase.include?(query)
|
||||||
|
result = SearchResult.new(group: group, query: query, is_group: true, is_from_name: true)
|
||||||
|
search_results.results << result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def search_actions(query, search_results)
|
||||||
|
window.backend.config.groups.each do |group|
|
||||||
|
group.actions.each do |action|
|
||||||
|
if action.name.downcase.include?(query)
|
||||||
|
result = SearchResult.new(group: group, action: action, query: query, is_action: true, is_from_name: true)
|
||||||
|
search_results.results << result
|
||||||
|
end
|
||||||
|
|
||||||
|
if action.comment.downcase.include?(query)
|
||||||
|
result = SearchResult.new(group: group, action: action, query: query, is_action: true, is_from_comment: true)
|
||||||
|
search_results.results << result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def search_variables(query, search_results)
|
||||||
|
window.backend.config.groups.each do |group|
|
||||||
|
group.actions.each do |action|
|
||||||
|
action.variables.each do |variable|
|
||||||
|
if variable.name.downcase.include?(query)
|
||||||
|
result = SearchResult.new(group: group, action: action, variable: variable, is_variable: true, query: query, is_from_name: true)
|
||||||
|
search_results.results << result
|
||||||
|
end
|
||||||
|
|
||||||
|
if variable.value.downcase.include?(query)
|
||||||
|
result = SearchResult.new(group: group, action: action, variable: variable, is_variable: true, query: query, is_from_value: true)
|
||||||
|
search_results.results << result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def search_presets(query, search_results)
|
||||||
|
window.backend.config.presets.groups.each do |group|
|
||||||
|
if group.name.downcase.include?(query)
|
||||||
|
result = SearchResult.new(group: group, query: query, is_group: true, is_from_name: true, is_preset: true)
|
||||||
|
search_results.results << result
|
||||||
|
end
|
||||||
|
|
||||||
|
group.actions.each do |action|
|
||||||
|
if action.name.downcase.include?(query)
|
||||||
|
result = SearchResult.new(group: group, action: action, query: query, is_action: true, is_from_name: true, is_preset: true)
|
||||||
|
search_results.results << result
|
||||||
|
end
|
||||||
|
|
||||||
|
if action.comment.downcase.include?(query)
|
||||||
|
result = SearchResult.new(group: group, action: action, query: query, is_action: true, is_from_comment: true, is_preset: true)
|
||||||
|
search_results.results << result
|
||||||
|
end
|
||||||
|
|
||||||
|
action.variables.each do |variable|
|
||||||
|
if variable.name.downcase.include?(query)
|
||||||
|
result = SearchResult.new(group: group, action: action, variable: variable, is_variable: true, query: query, is_from_name: true, is_preset: true)
|
||||||
|
search_results.results << result
|
||||||
|
end
|
||||||
|
|
||||||
|
if variable.value.downcase.include?(query)
|
||||||
|
result = SearchResult.new(group: group, action: action, variable: variable, is_variable: true, query: query, is_from_value: true, is_preset: true)
|
||||||
|
search_results.results << result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
window.backend.config.presets.actions.each do |action|
|
||||||
|
if action.name.downcase.include?(query)
|
||||||
|
result = SearchResult.new(group: nil, action: action, query: query, is_action: true, is_from_name: true, is_preset: true)
|
||||||
|
search_results.results << result
|
||||||
|
end
|
||||||
|
|
||||||
|
if action.comment.downcase.include?(query)
|
||||||
|
result = SearchResult.new(group: nil, action: action, query: query, is_action: true, is_from_comment: true, is_preset: true)
|
||||||
|
search_results.results << result
|
||||||
|
end
|
||||||
|
|
||||||
|
action.variables.each do |variable|
|
||||||
|
if variable.name.downcase.include?(query)
|
||||||
|
result = SearchResult.new(group: nil, action: action, variable: variable, is_variable: true, query: query, is_from_name: true, is_preset: true)
|
||||||
|
search_results.results << result
|
||||||
|
end
|
||||||
|
|
||||||
|
if variable.value.downcase.include?(query)
|
||||||
|
result = SearchResult.new(group: nil, action: action, variable: variable, is_variable: true, query: query, is_from_value: true, is_preset: true)
|
||||||
|
search_results.results << result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class SearchResults
|
||||||
|
attr_reader :results
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@results = []
|
||||||
|
end
|
||||||
|
|
||||||
|
def groups
|
||||||
|
@results.select { |result| result.group? && !result.preset? }
|
||||||
|
end
|
||||||
|
|
||||||
|
def actions
|
||||||
|
@results.select { |result| result.action? && !result.preset? }
|
||||||
|
end
|
||||||
|
|
||||||
|
def variables
|
||||||
|
@results.select { |result| result.variable? && !result.preset? }
|
||||||
|
end
|
||||||
|
|
||||||
|
def group_presets
|
||||||
|
@results.select { |result| result.group? && result.preset? }
|
||||||
|
end
|
||||||
|
|
||||||
|
def action_presets
|
||||||
|
@results.select { |result| result.action? && result.preset? }
|
||||||
|
end
|
||||||
|
|
||||||
|
def variables_from_presets
|
||||||
|
@results.select { |result| result.variable? && result.preset? }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class SearchResult
|
||||||
|
attr_reader :group, :action, :variable, :query
|
||||||
|
|
||||||
|
def initialize(query:, group:, action: nil, variable: nil,
|
||||||
|
is_group: false, is_action: false, is_variable: false,
|
||||||
|
is_from_name: false, is_from_value: false, is_from_comment: false, is_preset: false)
|
||||||
|
@group = group
|
||||||
|
@action = action
|
||||||
|
@variable = variable
|
||||||
|
@query = query
|
||||||
|
|
||||||
|
@is_group = is_group
|
||||||
|
@is_action = is_action
|
||||||
|
@is_variable = is_variable
|
||||||
|
|
||||||
|
@is_from_name = is_from_name
|
||||||
|
@is_from_value = is_from_value
|
||||||
|
@is_from_comment = is_from_comment
|
||||||
|
@is_preset = is_preset
|
||||||
|
end
|
||||||
|
|
||||||
|
def group?
|
||||||
|
@is_group
|
||||||
|
end
|
||||||
|
|
||||||
|
def action?
|
||||||
|
@is_action
|
||||||
|
end
|
||||||
|
|
||||||
|
def variable?
|
||||||
|
@is_variable
|
||||||
|
end
|
||||||
|
|
||||||
|
def from_name?
|
||||||
|
@is_from_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def from_value?
|
||||||
|
@is_from_value
|
||||||
|
end
|
||||||
|
|
||||||
|
def from_comment?
|
||||||
|
@is_from_comment
|
||||||
|
end
|
||||||
|
|
||||||
|
def preset?
|
||||||
|
@is_preset
|
||||||
|
end
|
||||||
|
|
||||||
|
def highlight(string)
|
||||||
|
string.gsub(/#{@query}/i, "<b><c=ff00ff>#{@query}</c></b>")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
85
lib/pages/simulator.rb
Normal file
85
lib/pages/simulator.rb
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
module TAC
|
||||||
|
class Pages
|
||||||
|
class Simulator < Page
|
||||||
|
SOURCE_FILE_PATH = "#{TAC::ROOT_PATH}/data/simulator.rb"
|
||||||
|
def setup
|
||||||
|
header_bar("Simulator")
|
||||||
|
|
||||||
|
menu_bar.clear do
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/right.png"), tip: "Run Simulation", image_height: 1.0 do
|
||||||
|
save_source
|
||||||
|
|
||||||
|
begin
|
||||||
|
@simulation = TAC::Simulator::Simulation.new(source_code: @source_code.value, field_container: @field_container)
|
||||||
|
@simulation.start
|
||||||
|
rescue SyntaxError, NameError, NoMethodError, TypeError, ArgumentError => e
|
||||||
|
puts e.backtrace.reverse.join("\n")
|
||||||
|
puts e
|
||||||
|
push_state(Dialog::AlertDialog, title: "#{e.class}", message: e)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/stop.png"), tip: "Stop Simulation", image_height: 1.0 do
|
||||||
|
@simulation.robots.each { |robot| robot.queue.clear } if @simulation
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/save.png"), tip: "Save", image_height: 1.0 do
|
||||||
|
save_source
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
status_bar.clear do
|
||||||
|
@simulation_status = label ""
|
||||||
|
end
|
||||||
|
|
||||||
|
body.clear do
|
||||||
|
flow(width: 1.0, height: 1.0) do
|
||||||
|
@field_container = stack width: 0.4, height: 1.0 do
|
||||||
|
background 0xff_111111
|
||||||
|
end
|
||||||
|
|
||||||
|
stack(width: 0.6, height: 1.0) do
|
||||||
|
source_code =
|
||||||
|
"robot = create_robot(alliance: :blue, width: 18, depth: 18)
|
||||||
|
robot.backward 100
|
||||||
|
robot.turn 90
|
||||||
|
robot.forward 100
|
||||||
|
robot.turn -90
|
||||||
|
robot.forward 100
|
||||||
|
robot.turn -90
|
||||||
|
robot.forward 100"
|
||||||
|
|
||||||
|
source_code = File.read(SOURCE_FILE_PATH) if File.exists?(SOURCE_FILE_PATH)
|
||||||
|
|
||||||
|
@source_code = edit_box source_code, width: 1.0, height: 1.0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def save_source
|
||||||
|
File.open(SOURCE_FILE_PATH, "w") { |f| f.write @source_code.value }
|
||||||
|
@simulation_status.value = "Saved source to #{SOURCE_FILE_PATH}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def blur
|
||||||
|
@simulation.robots.each { |robot| robot.queue.clear } if @simulation
|
||||||
|
save_source
|
||||||
|
end
|
||||||
|
|
||||||
|
def draw
|
||||||
|
@simulation.draw if @simulation
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
return unless @simulation
|
||||||
|
|
||||||
|
@simulation.update
|
||||||
|
|
||||||
|
unless @simulation.robots.all? { |robot| robot.queue.empty? } # Only update clock if simulation is running
|
||||||
|
@simulation_status.value = "Time: #{(@simulation.simulation_time).round(1)} seconds"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
76
lib/pages/tacnet.rb
Normal file
76
lib/pages/tacnet.rb
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
module TAC
|
||||||
|
class Pages
|
||||||
|
class TACNET < Page
|
||||||
|
def setup
|
||||||
|
header_bar("TimeCrafters Auxiliary Configuration Network")
|
||||||
|
|
||||||
|
menu_bar.clear do
|
||||||
|
@connect_menu = flow(width: 1.0, height: 1.0) do
|
||||||
|
label "Hostname", text_size: 28
|
||||||
|
hostname = edit_line window.backend.settings.hostname, width: 0.33, height: 1.0, text_size: 28
|
||||||
|
label "Port", text_size: 28
|
||||||
|
port = edit_line window.backend.settings.port, width: 0.33, height: 1.0, text_size: 28
|
||||||
|
button "Connect", height: 1.0, text_size: 28 do
|
||||||
|
if hostname.value != window.backend.settings.hostname || port.value.to_i != window.backend.settings.port
|
||||||
|
window.backend.settings_changed!
|
||||||
|
end
|
||||||
|
|
||||||
|
window.backend.settings.hostname = hostname.value
|
||||||
|
window.backend.settings.port = port.value.to_i
|
||||||
|
|
||||||
|
window.backend.tacnet.connect(hostname.value, port.value.to_i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@disconnect_menu = flow(width: 1.0, height: 1.0) do
|
||||||
|
button "Disconnect", height: 1.0, text_size: 28 do
|
||||||
|
window.backend.tacnet.close
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
status_bar.clear do
|
||||||
|
@tacnet_icon = image "#{TAC::ROOT_PATH}/media/icons/signal3.png", height: 26
|
||||||
|
@status_label = label "TACNET: Not Connected", text_size: 26
|
||||||
|
end
|
||||||
|
|
||||||
|
body.clear do
|
||||||
|
@full_status_label = label ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
case window.backend.tacnet.status
|
||||||
|
when :connected
|
||||||
|
sent = "#{window.backend.tacnet.client.packets_sent}".rjust(4, '0')
|
||||||
|
received = "#{window.backend.tacnet.client.packets_received}".rjust(4, '0')
|
||||||
|
@status_label.value = "TACNET: Connected Pkt Sent: #{sent} Pkt Received: #{received}"
|
||||||
|
@full_status_label.value = window.backend.tacnet.full_status
|
||||||
|
@tacnet_icon.style.color = TAC::Palette::TACNET_CONNECTED
|
||||||
|
@connect_menu.hide
|
||||||
|
@disconnect_menu.show
|
||||||
|
|
||||||
|
when :connecting
|
||||||
|
@status_label.value = "TACNET: Connecting..."
|
||||||
|
@tacnet_icon.style.color = TAC::Palette::TACNET_CONNECTING
|
||||||
|
@connect_menu.hide
|
||||||
|
@disconnect_menu.show
|
||||||
|
|
||||||
|
when :connection_error
|
||||||
|
@status_label.value = "TACNET: Connection Error"
|
||||||
|
@full_status_label.value = window.backend.tacnet.full_status
|
||||||
|
@tacnet_icon.style.color = TAC::Palette::TACNET_CONNECTION_ERROR
|
||||||
|
@connect_menu.show
|
||||||
|
@disconnect_menu.hide
|
||||||
|
|
||||||
|
when :not_connected
|
||||||
|
@status_label.value = "TACNET: Not Connected"
|
||||||
|
@full_status_label.value = ""
|
||||||
|
@tacnet_icon.style.color = 0xff_ffffff
|
||||||
|
@connect_menu.show
|
||||||
|
@disconnect_menu.hide
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -12,8 +12,8 @@ module TAC
|
|||||||
BLUE_ALLIANCE = Gosu::Color.new(0xff_000080)
|
BLUE_ALLIANCE = Gosu::Color.new(0xff_000080)
|
||||||
RED_ALLIANCE = Gosu::Color.new(0xff_800000)
|
RED_ALLIANCE = Gosu::Color.new(0xff_800000)
|
||||||
|
|
||||||
TACNET_PRIMARY = Gosu::Color.new(0xff_003f7f)
|
TACNET_PRIMARY = Gosu::Color.new(0xff000080)
|
||||||
TACNET_SECONDARY = Gosu::Color.new(0xff_007f7f)
|
TACNET_SECONDARY = Gosu::Color.new(0xff000060)
|
||||||
|
|
||||||
GROUPS_PRIMARY = Gosu::Color.new(0xff_444444)
|
GROUPS_PRIMARY = Gosu::Color.new(0xff_444444)
|
||||||
GROUPS_SECONDARY = Gosu::Color.new(0xff_444444)
|
GROUPS_SECONDARY = Gosu::Color.new(0xff_444444)
|
||||||
@@ -26,5 +26,8 @@ module TAC
|
|||||||
|
|
||||||
EDITOR_PRIMARY = Gosu::Color.new(0xff_446688)
|
EDITOR_PRIMARY = Gosu::Color.new(0xff_446688)
|
||||||
EDITOR_SECONDARY = Gosu::Color.new(0xff_224466)
|
EDITOR_SECONDARY = Gosu::Color.new(0xff_224466)
|
||||||
|
|
||||||
|
ALERT = TACNET_CONNECTING
|
||||||
|
DANGEROUS = TACNET_CONNECTION_ERROR
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
module TAC
|
module TAC
|
||||||
class Simulator
|
class Simulator
|
||||||
class Field
|
class Field
|
||||||
|
attr_reader :scale
|
||||||
|
|
||||||
def initialize(container:, season:, simulation:)
|
def initialize(container:, season:, simulation:)
|
||||||
@container = container
|
@container = container
|
||||||
@season = season
|
@season = season
|
||||||
@@ -9,21 +11,23 @@ module TAC
|
|||||||
@position = CyberarmEngine::Vector.new
|
@position = CyberarmEngine::Vector.new
|
||||||
@scale = 1
|
@scale = 1
|
||||||
@size = 0
|
@size = 0
|
||||||
@field_size = 144 # inches [1 pxel = 1 inch]
|
@field_size = 144 # inches [1 pixel = 1 inch]
|
||||||
|
|
||||||
@blue = Gosu::Color.new(0xff_004080)
|
@blue = Gosu::Color.new(0xff_004080)
|
||||||
@red = Gosu::Color.new(0xff_800000)
|
@red = Gosu::Color.new(0xff_800000)
|
||||||
end
|
end
|
||||||
|
|
||||||
def draw
|
def draw
|
||||||
|
Gosu.flush
|
||||||
|
|
||||||
Gosu.clip_to(@position.x, @position.y, @size, @size) do
|
Gosu.clip_to(@position.x, @position.y, @size, @size) do
|
||||||
Gosu.translate(@position.x, @position.y) do
|
Gosu.translate(@position.x, @position.y) do
|
||||||
draw_field
|
draw_field
|
||||||
Gosu.scale(@scale) do
|
Gosu.scale(@scale) do
|
||||||
self.send(:"draw_field_#{@season}")
|
self.send(:"draw_field_#{@season}")
|
||||||
|
|
||||||
@simulation.robots.each(&:draw)
|
@simulation&.robots&.each(&:draw)
|
||||||
@simulation.robots.each { |robot| robot.queue.first.draw if robot.queue.first && @simulation.show_paths }
|
@simulation&.robots&.each { |robot| robot.queue.first.draw if robot.queue.first && @simulation.show_paths }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -94,9 +98,185 @@ module TAC
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def draw_field_ultimate_goal
|
||||||
|
# middle line
|
||||||
|
Gosu.draw_rect(0, @field_size / 2 - 13, @field_size, 2, Gosu::Color::WHITE)
|
||||||
|
|
||||||
|
# phantom center line to indict half field for remote season field
|
||||||
|
Gosu.draw_rect(@field_size / 2 - (0.5 + 24), 0, 1, @field_size, 0x88_448844)
|
||||||
|
|
||||||
|
|
||||||
|
# blue starting lines
|
||||||
|
Gosu.draw_rect(24 - 1, @field_size - 24, 2, 24, @blue)
|
||||||
|
Gosu.draw_rect(48 - 1, @field_size - 24, 2, 24, @blue)
|
||||||
|
|
||||||
|
# blue wobbly wobs
|
||||||
|
Gosu.draw_circle(24, @field_size - 24, 4, 32, @blue)
|
||||||
|
Gosu.draw_circle(48, @field_size - 24, 4, 32, @blue)
|
||||||
|
|
||||||
|
# blue starter stack
|
||||||
|
Gosu.draw_rect(36 - 1, @field_size - 50, 2, 2, @blue)
|
||||||
|
|
||||||
|
# blue target zones
|
||||||
|
# A
|
||||||
|
draw_tile_box(@blue)
|
||||||
|
|
||||||
|
# B
|
||||||
|
Gosu.translate(24, 24) do
|
||||||
|
draw_tile_box(@blue)
|
||||||
|
end
|
||||||
|
|
||||||
|
# C
|
||||||
|
Gosu.translate(0, 48) do
|
||||||
|
draw_tile_box(@blue)
|
||||||
|
end
|
||||||
|
|
||||||
|
# red starting lines
|
||||||
|
Gosu.draw_rect(@field_size - 24 - 1, @field_size - 24, 2, 24, @red)
|
||||||
|
Gosu.draw_rect(@field_size - 48 - 1, @field_size - 24, 2, 24, @red)
|
||||||
|
|
||||||
|
# red wobbly wobs
|
||||||
|
Gosu.draw_circle(@field_size - 24, @field_size - 24, 4, 32, @red)
|
||||||
|
Gosu.draw_circle(@field_size - 48, @field_size - 24, 4, 32, @red)
|
||||||
|
|
||||||
|
# red starter stack
|
||||||
|
Gosu.draw_rect(@field_size - 37, @field_size - 50, 2, 2, @red)
|
||||||
|
|
||||||
|
# red target zones
|
||||||
|
# A
|
||||||
|
Gosu.translate(@field_size - 24, 0) do
|
||||||
|
draw_tile_box(@red)
|
||||||
|
end
|
||||||
|
|
||||||
|
# B
|
||||||
|
Gosu.translate(@field_size - 48, 24) do
|
||||||
|
draw_tile_box(@red)
|
||||||
|
end
|
||||||
|
|
||||||
|
# C
|
||||||
|
Gosu.translate(@field_size - 24, 48) do
|
||||||
|
draw_tile_box(@red)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def draw_field_freight_frenzy
|
||||||
|
# blue ZONE
|
||||||
|
Gosu.draw_rect(24, @field_size - 24, 2, 24, @blue)
|
||||||
|
Gosu.draw_rect(24, @field_size - 24, 24, 2, @blue)
|
||||||
|
Gosu.draw_rect(48 - 2, @field_size - 24, 2, 24, @blue)
|
||||||
|
|
||||||
|
# blue barcode 1
|
||||||
|
Gosu.draw_rect(36 - 1, @field_size - 24 - 4, 2, 2, @blue)
|
||||||
|
Gosu.draw_rect(36 - 1, @field_size - 36 - 1, 2, 2, @blue)
|
||||||
|
Gosu.draw_rect(36 - 1, @field_size - 48 + 2, 2, 2, @blue)
|
||||||
|
|
||||||
|
# blue barcode 2
|
||||||
|
Gosu.draw_rect(36 - 1, 48 + 2, 2, 2, @blue)
|
||||||
|
Gosu.draw_rect(36 - 1, 60 - 1, 2, 2, @blue)
|
||||||
|
Gosu.draw_rect(36 - 1, 72 - 4, 2, 2, @blue)
|
||||||
|
|
||||||
|
# blue wobble goal
|
||||||
|
Gosu.draw_circle(48, 84, 9, 32, @blue)
|
||||||
|
|
||||||
|
# blue shared wobble goal
|
||||||
|
Gosu.draw_circle(@field_size / 2, 24, 9, 32, @blue)
|
||||||
|
|
||||||
|
# red ZONE
|
||||||
|
Gosu.draw_rect(@field_size - 24 - 2, @field_size - 24, 2, 24, @red)
|
||||||
|
Gosu.draw_rect(@field_size - 48, @field_size - 24, 24, 2, @red)
|
||||||
|
Gosu.draw_rect(@field_size - 48, @field_size - 24, 2, 24, @red)
|
||||||
|
|
||||||
|
# red barcode 1
|
||||||
|
Gosu.draw_rect(@field_size - 36 - 1, @field_size - 24 - 4, 2, 2, @red)
|
||||||
|
Gosu.draw_rect(@field_size - 36 - 1, @field_size - 36 - 1, 2, 2, @red)
|
||||||
|
Gosu.draw_rect(@field_size - 36 - 1, @field_size - 48 + 2, 2, 2, @red)
|
||||||
|
|
||||||
|
# red barcode 2
|
||||||
|
Gosu.draw_rect(@field_size - 36 - 1, 48 + 2, 2, 2, @red)
|
||||||
|
Gosu.draw_rect(@field_size - 36 - 1, 60 - 1, 2, 2, @red)
|
||||||
|
Gosu.draw_rect(@field_size - 36 - 1, 72 - 4, 2, 2, @red)
|
||||||
|
|
||||||
|
# red wobble goal
|
||||||
|
Gosu.draw_circle(@field_size - 48, 84, 9, 32, @red)
|
||||||
|
|
||||||
|
# red shared wobble goal
|
||||||
|
Gosu.clip_to(@field_size / 2, 0, 10, 48) do
|
||||||
|
Gosu.draw_circle(@field_size / 2, 24, 9, 32, @red)
|
||||||
|
end
|
||||||
|
|
||||||
|
# white corner left
|
||||||
|
faint_white = Gosu::Color.rgb(240, 240, 240)
|
||||||
|
|
||||||
|
Gosu.draw_rect(0, 46 - 2, 46, 2, faint_white)
|
||||||
|
Gosu.draw_rect(46 - 2, 0, 2, 46, faint_white)
|
||||||
|
# white corner right
|
||||||
|
Gosu.draw_rect(@field_size - 46, 46 - 2, 46, 2, faint_white)
|
||||||
|
Gosu.draw_rect(@field_size - 46, 0, 2, 46, faint_white)
|
||||||
|
|
||||||
|
# cross bars
|
||||||
|
bar_gray = Gosu::Color.rgb(50, 50, 50)
|
||||||
|
# MAIN
|
||||||
|
Gosu.draw_rect(13.75, 48 - 2, @field_size - 13.75 * 2, 1, bar_gray)
|
||||||
|
Gosu.draw_rect(13.75, 48 + 1, @field_size - 13.75 * 2, 1, bar_gray)
|
||||||
|
Gosu.draw_rect(13.75, 48 - 2, 1, 4, Gosu::Color::BLACK)
|
||||||
|
Gosu.draw_rect(@field_size - 13.75 - 1, 48 - 2, 1, 4, Gosu::Color::BLACK)
|
||||||
|
|
||||||
|
# BLUE
|
||||||
|
Gosu.draw_rect(48 - 2, 13.75, 1, 48 - 13.75 - 2, bar_gray)
|
||||||
|
Gosu.draw_rect(48 + 1, 13.75, 1, 48 - 13.75 - 2, bar_gray)
|
||||||
|
Gosu.draw_rect(48 - 2, 13.75, 4, 1, Gosu::Color::BLACK)
|
||||||
|
Gosu.draw_rect(48 - 2, 48 - 3, 4, 1, Gosu::Color::BLACK)
|
||||||
|
|
||||||
|
# RED
|
||||||
|
Gosu.draw_rect(@field_size - 48 - 2, 13.75, 1, 48 - 13.75 - 2, bar_gray)
|
||||||
|
Gosu.draw_rect(@field_size - 48 + 1, 13.75, 1, 48 - 13.75 - 2, bar_gray)
|
||||||
|
Gosu.draw_rect(@field_size - 48 - 2, 13.75, 4, 1, Gosu::Color::BLACK)
|
||||||
|
Gosu.draw_rect(@field_size - 48 - 2, 48 - 3, 4, 1, Gosu::Color::BLACK)
|
||||||
|
|
||||||
|
# Duck Delivery
|
||||||
|
Gosu.draw_circle(2, @field_size - 2, 9, 16, Gosu::Color.rgb(75, 75, 75))
|
||||||
|
Gosu.draw_circle(@field_size - 2, @field_size - 2, 9, 16, Gosu::Color.rgb(75, 75, 75))
|
||||||
|
|
||||||
|
# packages
|
||||||
|
soft_orange = Gosu::Color.rgb(255, 175, 0)
|
||||||
|
|
||||||
|
7.times do |y|
|
||||||
|
7.times do |x|
|
||||||
|
if x.even?
|
||||||
|
Gosu.draw_rect(x * 3 + 1, y * 3 + 1, 2, 2, soft_orange)
|
||||||
|
else
|
||||||
|
Gosu.draw_circle(x * 3 + 2, y * 3 + 2, 1, 16, faint_white)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
7.times do |y|
|
||||||
|
7.times do |x|
|
||||||
|
if x.even?
|
||||||
|
Gosu.draw_rect((@field_size - 4) - x * 3 + 1, y * 3 + 1, 2, 2, soft_orange)
|
||||||
|
else
|
||||||
|
Gosu.draw_circle((@field_size - 4) - x * 3 + 2, y * 3 + 2, 1, 16, faint_white)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Gosu.draw_rect(0, 60 - 1, 2, 2, soft_orange)
|
||||||
|
Gosu.draw_rect(0, 108 - 1, 2, 2, soft_orange)
|
||||||
|
Gosu.draw_rect(@field_size - 2, 60 - 1, 2, 2, soft_orange)
|
||||||
|
Gosu.draw_rect(@field_size - 2, 108 - 1, 2, 2, soft_orange)
|
||||||
|
end
|
||||||
|
|
||||||
|
def draw_tile_box(color)
|
||||||
|
Gosu.draw_rect(0, 0, 24, 2, color)
|
||||||
|
Gosu.draw_rect(22, 2, 2, 22, color)
|
||||||
|
Gosu.draw_rect(0, 22, 22, 2, color)
|
||||||
|
Gosu.draw_rect(0, 2, 2, 22, color)
|
||||||
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@position.x, @position.y = @container.x, @container.y
|
@position.x = @container.x
|
||||||
@size = @container.width
|
@position.y = @container.y
|
||||||
|
@size = [@container.width, @container.height].min
|
||||||
@scale = @size.to_f / @field_size
|
@scale = @size.to_f / @field_size
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -75,6 +75,14 @@ module TAC
|
|||||||
@queue << Move.new(robot: self, distance: -distance, power: power)
|
@queue << Move.new(robot: self, distance: -distance, power: power)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def strafe_right(distance, power = 1.0)
|
||||||
|
@queue << Strafe.new(robot: self, distance: distance, power: power)
|
||||||
|
end
|
||||||
|
|
||||||
|
def strafe_left(distance, power = 1.0)
|
||||||
|
@queue << Strafe.new(robot: self, distance: -distance, power: power)
|
||||||
|
end
|
||||||
|
|
||||||
def turn(relative_angle, power = 1.0)
|
def turn(relative_angle, power = 1.0)
|
||||||
@queue << Turn.new(robot: self, relative_angle: relative_angle, power: power)
|
@queue << Turn.new(robot: self, relative_angle: relative_angle, power: power)
|
||||||
end
|
end
|
||||||
@@ -143,6 +151,52 @@ class State
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class Strafe < State
|
||||||
|
def initialize(robot:, distance:, power:)
|
||||||
|
@robot = robot
|
||||||
|
@distance = distance
|
||||||
|
@power = power.clamp(-1.0, 1.0)
|
||||||
|
end
|
||||||
|
|
||||||
|
def start
|
||||||
|
@starting_position = @robot.position.clone
|
||||||
|
@goal = @starting_position.clone
|
||||||
|
if @distance.positive?
|
||||||
|
@goal.x += Math.cos((@robot.angle + 90).gosu_to_radians) * @distance
|
||||||
|
@goal.y += Math.sin((@robot.angle + 90).gosu_to_radians) * @distance
|
||||||
|
else
|
||||||
|
@goal.x += Math.cos((@robot.angle - 90).gosu_to_radians) * @distance
|
||||||
|
@goal.y += Math.sin((@robot.angle - 90).gosu_to_radians) * @distance
|
||||||
|
end
|
||||||
|
|
||||||
|
@complete = false
|
||||||
|
@allowable_error = 1.0
|
||||||
|
end
|
||||||
|
|
||||||
|
def draw
|
||||||
|
Gosu.draw_line(
|
||||||
|
@robot.position.x + @robot.width / 2, @robot.position.y + @robot.depth / 2, TAC::Palette::TIMECRAFTERS_TERTIARY,
|
||||||
|
@goal.x + @robot.width / 2, @goal.y + @robot.depth / 2, TAC::Palette::TIMECRAFTERS_TERTIARY
|
||||||
|
)
|
||||||
|
Gosu.draw_rect(@goal.x + (@robot.width / 2 - 1), @goal.y + (@robot.depth / 2 - 1), 2, 2, Gosu::Color::RED)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(dt)
|
||||||
|
speed = (@distance > 0 ? @power * dt : -@power * dt) * @robot.speed
|
||||||
|
|
||||||
|
if @robot.position.distance(@goal) <= @allowable_error
|
||||||
|
@complete = true
|
||||||
|
@robot.position = @goal
|
||||||
|
else
|
||||||
|
if speed > 0
|
||||||
|
@robot.position -= (@robot.position - @goal).normalized * speed
|
||||||
|
else
|
||||||
|
@robot.position += (@robot.position - @goal).normalized * speed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class Turn < State
|
class Turn < State
|
||||||
def initialize(robot:, relative_angle:, power:)
|
def initialize(robot:, relative_angle:, power:)
|
||||||
@robot = robot
|
@robot = robot
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
module TAC
|
module TAC
|
||||||
class Simulator
|
class Simulator
|
||||||
class Simulation
|
class Simulation
|
||||||
attr_reader :robots, :show_paths
|
attr_reader :robots, :show_paths, :simulation_time
|
||||||
|
|
||||||
def initialize(source_code:, field_container:)
|
def initialize(source_code:, field_container:)
|
||||||
@source_code = source_code
|
@source_code = source_code
|
||||||
@field_container = field_container
|
@field_container = field_container
|
||||||
|
|
||||||
@robots = []
|
@robots = []
|
||||||
@field = Field.new(simulation: self, season: :skystone, container: @field_container)
|
@field = Field.new(simulation: self, season: :freight_frenzy, container: @field_container)
|
||||||
@show_paths = false
|
@show_paths = false
|
||||||
|
|
||||||
@last_milliseconds = Gosu.milliseconds
|
@last_milliseconds = Gosu.milliseconds
|
||||||
|
@simulation_step = 1.0 / 60.0
|
||||||
|
@accumulator = 0.0
|
||||||
|
@simulation_time = 0.0
|
||||||
end
|
end
|
||||||
|
|
||||||
def start
|
def start
|
||||||
@@ -23,8 +27,15 @@ module TAC
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
@accumulator += (Gosu.milliseconds - @last_milliseconds) / 1000.0
|
||||||
|
|
||||||
|
while(@accumulator > @simulation_step)
|
||||||
@field.update
|
@field.update
|
||||||
@robots.each { |robot| robot.update((Gosu.milliseconds - @last_milliseconds) / 1000.0) }
|
@robots.each { |robot| robot.update(@simulation_step) }
|
||||||
|
|
||||||
|
@accumulator -= @simulation_step
|
||||||
|
@simulation_time += @simulation_step
|
||||||
|
end
|
||||||
|
|
||||||
@last_milliseconds = Gosu.milliseconds
|
@last_milliseconds = Gosu.milliseconds
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,18 +6,22 @@ module TAC
|
|||||||
background [TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_SECONDARY, TAC::Palette::TIMECRAFTERS_TERTIARY, TAC::Palette::TIMECRAFTERS_PRIMARY]
|
background [TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_SECONDARY, TAC::Palette::TIMECRAFTERS_TERTIARY, TAC::Palette::TIMECRAFTERS_PRIMARY]
|
||||||
end
|
end
|
||||||
|
|
||||||
@title_font = CyberarmEngine::Text.new(TAC::NAME, z: 100, size: 72, shadow: true, shadow_size: 3, font: THEME[:Label][:font])
|
@title_font = CyberarmEngine::Text.new(TAC::NAME, z: 100, size: 72, border: true, border_size: 3, font: THEME[:Label][:font])
|
||||||
@logo = Gosu::Image.new("#{TAC::ROOT_PATH}/media/logo.png")
|
@logo = Gosu::Image.new("#{TAC::ROOT_PATH}/media/logo.png")
|
||||||
|
|
||||||
@animator = CyberarmEngine::Animator.new(start_time: 0, duration: 3_000, from: 0, to: 255)
|
@title_animator = CyberarmEngine::Animator.new(start_time: 0, duration: 750, from: 0.0, to: 1.0, tween: :swing_from_to)
|
||||||
@transition_color = Gosu::Color.new(0x00_000000)
|
@logo_animator = CyberarmEngine::Animator.new(start_time: 750, duration: 1_000, from: 0.0, to: 1.0, tween: :swing_to)
|
||||||
|
@transition_animator = CyberarmEngine::Animator.new(start_time: 2_250, duration: 750, from: 0, to: 255, tween: :ease_out)
|
||||||
|
@transition_color = Gosu::Color.new(0x00_111111)
|
||||||
|
|
||||||
|
@next_state = Editor
|
||||||
end
|
end
|
||||||
|
|
||||||
def draw
|
def draw
|
||||||
super
|
super
|
||||||
|
|
||||||
@title_font.draw
|
@title_font.draw
|
||||||
@logo.draw(window.width / 2 - @logo.width / 2, window.height / 2 - @logo.height / 2, 99)
|
@logo.draw_rot(window.width / 2, window.height / 2, 99, 0, 0.5, 0.5, @logo_animator.transition, @logo_animator.transition)
|
||||||
Gosu.draw_rect(0, 0, window.width, window.height, @transition_color, 10_00)
|
Gosu.draw_rect(0, 0, window.width, window.height, @transition_color, 10_00)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -25,17 +29,17 @@ module TAC
|
|||||||
super
|
super
|
||||||
|
|
||||||
@title_font.x = window.width / 2 - @title_font.width / 2
|
@title_font.x = window.width / 2 - @title_font.width / 2
|
||||||
@title_font.y = window.height / 2 - (@logo.height / 2 + @title_font.height)
|
@title_font.y = (window.height / 2 - (@logo.height / 2 + @title_font.height)) * @title_animator.transition
|
||||||
|
|
||||||
@transition_color.alpha = @animator.transition(0, 255, :sine)
|
@transition_color.alpha = @transition_animator.transition
|
||||||
|
|
||||||
push_state(Editor) if @transition_color.alpha >= 255
|
push_state(@next_state) if @transition_color.alpha >= 255
|
||||||
end
|
end
|
||||||
|
|
||||||
def button_up(id)
|
def button_up(id)
|
||||||
super
|
super
|
||||||
|
|
||||||
push_state(Editor)
|
push_state(@next_state)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,379 +1,199 @@
|
|||||||
module TAC
|
class Editor < CyberarmEngine::GuiState
|
||||||
class States
|
include CyberarmEngine::Theme # get access to deep_merge method
|
||||||
class Editor < CyberarmEngine::GuiState
|
attr_reader :header_bar, :header_bar_label, :navigation, :content, :menu_bar, :status_bar, :body
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
@active_group = nil
|
@window_width = 0
|
||||||
@active_action = nil
|
@window_height = 0
|
||||||
|
|
||||||
theme(THEME)
|
@pages = {}
|
||||||
|
@page = nil
|
||||||
|
|
||||||
stack width: 1.0, height: 1.0 do
|
# TODO: Use these colors for buttons
|
||||||
stack width: 1.0, height: 0.1, border_thickness: 1, border_color: [0, 0, Gosu::Color::BLACK, 0] do
|
_theme = {
|
||||||
background THEME_HEADER_BACKGROUND
|
Button: {
|
||||||
|
background: 0xff_006000,
|
||||||
|
border_color: 0x88_111111,
|
||||||
|
hover: {
|
||||||
|
color: 0xff_ffffff,
|
||||||
|
background: 0xff_00d000,
|
||||||
|
border_color: 0x88_111111
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
color: 0xff_ffffff,
|
||||||
|
background: 0xff_004000,
|
||||||
|
border_color: 0x88_111111
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
flow width: 1.0, height: 1.0 do
|
theme(deep_merge(TAC::THEME, _theme))
|
||||||
stack width: 0.60 do
|
|
||||||
label TAC::NAME, bold: true, text_size: THEME_HEADING_TEXT_SIZE
|
@header_bar = flow(width: 1.0, height: 36) do
|
||||||
flow width: 1.0 do
|
background 0xff_006000
|
||||||
flow width: 0.3 do
|
|
||||||
label "Group: "
|
@header_bar_label = label TAC::NAME, width: 1.0, text_align: :center, text_size: 32
|
||||||
@active_group_label = label ""
|
|
||||||
|
@window_controls = flow(x: window.width - 36 * 2, y: 0, height: 1.0) do
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/minus.png"), tip: "Minimize", image_height: 1.0 do
|
||||||
|
window.minimize if window.respond_to?(:minimize)
|
||||||
end
|
end
|
||||||
|
|
||||||
flow width: 0.3 do
|
button get_image("#{TAC::ROOT_PATH}/media/icons/larger.png"), tip: "Maximize", image_height: 1.0 do |btn|
|
||||||
label "Action: "
|
window.maximize if window.respond_to?(:maximize)
|
||||||
@active_action_label = label ""
|
|
||||||
end
|
end
|
||||||
|
|
||||||
flow width: 0.395 do
|
button get_image("#{TAC::ROOT_PATH}/media/icons/cross.png"), tip: "Exit", image_height: 1.0, **TAC::THEME_DANGER_BUTTON do
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/right.png"), image_width: THEME_ICON_SIZE, margin_left: 10, tip: "Simulator" do
|
window.close
|
||||||
push_state(Simulator)
|
|
||||||
end
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/menuList.png"), image_width: THEME_ICON_SIZE, margin_left: 10, tip: "Manage presets" do
|
|
||||||
push_state(ManagePresets)
|
|
||||||
end
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/wrench.png"), image_width: THEME_ICON_SIZE, margin_left: 10, tip: "Manage configurations" do
|
|
||||||
push_state(ManageConfigurations)
|
|
||||||
end
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/save.png"), image_width: THEME_ICON_SIZE, margin_left: 10, tip: "Save config and settings to disk" do
|
|
||||||
window.backend.save_config(window.backend.settings.config)
|
|
||||||
window.backend.save_settings
|
|
||||||
end
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/export.png"), image_width: THEME_ICON_SIZE, margin_left: 10, tip: "Upload local config to remote, if connected." do
|
|
||||||
window.backend.upload_config(window.backend.settings.config)
|
|
||||||
end
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/import.png"), image_width: THEME_ICON_SIZE, margin_left: 10, tip: "Download remote config, if connected." do
|
|
||||||
window.backend.download_config(window.backend.settings.config)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
flow width: 0.399 do
|
@container = flow(width: 1.0, height: 1.0) do
|
||||||
stack width: 0.5 do
|
@navigation = stack(width: 64, height: 1.0, scroll: true) do
|
||||||
label "TACNET v#{TACNET::Packet::PROTOCOL_VERSION}", color: TAC::Palette::TACNET_PRIMARY, text_shadow: true, text_shadow_size: 1, text_shadow_color: Gosu::Color::BLACK
|
background 0xff_333333
|
||||||
flow width: 1.0 do
|
|
||||||
@tacnet_hostname = edit_line "#{window.backend.settings.hostname}", width: 0.5, margin_right: 0
|
button get_image("#{TAC::ROOT_PATH}/media/icons/home.png"), margin: 4, tip: "Home", image_width: 1.0 do
|
||||||
@tacnet_hostname.subscribe(:changed) do |caller, value|
|
page(TAC::Pages::Home)
|
||||||
window.backend.settings.hostname = value
|
|
||||||
window.backend.settings_changed!
|
|
||||||
end
|
end
|
||||||
|
|
||||||
label ":", margin: 0, padding: 0, padding_top: 3
|
button get_image("#{TAC::ROOT_PATH}/media/icons/menuList.png"), margin: 4, tip: "Editor", image_width: 1.0 do
|
||||||
|
page(TAC::Pages::Editor)
|
||||||
|
end
|
||||||
|
|
||||||
@tacnet_port = edit_line "#{window.backend.settings.port}", width: 0.2, margin_left: 0
|
@tacnet_button = button get_image("#{TAC::ROOT_PATH}/media/icons/signal3.png"), margin: 4, tip: "TACNET", image_width: 1.0 do
|
||||||
@tacnet_port.subscribe(:changed) do |caller, value|
|
page(TAC::Pages::TACNET)
|
||||||
window.backend.settings.port = Integer(value)
|
end
|
||||||
window.backend.settings_changed!
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/right.png"), margin: 4, tip: "Simulator", image_width: 1.0 do
|
||||||
|
page(TAC::Pages::Simulator)
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/gear.png"), margin: 4, tip: "Configurations", image_width: 1.0 do
|
||||||
|
page(TAC::Pages::Configurations)
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/menuGrid.png"), margin: 4, tip: "Presets", image_width: 1.0 do
|
||||||
|
page(TAC::Pages::Presets)
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/zoom.png"), margin: 4, tip: "Search", image_width: 1.0 do
|
||||||
|
page(TAC::Pages::Search)
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/joystickLeft.png"), margin: 4, tip: "Field Planner", image_width: 1.0 do
|
||||||
|
page(TAC::Pages::FieldPlanner)
|
||||||
|
end
|
||||||
|
|
||||||
|
button get_image("#{TAC::ROOT_PATH}/media/icons/massiveMultiplayer.png"), margin: 4, tip: "Drive Team Rotation Generator", image_width: 1.0 do
|
||||||
|
page(TAC::Pages::DriveTeamRotationGenerator)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@content = stack(width: window.width - @navigation.style.width, height: 1.0) do
|
||||||
|
@chrome = stack(width: 1.0, height: 96) do
|
||||||
|
@menu_bar = flow(width: 1.0, height: 48, padding: 8) do
|
||||||
|
background 0xff_008000
|
||||||
|
end
|
||||||
|
|
||||||
|
@status_bar = flow(width: 1.0, height: 96 - 48, padding: 2) do
|
||||||
|
background 0xff_006000
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@body = stack(width: 1.0, height: 1.0) do
|
||||||
|
background 0xff_707070
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
stack width: 0.499 do
|
@window_controls.hide unless BORDERLESS
|
||||||
@tacnet_status = label "Not Connected", background: TAC::Palette::TACNET_NOT_CONNECTED, width: 1.0, padding: 5, margin_top: 2, border_thickness: 1, border_color: Gosu::Color::GRAY
|
|
||||||
flow width: 1.0 do
|
page(TAC::Pages::Home)
|
||||||
@tacnet_connection_button = button "Connect", width: 0.475 do
|
|
||||||
case window.backend.tacnet.status
|
|
||||||
when :connected, :connecting
|
|
||||||
window.backend.tacnet.close
|
|
||||||
when :not_connected, :connection_error
|
|
||||||
window.backend.tacnet.connect(@tacnet_hostname.value, @tacnet_port.value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/information.png"), image_width: THEME_ICON_SIZE, width: 0.475 do
|
|
||||||
push_state(Dialog::AlertDialog, title: "TACNET Status", message: window.backend.tacnet.full_status)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@content = flow width: 1.0, height: 0.9 do
|
def draw
|
||||||
background THEME_CONTENT_BACKGROUND
|
super
|
||||||
stack width: 0.333, height: 1.0, border_thickness: 1, border_color: [0, Gosu::Color::BLACK, 0, 0] do
|
|
||||||
flow do
|
|
||||||
label "Groups", text_size: THEME_SUBHEADING_TEXT_SIZE
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_width: THEME_ICON_SIZE, tip: "Add group" do
|
|
||||||
push_state(TAC::Dialog::NamePromptDialog, title: "Create Group", list: window.backend.config.groups, callback_method: method(:create_group))
|
|
||||||
end
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/button2.png"), image_width: THEME_ICON_SIZE, tip: "Clone currently selected group" do
|
|
||||||
if @active_group
|
|
||||||
push_state(Dialog::NamePromptDialog, title: "Clone Group", renaming: @active_group, accept_label: "Clone", list: window.backend.config.groups, callback_method: proc { |group, name|
|
|
||||||
clone = TAC::Config::Group.from_json( JSON.parse( @active_group.to_json, symbolize_names: true ))
|
|
||||||
clone.name = "#{name}"
|
|
||||||
window.backend.config.groups << clone
|
|
||||||
window.backend.config_changed!
|
|
||||||
|
|
||||||
populate_groups_list
|
@page.draw if @page
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/save.png"), image_width: THEME_ICON_SIZE, tip: "Save group as preset" do
|
|
||||||
if @active_group
|
|
||||||
push_state(Dialog::NamePromptDialog, title: "Save Group Preset", renaming: @active_group, accept_label: "Save", list: window.backend.config.presets.actions, callback_method: proc { |action, name|
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@groups_list = stack width: 1.0 do
|
|
||||||
end
|
|
||||||
end
|
|
||||||
stack width: 0.333, height: 1.0, border_thickness: 1, border_color: [0, Gosu::Color::BLACK, 0, 0] do
|
|
||||||
flow do
|
|
||||||
label "Actions", text_size: THEME_SUBHEADING_TEXT_SIZE
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_width: THEME_ICON_SIZE, tip: "Add action" do
|
|
||||||
if @active_group
|
|
||||||
push_state(TAC::Dialog::NamePromptDialog, title: "Create Action", list: @active_group.actions, callback_method: method(:create_action))
|
|
||||||
else
|
|
||||||
push_state(TAC::Dialog::AlertDialog, title: "Error", message: "Unable to create action,\nno group selected.")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/button2.png"), image_width: THEME_ICON_SIZE, tip: "Clone currently selected action" do
|
|
||||||
if @active_group && @active_action
|
|
||||||
push_state(Dialog::NamePromptDialog, title: "Clone Action", renaming: @active_action, accept_label: "Clone", list: @active_group.actions, callback_method: proc { |action, name|
|
|
||||||
clone = TAC::Config::Action.from_json( JSON.parse( @active_action.to_json, symbolize_names: true ))
|
|
||||||
clone.name = name
|
|
||||||
@active_group.actions << clone
|
|
||||||
window.backend.config_changed!
|
|
||||||
|
|
||||||
populate_actions_list(@active_group)
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/save.png"), image_width: THEME_ICON_SIZE, tip: "Save action as preset" do
|
|
||||||
if @active_action
|
|
||||||
push_state(Dialog::NamePromptDialog, title: "Save Action Preset", renaming: @active_action, accept_label: "Save", list: window.backend.config.presets.actions, callback_method: proc { |action, name|
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@actions_list = stack width: 1.0 do
|
|
||||||
end
|
|
||||||
end
|
|
||||||
stack width: 0.331, height: 1.0 do
|
|
||||||
flow do
|
|
||||||
label "Variables", text_size: THEME_SUBHEADING_TEXT_SIZE
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_width: THEME_ICON_SIZE, tip: "Add variable" do
|
|
||||||
if @active_action
|
|
||||||
push_state(TAC::Dialog::VariableDialog, title: "Create Variable", callback_method: method(:create_variable))
|
|
||||||
else
|
|
||||||
push_state(TAC::Dialog::AlertDialog, title: "Error", message: "Unable to create variable,\nno action selected.")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@variables_list = stack width: 1.0 do
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if window.backend.settings.config == nil || window.backend.config == nil
|
|
||||||
push_state(ManageConfigurations)
|
|
||||||
else
|
|
||||||
populate_groups_list
|
|
||||||
end
|
|
||||||
|
|
||||||
@tacnet_status_monitor = CyberarmEngine::Timer.new(250) do
|
|
||||||
case window.backend.tacnet.status
|
|
||||||
when :connected
|
|
||||||
@tacnet_status.value = "Connected"
|
|
||||||
@tacnet_status.background = TAC::Palette::TACNET_CONNECTED
|
|
||||||
|
|
||||||
@tacnet_connection_button.value = "Disconnect"
|
|
||||||
when :connecting
|
|
||||||
@tacnet_status.value = "Connecting..."
|
|
||||||
@tacnet_status.background = TAC::Palette::TACNET_CONNECTING
|
|
||||||
|
|
||||||
@tacnet_connection_button.value = "Disconnect"
|
|
||||||
when :connection_error
|
|
||||||
@tacnet_status.value = "Connection Error"
|
|
||||||
@tacnet_status.background = TAC::Palette::TACNET_CONNECTION_ERROR
|
|
||||||
|
|
||||||
@tacnet_connection_button.value = "Connect"
|
|
||||||
when :not_connected
|
|
||||||
@tacnet_status.value = "Not Connected"
|
|
||||||
@tacnet_status.background = TAC::Palette::TACNET_NOT_CONNECTED
|
|
||||||
|
|
||||||
@tacnet_connection_button.value = "Connect"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
super
|
super
|
||||||
|
|
||||||
@tacnet_status_monitor.update
|
@page.update if @page
|
||||||
|
|
||||||
|
case window.backend.tacnet.status
|
||||||
|
when :not_connected
|
||||||
|
@tacnet_button.style.color = Gosu::Color::WHITE
|
||||||
|
when :connecting
|
||||||
|
@tacnet_button.style.color = TAC::Palette::TACNET_CONNECTING
|
||||||
|
when :connected
|
||||||
|
@tacnet_button.style.color = TAC::Palette::TACNET_CONNECTED
|
||||||
|
when :connection_error
|
||||||
|
@tacnet_button.style.color = TAC::Palette::TACNET_CONNECTION_ERROR
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_group(name)
|
window.width = Gosu.available_width / 2 if window.width < Gosu.available_width / 2
|
||||||
window.backend.config.groups << TAC::Config::Group.new(name: name, actions: [])
|
window.height = Gosu.available_height / 2 if window.height < Gosu.available_height / 2
|
||||||
window.backend.config_changed!
|
|
||||||
|
|
||||||
populate_groups_list
|
if window.width != @window_width || window.height != @window_height
|
||||||
end
|
@window_width = window.width
|
||||||
|
@window_height = window.height
|
||||||
|
|
||||||
def update_group(group, name)
|
recalc
|
||||||
group.name = name
|
|
||||||
window.backend.config_changed!
|
|
||||||
|
|
||||||
populate_groups_list
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_group(group)
|
|
||||||
window.backend.config.groups.delete(group)
|
|
||||||
window.backend.config_changed!
|
|
||||||
|
|
||||||
@active_group = nil
|
|
||||||
@active_group_label.value = ""
|
|
||||||
@active_action = nil
|
|
||||||
@active_action_label.value = ""
|
|
||||||
@actions_list.clear
|
|
||||||
@variables_list.clear
|
|
||||||
|
|
||||||
populate_groups_list
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_action(name)
|
|
||||||
@active_group.actions << TAC::Config::Action.new(name: name, enabled: true, variables: [])
|
|
||||||
window.backend.config_changed!
|
|
||||||
|
|
||||||
populate_actions_list(@active_group)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_action(action, name)
|
|
||||||
action.name = name
|
|
||||||
window.backend.config_changed!
|
|
||||||
|
|
||||||
populate_actions_list(@active_group)
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_action(action)
|
|
||||||
@active_group.actions.delete(action)
|
|
||||||
window.backend.config_changed!
|
|
||||||
|
|
||||||
@active_action = nil
|
|
||||||
@active_action_label.value = ""
|
|
||||||
@variables_list.clear
|
|
||||||
|
|
||||||
populate_actions_list(@active_group)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_variable(name, type, value)
|
|
||||||
@active_action.variables << TAC::Config::Variable.new(name: name, type: type, value: value)
|
|
||||||
window.backend.config_changed!
|
|
||||||
|
|
||||||
populate_variables_list(@active_action)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_variable(variable, name, type, value)
|
|
||||||
variable.name = name
|
|
||||||
variable.type = type
|
|
||||||
variable.value = value
|
|
||||||
|
|
||||||
window.backend.config_changed!
|
|
||||||
|
|
||||||
populate_variables_list(@active_action)
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_variable(variable)
|
|
||||||
@active_action.variables.delete(variable)
|
|
||||||
window.backend.config_changed!
|
|
||||||
|
|
||||||
populate_variables_list(@active_action)
|
|
||||||
end
|
|
||||||
|
|
||||||
def populate_groups_list
|
|
||||||
groups = window.backend.config.groups
|
|
||||||
|
|
||||||
@groups_list.clear do
|
|
||||||
groups.each_with_index do |group, i|
|
|
||||||
flow width: 1.0, **THEME_ITEM_CONTAINER_PADDING do
|
|
||||||
background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
|
||||||
|
|
||||||
button group.name, width: 0.855 do
|
|
||||||
@active_group = group
|
|
||||||
@active_group_label.value = group.name
|
|
||||||
@active_action = nil
|
|
||||||
@active_action_label.value = ""
|
|
||||||
|
|
||||||
populate_actions_list(group)
|
|
||||||
@variables_list.clear
|
|
||||||
end
|
|
||||||
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/gear.png"), image_width: THEME_ICON_SIZE, tip: "Edit group" do
|
|
||||||
push_state(Dialog::NamePromptDialog, title: "Rename Group", renaming: group, list: window.backend.config.groups, callback_method: method(:update_group))
|
|
||||||
end
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/trashcan.png"), image_width: THEME_ICON_SIZE, tip: "Delete group", **THEME_DANGER_BUTTON do
|
|
||||||
push_state(Dialog::ConfirmDialog, title: "Are you sure?", message: "Delete group and all\nof its actions and variables?", callback_method: proc { delete_group(group) })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def button_down(id)
|
||||||
|
super
|
||||||
|
|
||||||
|
@page&.button_down(id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def populate_actions_list(group)
|
def button_up(id)
|
||||||
actions = group.actions
|
super
|
||||||
|
|
||||||
@actions_list.clear do
|
@page&.button_up(id)
|
||||||
actions.each_with_index do |action, i|
|
|
||||||
flow width: 1.0, **THEME_ITEM_CONTAINER_PADDING do
|
|
||||||
background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
|
||||||
|
|
||||||
button action.name, width: 0.8 do
|
|
||||||
@active_action = action
|
|
||||||
@active_action_label.value = action.name
|
|
||||||
|
|
||||||
populate_variables_list(action)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
toggle_button tip: "Enable action"
|
def recalc
|
||||||
|
@window_controls.style.x = window.width - @window_controls.width
|
||||||
|
@container.style.height = window.height - @header_bar.height
|
||||||
|
@content.style.width = window.width - @navigation.width
|
||||||
|
@body.style.height = window.height - (@chrome.height + @header_bar.height)
|
||||||
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/gear.png"), image_width: THEME_ICON_SIZE, tip: "Edit action" do
|
|
||||||
push_state(Dialog::NamePromptDialog, title: "Rename Action", renaming: action, list: @active_group.actions, callback_method: method(:update_action))
|
request_recalculate
|
||||||
end
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/trashcan.png"), image_width: THEME_ICON_SIZE, tip: "Delete action", **THEME_DANGER_BUTTON do
|
|
||||||
push_state(Dialog::ConfirmDialog, title: "Are you sure?", message: "Delete action and all\nof its variables?", callback_method: proc { delete_action(action) })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def populate_variables_list(action)
|
def page(klass, options = {})
|
||||||
variables = action.variables
|
@menu_bar.clear
|
||||||
|
@status_bar.clear
|
||||||
|
@body.clear
|
||||||
|
|
||||||
@variables_list.clear do
|
if window.backend.settings.config.empty? && page_requires_configuration?(klass)
|
||||||
variables.each_with_index do |variable, i|
|
push_state(TAC::Dialog::AlertDialog, title: "No Config Loaded", message: "A config must be loaded.")
|
||||||
flow width: 1.0, **THEME_ITEM_CONTAINER_PADDING do
|
page(TAC::Pages::Configurations)
|
||||||
background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
|
||||||
|
|
||||||
button "#{variable.name} [Type: #{variable.type}, Value: #{variable.value}]", width: 0.925, tip: "Edit variable" do
|
return
|
||||||
push_state(Dialog::VariableDialog, title: "Edit Variable", variable: variable, callback_method: method(:update_variable))
|
|
||||||
end
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/trashcan.png"), image_width: THEME_ICON_SIZE, tip: "Delete variable", **THEME_DANGER_BUTTON do
|
|
||||||
push_state(Dialog::ConfirmDialog, title: "Are you sure?", message: "Delete variable?", callback_method: proc { delete_variable(variable) })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def refresh_config
|
@page.blur if @page
|
||||||
@active_group = nil
|
|
||||||
@active_group_label.value = ""
|
|
||||||
@active_action = nil
|
|
||||||
@active_action_label.value = ""
|
|
||||||
|
|
||||||
@groups_list.clear
|
@pages[klass] = klass.new(host: self) unless @pages[klass]
|
||||||
@actions_list.clear
|
@page = @pages[klass]
|
||||||
@variables_list.clear
|
|
||||||
|
|
||||||
populate_groups_list
|
@page.options = options
|
||||||
end
|
@page.setup
|
||||||
|
@page.focus
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def page_requires_configuration?(klass)
|
||||||
|
[
|
||||||
|
TAC::Pages::Editor,
|
||||||
|
TAC::Pages::Presets,
|
||||||
|
TAC::Pages::Search
|
||||||
|
].include?(klass)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
module TAC
|
|
||||||
class States
|
|
||||||
class ManagePresets < CyberarmEngine::GuiState
|
|
||||||
def setup
|
|
||||||
theme(THEME)
|
|
||||||
|
|
||||||
stack width: 1.0, height: 0.1 do
|
|
||||||
background THEME_HEADER_BACKGROUND
|
|
||||||
label "#{TAC::NAME} ― Manage Presets", bold: true, text_size: THEME_HEADING_TEXT_SIZE
|
|
||||||
button "Close" 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
|
|
||||||
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
|
|
||||||
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
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
module TAC
|
|
||||||
class States
|
|
||||||
class Simulator < CyberarmEngine::GuiState
|
|
||||||
def setup
|
|
||||||
theme(THEME)
|
|
||||||
|
|
||||||
stack width: 1.0, height: 0.1 do
|
|
||||||
background THEME_HEADER_BACKGROUND
|
|
||||||
label "#{TAC::NAME} ― Simulator", bold: true, text_size: THEME_HEADING_TEXT_SIZE
|
|
||||||
button "Close" do
|
|
||||||
pop_state
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
flow width: 1.0, height: 0.9 do
|
|
||||||
@field_container = stack width: 0.4, height: 1.0 do
|
|
||||||
background Gosu::Color.new(0xff_333333)..Gosu::Color::BLACK
|
|
||||||
end
|
|
||||||
|
|
||||||
stack width: 0.6, height: 1.0 do
|
|
||||||
background Gosu::Color.new(0x88_ff8800)
|
|
||||||
|
|
||||||
flow width: 1.0, height: 0.05 do
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/right.png"), image_width: THEME_ICON_SIZE, width: 0.49, tip: "Run simulation" do
|
|
||||||
begin
|
|
||||||
@simulation_start_time = Gosu.milliseconds
|
|
||||||
@simulation = TAC::Simulator::Simulation.new(source_code: @source_code.value, field_container: @field_container)
|
|
||||||
@simulation.start
|
|
||||||
rescue SyntaxError, NameError, NoMethodError, TypeError, ArgumentError => e
|
|
||||||
puts e.backtrace.reverse.join("\n")
|
|
||||||
puts e
|
|
||||||
push_state(Dialog::AlertDialog, title: "#{e.class}", message: e)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/stop.png"), image_width: THEME_ICON_SIZE, width: 0.49, tip: "Stop simulation" do
|
|
||||||
@simulation.robots.each { |robot| robot.queue.clear } if @simulation
|
|
||||||
end
|
|
||||||
button get_image("#{TAC::ROOT_PATH}/media/icons/save.png"), image_width: THEME_ICON_SIZE, width: 0.49, tip: "Save source code" do
|
|
||||||
File.open("#{TAC::ROOT_PATH}/data/simulator.rb", "w") { |f| f.write @source_code.value }
|
|
||||||
end
|
|
||||||
|
|
||||||
@simulation_status = label ""
|
|
||||||
end
|
|
||||||
|
|
||||||
stack width: 1.0, height: 0.95 do
|
|
||||||
source_code = ""
|
|
||||||
if File.exist?("#{TAC::ROOT_PATH}/data/simulator.rb")
|
|
||||||
source_code = File.read("#{TAC::ROOT_PATH}/data/simulator.rb")
|
|
||||||
else
|
|
||||||
source_code =
|
|
||||||
"robot = create_robot(alliance: :blue, width: 18, depth: 18)
|
|
||||||
robot.backward 100
|
|
||||||
robot.turn 90
|
|
||||||
robot.forward 100
|
|
||||||
robot.turn -90
|
|
||||||
robot.forward 100
|
|
||||||
robot.turn -90
|
|
||||||
robot.forward 100"
|
|
||||||
end
|
|
||||||
@source_code = edit_box source_code, width: 1.0, height: 1.0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def draw
|
|
||||||
super
|
|
||||||
|
|
||||||
Gosu.flush
|
|
||||||
@simulation.draw if @simulation
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
super
|
|
||||||
|
|
||||||
@simulation.update if @simulation
|
|
||||||
@simulation_status.value = "Time: #{((Gosu.milliseconds - @simulation_start_time) / 1000.0).round(1)} seconds" if @simulation_start_time
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -41,15 +41,15 @@ module TAC
|
|||||||
net_stats += "<b>Data Received:</b> #{client.data_received} bytes\n"
|
net_stats += "<b>Data Received:</b> #{client.data_received} bytes\n"
|
||||||
|
|
||||||
"<b>Status:</b> #{_status}\n\n#{net_stats}"
|
"<b>Status:</b> #{_status}\n\n#{net_stats}"
|
||||||
elsif @connection && @connection.client && @connection.client.socket_error?
|
elsif @connection&.client && @connection.client.socket_error?
|
||||||
"<b>Status:</b> #{_status}\n\n#{@connection.client.last_socket_error.to_s.chars.each_slice(32).to_a.map { |c| c.join }.join("\n")}"
|
"<b>Status:</b> #{_status}\n\n#{@connection.client.last_socket_error.to_s}"
|
||||||
else
|
else
|
||||||
"<b>Status:</b> #{_status}"
|
"<b>Status:</b> #{_status}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def connected?
|
def connected?
|
||||||
@connection && @connection.connected?
|
@connection&.connected?
|
||||||
end
|
end
|
||||||
|
|
||||||
def close
|
def close
|
||||||
@@ -60,7 +60,7 @@ module TAC
|
|||||||
end
|
end
|
||||||
|
|
||||||
def client
|
def client
|
||||||
@connection.client if connected?
|
@connection.client
|
||||||
end
|
end
|
||||||
|
|
||||||
def puts(packet)
|
def puts(packet)
|
||||||
|
|||||||
@@ -2,14 +2,21 @@ module TAC
|
|||||||
class TACNET
|
class TACNET
|
||||||
class Packet
|
class Packet
|
||||||
PROTOCOL_VERSION = 1
|
PROTOCOL_VERSION = 1
|
||||||
PROTOCOL_HEADER_SEPERATOR = "|"
|
PROTOCOL_SEPERATOR = "|"
|
||||||
PROTOCOL_HEARTBEAT = "heartbeat"
|
PROTOCOL_HEARTBEAT = "heartbeat"
|
||||||
|
|
||||||
PACKET_TYPES = {
|
PACKET_TYPES = {
|
||||||
handshake: 0,
|
handshake: 0,
|
||||||
heartbeat: 1,
|
heartbeat: 1,
|
||||||
download_config: 2,
|
error: 2,
|
||||||
upload_config: 3,
|
|
||||||
|
download_config: 10,
|
||||||
|
upload_config: 11,
|
||||||
|
list_configs: 12,
|
||||||
|
select_config: 13,
|
||||||
|
add_config: 14,
|
||||||
|
update_config: 15,
|
||||||
|
delete_config: 16,
|
||||||
|
|
||||||
add_group: 20,
|
add_group: 20,
|
||||||
update_group: 21,
|
update_group: 21,
|
||||||
@@ -28,7 +35,7 @@ module TAC
|
|||||||
slice = message.split("|", 4)
|
slice = message.split("|", 4)
|
||||||
|
|
||||||
if slice.size < 4
|
if slice.size < 4
|
||||||
warn "Failed to split packet along first 4 " + PROTOCOL_HEADER_SEPERATOR + ". Raw return: " + slice.to_s
|
warn "Failed to split packet along first 4 " + PROTOCOL_SEPERATOR + ". Raw return: " + slice.to_s
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -71,11 +78,11 @@ module TAC
|
|||||||
def encode_header
|
def encode_header
|
||||||
string = ""
|
string = ""
|
||||||
string += protocol_version.to_s
|
string += protocol_version.to_s
|
||||||
string += PROTOCOL_HEADER_SEPERATOR
|
string += PROTOCOL_SEPERATOR
|
||||||
string += PACKET_TYPES[type].to_s
|
string += PACKET_TYPES[type].to_s
|
||||||
string += PROTOCOL_HEADER_SEPERATOR
|
string += PROTOCOL_SEPERATOR
|
||||||
string += content_length.to_s
|
string += content_length.to_s
|
||||||
string += PROTOCOL_HEADER_SEPERATOR
|
string += PROTOCOL_SEPERATOR
|
||||||
|
|
||||||
return string
|
return string
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ module TAC
|
|||||||
packet = Packet.from_stream(message)
|
packet = Packet.from_stream(message)
|
||||||
|
|
||||||
if packet
|
if packet
|
||||||
|
log.i(TAG, "Received packet of type: #{packet.type}")
|
||||||
hand_off(packet)
|
hand_off(packet)
|
||||||
else
|
else
|
||||||
log.d(TAG, "Rejected raw packet: #{message}")
|
log.d(TAG, "Rejected raw packet: #{message}")
|
||||||
@@ -22,10 +23,23 @@ module TAC
|
|||||||
handle_handshake(packet)
|
handle_handshake(packet)
|
||||||
when :heartbeat
|
when :heartbeat
|
||||||
handle_heartbeat(packet)
|
handle_heartbeat(packet)
|
||||||
|
when :error
|
||||||
|
handle_error(packet)
|
||||||
|
|
||||||
when :download_config
|
when :download_config
|
||||||
handle_download_config(packet)
|
handle_download_config(packet)
|
||||||
when :upload_config
|
when :upload_config
|
||||||
handle_upload_config(packet)
|
handle_upload_config(packet)
|
||||||
|
when :list_configs
|
||||||
|
handle_list_configs(packet)
|
||||||
|
when :select_config
|
||||||
|
handle_select_config(packet)
|
||||||
|
when :add_config
|
||||||
|
handle_add_config(packet)
|
||||||
|
when :update_config # rename config/file
|
||||||
|
handle_update_config(packet)
|
||||||
|
when :delete_config
|
||||||
|
handle_delete_config(packet)
|
||||||
else
|
else
|
||||||
log.d(TAG, "No hand off available for packet type: #{packet.type}")
|
log.d(TAG, "No hand off available for packet type: #{packet.type}")
|
||||||
end
|
end
|
||||||
@@ -41,52 +55,134 @@ module TAC
|
|||||||
def handle_heartbeat(packet)
|
def handle_heartbeat(packet)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: Handle errors
|
||||||
|
def handle_error(packet)
|
||||||
|
if @host_is_a_connection
|
||||||
|
title, message = packet.body.split(Packet::PROTOCOL_SEPERATOR, 2)
|
||||||
|
$window.push_state(TAC::Dialog::TACNETDialog, title: title, message: message)
|
||||||
|
else
|
||||||
|
log.e(TAG, "Remote error: #{title}: #{message}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def handle_upload_config(packet)
|
def handle_upload_config(packet)
|
||||||
begin
|
begin
|
||||||
data = JSON.parse(packet.body, symbolize_names: true)
|
config_name, json = packet.body.split(Packet::PROTOCOL_SEPERATOR, 2)
|
||||||
|
data = JSON.parse(json, symbolize_names: true)
|
||||||
|
|
||||||
if @host_is_a_connection
|
if data.is_a?(Hash) && data.dig(:config, :spec_version) == TAC::CONFIG_SPEC_VERSION
|
||||||
if data.is_a?(Array)
|
File.open("#{TAC::CONFIGS_PATH}/#{config_name}.json", "w") { |f| f.write json }
|
||||||
# OLDEST CONFIG, upgrade?
|
|
||||||
$window.push_state(TAC::Dialog::AlertDialog, title: "Invalid Config", message: "Remote config to old.")
|
|
||||||
|
|
||||||
elsif data.is_a?(Hash) && data.dig(:config, :spec_version) == TAC::CONFIG_SPEC_VERSION
|
|
||||||
$window.push_state(TAC::Dialog::ConfirmDialog, title: "Replace Config", message: "Replace local config\nwith remote config?", callback_method: proc {
|
|
||||||
File.open("#{TAC::ROOT_PATH}/data/config.json", "w") { |f| f.write packet.body }
|
|
||||||
|
|
||||||
$window.backend.update_config
|
|
||||||
})
|
|
||||||
|
|
||||||
elsif data.is_a?(Hash) && data.dig(:config, :spec_version) < TAC::CONFIG_SPEC_VERSION
|
|
||||||
# OLD CONFIG, Upgrade?
|
|
||||||
$window.push_state(TAC::Dialog::ConfirmDialog, title: "Upgrade Config", message: "Remote config is an older\nspec version.\nTry to upgrade?", callback_method: proc {})
|
|
||||||
|
|
||||||
elsif data.is_a?(Hash) && data.dig(:config, :spec_version) > TAC::CONFIG_SPEC_VERSION
|
|
||||||
# NEWER CONFIG, Error Out
|
|
||||||
$window.push_state(TAC::Dialog::AlertDialog, title: "Invalid Config", message: "Client outdated, check for\nupdates.\nSupported config spec:\nv#{TAC::CONFIG_SPEC_VERSION} got v#{data.dig(:config, :spec_version)}")
|
|
||||||
|
|
||||||
|
if $window.backend.config&.name == config_name
|
||||||
|
$window.backend.load_config(config_name)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
# CONFIG is unknown
|
$window.push_state(TAC::Dialog::AlertDialog, title: "Invalid Config", message: "Supported config spec: v#{TAC::CONFIG_SPEC_VERSION} got v#{data.dig(:config, :spec_version)}")
|
||||||
$window.push_state(TAC::Dialog::AlertDialog, title: "Invalid Config", message: "Remote config is not supported.")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
rescue JSON::ParserError => e
|
rescue JSON::ParserError => e
|
||||||
log.e(TAG, "JSON parsing error: #{e}")
|
log.e(TAG, "JSON parsing error: #{e}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_download_config(packet)
|
def handle_download_config(packet)
|
||||||
|
config_name = packet.body
|
||||||
|
log.i(TAG, config_name)
|
||||||
|
pkt = nil
|
||||||
|
|
||||||
|
if File.exist?("#{TAC::CONFIGS_PATH}/#{config_name}.json")
|
||||||
|
pkt = PacketHandler.packet_upload_config(config_name, Config.new(config_name).to_json)
|
||||||
|
else
|
||||||
|
pkt = PacketHandler.packet_error("Remote config not found", "The requested config #{config_name} does not exist over here.")
|
||||||
|
end
|
||||||
|
|
||||||
if @host_is_a_connection
|
if @host_is_a_connection
|
||||||
json = JSON.dump($window.backend.config)
|
$window.backend.tacnet.puts(pkt)
|
||||||
$window.backend.tacnet.puts(PacketHandler.packet_upload_config(json))
|
else
|
||||||
|
$server.active_client.puts(pkt)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_list_configs(packet)
|
||||||
|
if @host_is_a_connection # Download new or updated configs
|
||||||
|
list = packet.body.split(Packet::PROTOCOL_SEPERATOR).map { |part| part.split(",") }
|
||||||
|
|
||||||
|
remote_configs = list.map { |l| l.first }
|
||||||
|
local_configs = Dir.glob("#{TAC::CONFIGS_PATH}/*.json").map { |f| File.basename(f, ".json") }
|
||||||
|
_diff = local_configs - remote_configs
|
||||||
|
|
||||||
|
list.each do |name, revision|
|
||||||
|
revision = Integer(revision)
|
||||||
|
path = "#{TAC::CONFIGS_PATH}/#{name}.json"
|
||||||
|
|
||||||
|
if File.exist?(path)
|
||||||
|
config = Config.new(name)
|
||||||
|
|
||||||
|
if config.configuration.revision < revision
|
||||||
|
$window.backend.tacnet.puts( PacketHandler.packet_download_config(name) )
|
||||||
|
elsif config.configuration.revision > revision
|
||||||
|
$window.backend.tacnet.puts( PacketHandler.packet_upload_config(name, JSON.dump( config )) )
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
$window.backend.tacnet.puts( PacketHandler.packet_download_config(name) )
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
_diff.each do |name|
|
||||||
|
config = Config.new(name)
|
||||||
|
|
||||||
|
$window.backend.tacnet.puts( PacketHandler.packet_upload_config(name, JSON.dump( config )) )
|
||||||
|
end
|
||||||
else
|
else
|
||||||
if $server.active_client && $server.active_client.connected?
|
if $server.active_client && $server.active_client.connected?
|
||||||
json = File.read(TAC::CONFIGS_PATH)
|
$server.active_client.puts(PacketHandler.packet_list_configs)
|
||||||
$server.active_client.puts(PacketHandler.packet_upload_config(json))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_select_config(packet)
|
||||||
|
config_name = packet.body
|
||||||
|
|
||||||
|
$window.backend.settings.config = config_name
|
||||||
|
$window.backend.save_settings
|
||||||
|
$window.backend.load_config(config_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_add_config(packet)
|
||||||
|
config_name = packet.body
|
||||||
|
|
||||||
|
if $window.backend.configs_list.include?(config_name)
|
||||||
|
unless @host_is_a_connection
|
||||||
|
if $server.active_client&.connected?
|
||||||
|
$server.active_client.puts(PacketHandler.packet_error("Config already exists!", "A config with the name #{config_name} already exists over here."))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
$window.backend.write_new_config(config_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_update_config(packet)
|
||||||
|
old_config_name, new_config_name = packet.body.split(PROTOCOL_SEPERATOR, 2)
|
||||||
|
|
||||||
|
if $window.backend.configs_list.include?(config_name)
|
||||||
|
unless @host_is_a_connection
|
||||||
|
if $server.active_client&.connected?
|
||||||
|
$server.active_client.puts(PacketHandler.packet_error("Config already exists!", "A config with the name #{config_name} already exists over here."))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
$window.backend.move_config(old_config_name, new_config_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_delete_config(packet)
|
||||||
|
config_name = packet.body
|
||||||
|
|
||||||
|
$window.backend.delete_config(config_name)
|
||||||
|
end
|
||||||
|
|
||||||
def self.packet_handshake(client_uuid)
|
def self.packet_handshake(client_uuid)
|
||||||
Packet.create(Packet::PACKET_TYPES[:handshake], client_uuid)
|
Packet.create(Packet::PACKET_TYPES[:handshake], client_uuid)
|
||||||
end
|
end
|
||||||
@@ -95,15 +191,41 @@ module TAC
|
|||||||
Packet.create(Packet::PACKET_TYPES[:heartbeat], Packet::PROTOCOL_HEARTBEAT)
|
Packet.create(Packet::PACKET_TYPES[:heartbeat], Packet::PROTOCOL_HEARTBEAT)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.packet_download_config
|
def self.packet_error(error_code, message)
|
||||||
Packet.create(Packet::PACKET_TYPES[:download_config], "")
|
Packet.create(Packet::PACKET_TYPES[:error], error_code.to_s, message.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.packet_upload_config(string)
|
def self.packet_download_config(config_name)
|
||||||
string = string.gsub("\n", " ")
|
Packet.create(Packet::PACKET_TYPES[:download_config], "#{config_name}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.packet_upload_config(config_name, json)
|
||||||
|
string = "#{config_name}#{Packet::PROTOCOL_SEPERATOR}#{json.gsub("\n", " ")}"
|
||||||
|
|
||||||
Packet.create(Packet::PACKET_TYPES[:upload_config], string)
|
Packet.create(Packet::PACKET_TYPES[:upload_config], string)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.packet_list_configs
|
||||||
|
files = Dir.glob("#{TAC::CONFIGS_PATH}/*.json")
|
||||||
|
list = files.map do |file|
|
||||||
|
name = File.basename(file, ".json")
|
||||||
|
config = Config.new(name)
|
||||||
|
"#{name},#{config.configuration.revision}"
|
||||||
|
end.join(Packet::PROTOCOL_SEPERATOR)
|
||||||
|
|
||||||
|
Packet.create(
|
||||||
|
Packet::PACKET_TYPES[:list_configs],
|
||||||
|
list
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.packet_select_config(config_name)
|
||||||
|
Packet.create(Packet::PACKET_TYPES[:select_config], config_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.packet_delete_config(config_name)
|
||||||
|
Packet.create(Packet::PACKET_TYPES[:delete_config], config_name)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -71,10 +71,9 @@ module TAC
|
|||||||
@active_client = client
|
@active_client = client
|
||||||
# TODO: Backup local config
|
# TODO: Backup local config
|
||||||
# SEND CONFIG
|
# SEND CONFIG
|
||||||
config = File.read(TAC::CONFIGS_PATH)
|
|
||||||
|
|
||||||
@active_client.puts(PacketHandler.packet_handshake(@active_client.uuid))
|
@active_client.puts(PacketHandler.packet_handshake(@active_client.uuid))
|
||||||
@active_client.puts(PacketHandler.packet_upload_config(config))
|
@active_client.puts(PacketHandler.packet_list_configs)
|
||||||
|
|
||||||
log.i(TAG, "Client connected!")
|
log.i(TAG, "Client connected!")
|
||||||
|
|
||||||
|
|||||||
19
lib/theme.rb
19
lib/theme.rb
@@ -1,11 +1,13 @@
|
|||||||
module TAC
|
module TAC
|
||||||
|
THEME_FONT = "#{TAC::ROOT_PATH}/media/fonts/DejaVuSansCondensed.ttf"
|
||||||
THEME = {
|
THEME = {
|
||||||
Label: {
|
Label: {
|
||||||
font: "#{TAC::ROOT_PATH}/media/fonts/DejaVuSansCondensed.ttf",
|
font: THEME_FONT,
|
||||||
text_size: 18,
|
text_size: 22,
|
||||||
color: Gosu::Color.new(0xee_ffffff),
|
color: Gosu::Color.new(0xee_ffffff),
|
||||||
},
|
},
|
||||||
Button: {
|
Button: {
|
||||||
|
text_size: 22,
|
||||||
background: TAC::Palette::TIMECRAFTERS_PRIMARY,
|
background: TAC::Palette::TIMECRAFTERS_PRIMARY,
|
||||||
border_thickness: 1,
|
border_thickness: 1,
|
||||||
border_color: Gosu::Color.new(0xff_111111),
|
border_color: Gosu::Color.new(0xff_111111),
|
||||||
@@ -17,7 +19,7 @@ module TAC
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
EditLine: {
|
EditLine: {
|
||||||
caret_color: Gosu::Color.new(0xff_434343),
|
caret_color: Gosu::Color.new(0xff_88ef90),
|
||||||
},
|
},
|
||||||
ToggleButton: {
|
ToggleButton: {
|
||||||
width: 18,
|
width: 18,
|
||||||
@@ -36,7 +38,8 @@ module TAC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
THEME_ICON_SIZE = 18
|
THEME_DIALOG_BUTTON_PADDING = 24
|
||||||
|
THEME_ICON_SIZE = 22
|
||||||
THEME_HEADING_TEXT_SIZE = 32
|
THEME_HEADING_TEXT_SIZE = 32
|
||||||
THEME_SUBHEADING_TEXT_SIZE = 28
|
THEME_SUBHEADING_TEXT_SIZE = 28
|
||||||
THEME_ITEM_PADDING = 8
|
THEME_ITEM_PADDING = 8
|
||||||
@@ -46,11 +49,15 @@ module TAC
|
|||||||
padding_top: THEME_ITEM_PADDING,
|
padding_top: THEME_ITEM_PADDING,
|
||||||
padding_bottom: THEME_ITEM_PADDING
|
padding_bottom: THEME_ITEM_PADDING
|
||||||
}
|
}
|
||||||
THEME_EVEN_COLOR = Gosu::Color.new(0xff_606060)
|
THEME_EVEN_COLOR = Gosu::Color.new(0xff_202020)
|
||||||
THEME_ODD_COLOR = Gosu::Color.new(0xff_202020)
|
THEME_ODD_COLOR = Gosu::Color.new(0xff_606060)
|
||||||
THEME_CONTENT_BACKGROUND = Gosu::Color.new(0x88_007f3f)
|
THEME_CONTENT_BACKGROUND = Gosu::Color.new(0x88_007f3f)
|
||||||
THEME_HEADER_BACKGROUND = [
|
THEME_HEADER_BACKGROUND = [
|
||||||
TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_PRIMARY,
|
TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_PRIMARY,
|
||||||
TAC::Palette::TIMECRAFTERS_SECONDARY, TAC::Palette::TIMECRAFTERS_SECONDARY,
|
TAC::Palette::TIMECRAFTERS_SECONDARY, TAC::Palette::TIMECRAFTERS_SECONDARY,
|
||||||
]
|
]
|
||||||
|
THEME_NOTIFICATION_EDGE_COLOR = Gosu::Color.new(0xff_008000)
|
||||||
|
THEME_NOTIFICATION_BACKGROUND = Gosu::Color.new(0xff_102010)
|
||||||
|
THEME_NOTIFICATION_TITLE_COLOR = Gosu::Color::WHITE
|
||||||
|
THEME_NOTIFICATION_TAGLINE_COLOR = Gosu::Color::WHITE
|
||||||
end
|
end
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
module TAC
|
module TAC
|
||||||
NAME = "TimeCrafters Configuration Tool"
|
NAME = "TimeCrafters Configuration Tool"
|
||||||
VERSION = "0.1.0"
|
VERSION = "0.4.1"
|
||||||
RELEASE_NAME = "IN-DEV"
|
RELEASE_NAME = "Beta"
|
||||||
end
|
end
|
||||||
@@ -1,23 +1,71 @@
|
|||||||
module TAC
|
module TAC
|
||||||
class Window < CyberarmEngine::Window
|
class Window < CyberarmEngine::Window
|
||||||
attr_reader :backend
|
attr_reader :backend, :notification_manager
|
||||||
def initialize(**args)
|
def initialize(**args)
|
||||||
super(**args)
|
super(**args)
|
||||||
|
|
||||||
self.caption = "#{TAC::NAME} v#{TAC::VERSION} (#{TAC::RELEASE_NAME})"
|
self.caption = "#{TAC::NAME} v#{TAC::VERSION} (#{TAC::RELEASE_NAME})"
|
||||||
@backend = Backend.new
|
@backend = Backend.new
|
||||||
|
@notification_manager = GosuNotifications::NotificationManager.new(window: self, edge: :bottom)
|
||||||
|
|
||||||
push_state(TAC::States::Boot)
|
push_state(TAC::States::Boot)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def draw
|
||||||
|
super
|
||||||
|
|
||||||
|
Gosu.flush
|
||||||
|
@notification_manager.draw
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
super
|
||||||
|
|
||||||
|
@notification_manager.update
|
||||||
|
end
|
||||||
|
|
||||||
def needs_cursor?
|
def needs_cursor?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def toast(title, message = nil)
|
||||||
|
@notification_manager.create_notification(
|
||||||
|
priority: GosuNotifications::Notification::PRIORITY_HIGH,
|
||||||
|
title: title,
|
||||||
|
|
||||||
|
tagline: message ? message : "",
|
||||||
|
edge_color: THEME_NOTIFICATION_EDGE_COLOR,
|
||||||
|
background_color: THEME_NOTIFICATION_BACKGROUND,
|
||||||
|
title_color: THEME_NOTIFICATION_TITLE_COLOR,
|
||||||
|
tagline_color: THEME_NOTIFICATION_TAGLINE_COLOR
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def hit_test(x, y)
|
||||||
|
return 0 unless BORDERLESS
|
||||||
|
|
||||||
|
if y <= 4
|
||||||
|
return 2 if x <= 4
|
||||||
|
return 4 if x >= width - 4
|
||||||
|
return 3
|
||||||
|
end
|
||||||
|
|
||||||
|
if y >= height - 4
|
||||||
|
return 8 if x <= 4
|
||||||
|
return 6 if x >= width - 4
|
||||||
|
return 7
|
||||||
|
end
|
||||||
|
|
||||||
|
return 1 if y <= 36 && x <= width - (36 * 3 + 4 * 6)
|
||||||
|
|
||||||
|
0
|
||||||
|
end
|
||||||
|
|
||||||
def close
|
def close
|
||||||
if @backend.config_changed?
|
if @backend.config_changed?
|
||||||
push_state(Dialog::ConfirmDialog, title: "Unsaved Config", message: "Config has unsaved changes\nthat will be lost if\nyou continue!", callback_method: proc { cleanup_and_close })
|
push_state(Dialog::ConfirmDialog, title: "Unsaved Config", message: "Config has unsaved changes that will be lost if you continue!", callback_method: proc { cleanup_and_close })
|
||||||
elsif @backend.settings_changed?
|
elsif @backend.settings_changed?
|
||||||
push_state(Dialog::ConfirmDialog, title: "Unsaved Settings", message: "Settings has unsaved changes\nthat will be lost if\nyou continue!", callback_method: proc { cleanup_and_close })
|
push_state(Dialog::ConfirmDialog, title: "Unsaved Settings", message: "Settings has unsaved changes that will be lost if you continue!", callback_method: proc { cleanup_and_close })
|
||||||
else
|
else
|
||||||
cleanup_and_close
|
cleanup_and_close
|
||||||
end
|
end
|
||||||
|
|||||||
BIN
media/error_alarm.ogg
Normal file
BIN
media/error_alarm.ogg
Normal file
Binary file not shown.
BIN
media/icon.ico
Normal file
BIN
media/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
@@ -4,6 +4,8 @@ require "securerandom"
|
|||||||
|
|
||||||
require_relative "lib/tac"
|
require_relative "lib/tac"
|
||||||
require_relative "lib/logger"
|
require_relative "lib/logger"
|
||||||
|
require_relative "lib/settings"
|
||||||
|
require_relative "lib/config"
|
||||||
|
|
||||||
require_relative "lib/tacnet"
|
require_relative "lib/tacnet"
|
||||||
require_relative "lib/tacnet/packet"
|
require_relative "lib/tacnet/packet"
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
require_relative "../cyberarm_engine/lib/cyberarm_engine"
|
begin
|
||||||
|
raise LoadError if defined?(Ocra)
|
||||||
|
require_relative "../cyberarm_engine/lib/cyberarm_engine"
|
||||||
|
rescue LoadError
|
||||||
|
require "cyberarm_engine"
|
||||||
|
end
|
||||||
|
require "gosu_notifications"
|
||||||
require "socket"
|
require "socket"
|
||||||
require "securerandom"
|
require "securerandom"
|
||||||
require "json"
|
require "json"
|
||||||
@@ -13,9 +19,16 @@ require_relative "lib/config"
|
|||||||
require_relative "lib/settings"
|
require_relative "lib/settings"
|
||||||
require_relative "lib/states/boot"
|
require_relative "lib/states/boot"
|
||||||
require_relative "lib/states/editor"
|
require_relative "lib/states/editor"
|
||||||
require_relative "lib/states/simulator"
|
require_relative "lib/page"
|
||||||
require_relative "lib/states/manage_presets"
|
require_relative "lib/pages/home"
|
||||||
require_relative "lib/states/manage_configurations"
|
require_relative "lib/pages/editor"
|
||||||
|
require_relative "lib/pages/tacnet"
|
||||||
|
require_relative "lib/pages/simulator"
|
||||||
|
require_relative "lib/pages/configurations"
|
||||||
|
require_relative "lib/pages/presets"
|
||||||
|
require_relative "lib/pages/search"
|
||||||
|
require_relative "lib/pages/field_planner"
|
||||||
|
require_relative "lib/pages/drive_team_rotation_generator"
|
||||||
require_relative "lib/simulator/robot"
|
require_relative "lib/simulator/robot"
|
||||||
require_relative "lib/simulator/field"
|
require_relative "lib/simulator/field"
|
||||||
require_relative "lib/simulator/simulation"
|
require_relative "lib/simulator/simulation"
|
||||||
@@ -25,7 +38,11 @@ require_relative "lib/dialog"
|
|||||||
require_relative "lib/dialogs/alert_dialog"
|
require_relative "lib/dialogs/alert_dialog"
|
||||||
require_relative "lib/dialogs/confirm_dialog"
|
require_relative "lib/dialogs/confirm_dialog"
|
||||||
require_relative "lib/dialogs/name_prompt_dialog"
|
require_relative "lib/dialogs/name_prompt_dialog"
|
||||||
|
require_relative "lib/dialogs/action_dialog"
|
||||||
require_relative "lib/dialogs/variable_dialog"
|
require_relative "lib/dialogs/variable_dialog"
|
||||||
|
require_relative "lib/dialogs/tacnet_dialog"
|
||||||
|
require_relative "lib/dialogs/tacnet_status_dialog"
|
||||||
|
require_relative "lib/dialogs/pick_preset_dialog"
|
||||||
require_relative "lib/tacnet"
|
require_relative "lib/tacnet"
|
||||||
require_relative "lib/tacnet/packet"
|
require_relative "lib/tacnet/packet"
|
||||||
require_relative "lib/tacnet/packet_handler"
|
require_relative "lib/tacnet/packet_handler"
|
||||||
@@ -35,4 +52,9 @@ require_relative "lib/tacnet/server"
|
|||||||
|
|
||||||
# Thread.abort_on_exception = true
|
# Thread.abort_on_exception = true
|
||||||
|
|
||||||
TAC::Window.new(width: (Gosu.screen_width * 0.8).round, height: (Gosu.screen_height * 0.8).round, resizable: true).show
|
USE_REDESIGN = ARGV.include?("--redesign")
|
||||||
|
BORDERLESS = ARGV.include?("--borderless")
|
||||||
|
|
||||||
|
if not defined?(Ocra)
|
||||||
|
TAC::Window.new(width: (Gosu.screen_width * 0.8).round, height: (Gosu.screen_height * 0.8).round, resizable: true, borderless: BORDERLESS).show
|
||||||
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user