summaryrefslogtreecommitdiff
path: root/addons/gut/gui
diff options
context:
space:
mode:
authorSophia Pearson <codergal89@gmail.com>2022-05-20 00:45:25 +0200
committerSophia Pearson <codergal89@gmail.com>2022-05-20 18:56:04 +0200
commit05d29ccce1898ed89c0b650c77242c2fa2805128 (patch)
treee8ee3bcb570fa6f3d9d96273c2bf4d4c8618d08b /addons/gut/gui
downloadtexty-05d29ccce1898ed89c0b650c77242c2fa2805128.tar.xz
texty-05d29ccce1898ed89c0b650c77242c2fa2805128.zip
texty: initial commit
Diffstat (limited to 'addons/gut/gui')
-rw-r--r--addons/gut/gui/BottomPanelShortcuts.gd82
-rw-r--r--addons/gut/gui/BottomPanelShortcuts.tscn232
-rw-r--r--addons/gut/gui/GutBottomPanel.gd326
-rw-r--r--addons/gut/gui/GutBottomPanel.tscn386
-rw-r--r--addons/gut/gui/GutRunner.gd98
-rw-r--r--addons/gut/gui/GutRunner.tscn9
-rw-r--r--addons/gut/gui/GutSceneTheme.tres11
-rw-r--r--addons/gut/gui/RunAtCursor.gd130
-rw-r--r--addons/gut/gui/RunAtCursor.tscn80
-rw-r--r--addons/gut/gui/Settings.tscn7
-rw-r--r--addons/gut/gui/ShortcutButton.gd164
-rw-r--r--addons/gut/gui/ShortcutButton.tscn80
-rw-r--r--addons/gut/gui/arrow.png3
-rw-r--r--addons/gut/gui/arrow.png.import35
-rw-r--r--addons/gut/gui/gut_config_gui.gd408
-rw-r--r--addons/gut/gui/play.png3
-rw-r--r--addons/gut/gui/play.png.import35
-rw-r--r--addons/gut/gui/script_text_editor_controls.gd230
18 files changed, 2319 insertions, 0 deletions
diff --git a/addons/gut/gui/BottomPanelShortcuts.gd b/addons/gut/gui/BottomPanelShortcuts.gd
new file mode 100644
index 0000000..86fbf8d
--- /dev/null
+++ b/addons/gut/gui/BottomPanelShortcuts.gd
@@ -0,0 +1,82 @@
+tool
+extends WindowDialog
+
+onready var _ctrls = {
+ run_all = $Layout/CRunAll/ShortcutButton,
+ run_current_script = $Layout/CRunCurrentScript/ShortcutButton,
+ run_current_inner = $Layout/CRunCurrentInner/ShortcutButton,
+ run_current_test = $Layout/CRunCurrentTest/ShortcutButton,
+ panel_button = $Layout/CPanelButton/ShortcutButton,
+}
+
+func _ready():
+ for key in _ctrls:
+ var sc_button = _ctrls[key]
+ sc_button.connect('start_edit', self, '_on_edit_start', [sc_button])
+ sc_button.connect('end_edit', self, '_on_edit_end')
+
+
+ # show dialog when running scene from editor.
+ if(get_parent() == get_tree().root):
+ popup_centered()
+
+# ------------
+# Events
+# ------------
+func _on_Hide_pressed():
+ hide()
+
+func _on_edit_start(which):
+ for key in _ctrls:
+ var sc_button = _ctrls[key]
+ if(sc_button != which):
+ sc_button.disable_set(true)
+ sc_button.disable_clear(true)
+
+func _on_edit_end():
+ for key in _ctrls:
+ var sc_button = _ctrls[key]
+ sc_button.disable_set(false)
+ sc_button.disable_clear(false)
+
+# ------------
+# Public
+# ------------
+func get_run_all():
+ return _ctrls.run_all.get_shortcut()
+
+func get_run_current_script():
+ return _ctrls.run_current_script.get_shortcut()
+
+func get_run_current_inner():
+ return _ctrls.run_current_inner.get_shortcut()
+
+func get_run_current_test():
+ return _ctrls.run_current_test.get_shortcut()
+
+func get_panel_button():
+ return _ctrls.panel_button.get_shortcut()
+
+
+func save_shortcuts(path):
+ var f = ConfigFile.new()
+
+ f.set_value('main', 'run_all', _ctrls.run_all.get_shortcut())
+ f.set_value('main', 'run_current_script', _ctrls.run_current_script.get_shortcut())
+ f.set_value('main', 'run_current_inner', _ctrls.run_current_inner.get_shortcut())
+ f.set_value('main', 'run_current_test', _ctrls.run_current_test.get_shortcut())
+ f.set_value('main', 'panel_button', _ctrls.panel_button.get_shortcut())
+
+ f.save(path)
+
+
+func load_shortcuts(path):
+ var emptyShortcut = ShortCut.new()
+ var f = ConfigFile.new()
+ f.load(path)
+
+ _ctrls.run_all.set_shortcut(f.get_value('main', 'run_all', emptyShortcut))
+ _ctrls.run_current_script.set_shortcut(f.get_value('main', 'run_current_script', emptyShortcut))
+ _ctrls.run_current_inner.set_shortcut(f.get_value('main', 'run_current_inner', emptyShortcut))
+ _ctrls.run_current_test.set_shortcut(f.get_value('main', 'run_current_test', emptyShortcut))
+ _ctrls.panel_button.set_shortcut(f.get_value('main', 'panel_button', emptyShortcut))
diff --git a/addons/gut/gui/BottomPanelShortcuts.tscn b/addons/gut/gui/BottomPanelShortcuts.tscn
new file mode 100644
index 0000000..5e2e5d9
--- /dev/null
+++ b/addons/gut/gui/BottomPanelShortcuts.tscn
@@ -0,0 +1,232 @@
+[gd_scene load_steps=3 format=2]
+
+[ext_resource path="res://addons/gut/gui/ShortcutButton.tscn" type="PackedScene" id=1]
+[ext_resource path="res://addons/gut/gui/BottomPanelShortcuts.gd" type="Script" id=2]
+
+[node name="BottomPanelShortcuts" type="WindowDialog"]
+visible = true
+anchor_right = 0.234
+anchor_bottom = 0.328
+margin_right = 195.384
+margin_bottom = 62.2
+rect_min_size = Vector2( 435, 305 )
+popup_exclusive = true
+window_title = "GUT Shortcuts"
+resizable = true
+script = ExtResource( 2 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Layout" type="VBoxContainer" parent="."]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_left = 5.0
+margin_right = -5.0
+margin_bottom = 2.0
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="TopPad" type="CenterContainer" parent="Layout"]
+margin_right = 425.0
+margin_bottom = 5.0
+rect_min_size = Vector2( 0, 5 )
+
+[node name="Label2" type="Label" parent="Layout"]
+margin_top = 9.0
+margin_right = 425.0
+margin_bottom = 29.0
+rect_min_size = Vector2( 0, 20 )
+text = "Always Active"
+align = 1
+valign = 1
+autowrap = true
+
+[node name="ColorRect" type="ColorRect" parent="Layout/Label2"]
+show_behind_parent = true
+anchor_right = 1.0
+anchor_bottom = 1.0
+color = Color( 0, 0, 0, 0.196078 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="CPanelButton" type="HBoxContainer" parent="Layout"]
+margin_top = 33.0
+margin_right = 425.0
+margin_bottom = 58.0
+
+[node name="Label" type="Label" parent="Layout/CPanelButton"]
+margin_right = 138.0
+margin_bottom = 25.0
+rect_min_size = Vector2( 50, 0 )
+size_flags_vertical = 7
+text = "Show/Hide GUT Panel"
+valign = 1
+
+[node name="ShortcutButton" parent="Layout/CPanelButton" instance=ExtResource( 1 )]
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_left = 142.0
+margin_right = 425.0
+margin_bottom = 25.0
+size_flags_horizontal = 3
+
+[node name="GutPanelPad" type="CenterContainer" parent="Layout"]
+margin_top = 62.0
+margin_right = 425.0
+margin_bottom = 67.0
+rect_min_size = Vector2( 0, 5 )
+
+[node name="Label" type="Label" parent="Layout"]
+margin_top = 71.0
+margin_right = 425.0
+margin_bottom = 91.0
+rect_min_size = Vector2( 0, 20 )
+text = "Only Active When GUT Panel Shown"
+align = 1
+valign = 1
+autowrap = true
+
+[node name="ColorRect2" type="ColorRect" parent="Layout/Label"]
+show_behind_parent = true
+anchor_right = 1.0
+anchor_bottom = 1.0
+color = Color( 0, 0, 0, 0.196078 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="TopPad2" type="CenterContainer" parent="Layout"]
+margin_top = 95.0
+margin_right = 425.0
+margin_bottom = 100.0
+rect_min_size = Vector2( 0, 5 )
+
+[node name="CRunAll" type="HBoxContainer" parent="Layout"]
+margin_top = 104.0
+margin_right = 425.0
+margin_bottom = 129.0
+
+[node name="Label" type="Label" parent="Layout/CRunAll"]
+margin_right = 50.0
+margin_bottom = 25.0
+rect_min_size = Vector2( 50, 0 )
+size_flags_vertical = 7
+text = "Run All"
+valign = 1
+
+[node name="ShortcutButton" parent="Layout/CRunAll" instance=ExtResource( 1 )]
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_left = 54.0
+margin_right = 425.0
+margin_bottom = 25.0
+size_flags_horizontal = 3
+
+[node name="CRunCurrentScript" type="HBoxContainer" parent="Layout"]
+margin_top = 133.0
+margin_right = 425.0
+margin_bottom = 158.0
+
+[node name="Label" type="Label" parent="Layout/CRunCurrentScript"]
+margin_right = 115.0
+margin_bottom = 25.0
+rect_min_size = Vector2( 50, 0 )
+size_flags_vertical = 7
+text = "Run Current Script"
+valign = 1
+
+[node name="ShortcutButton" parent="Layout/CRunCurrentScript" instance=ExtResource( 1 )]
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_left = 119.0
+margin_right = 425.0
+margin_bottom = 25.0
+size_flags_horizontal = 3
+
+[node name="CRunCurrentInner" type="HBoxContainer" parent="Layout"]
+margin_top = 162.0
+margin_right = 425.0
+margin_bottom = 187.0
+
+[node name="Label" type="Label" parent="Layout/CRunCurrentInner"]
+margin_right = 150.0
+margin_bottom = 25.0
+rect_min_size = Vector2( 50, 0 )
+size_flags_vertical = 7
+text = "Run Current Inner Class"
+valign = 1
+
+[node name="ShortcutButton" parent="Layout/CRunCurrentInner" instance=ExtResource( 1 )]
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_left = 154.0
+margin_right = 425.0
+margin_bottom = 25.0
+size_flags_horizontal = 3
+
+[node name="CRunCurrentTest" type="HBoxContainer" parent="Layout"]
+margin_top = 191.0
+margin_right = 425.0
+margin_bottom = 216.0
+
+[node name="Label" type="Label" parent="Layout/CRunCurrentTest"]
+margin_right = 106.0
+margin_bottom = 25.0
+rect_min_size = Vector2( 50, 0 )
+size_flags_vertical = 7
+text = "Run Current Test"
+valign = 1
+
+[node name="ShortcutButton" parent="Layout/CRunCurrentTest" instance=ExtResource( 1 )]
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_left = 110.0
+margin_right = 425.0
+margin_bottom = 25.0
+size_flags_horizontal = 3
+
+[node name="CenterContainer2" type="CenterContainer" parent="Layout"]
+margin_top = 220.0
+margin_right = 425.0
+margin_bottom = 241.0
+rect_min_size = Vector2( 0, 5 )
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="ShiftDisclaimer" type="Label" parent="Layout"]
+margin_top = 245.0
+margin_right = 425.0
+margin_bottom = 259.0
+text = "\"Shift\" cannot be the only modifier for a shortcut."
+align = 2
+autowrap = true
+
+[node name="HBoxContainer" type="HBoxContainer" parent="Layout"]
+margin_top = 263.0
+margin_right = 425.0
+margin_bottom = 293.0
+
+[node name="CenterContainer" type="CenterContainer" parent="Layout/HBoxContainer"]
+margin_right = 361.0
+margin_bottom = 30.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="Hide" type="Button" parent="Layout/HBoxContainer"]
+margin_left = 365.0
+margin_right = 425.0
+margin_bottom = 30.0
+rect_min_size = Vector2( 60, 30 )
+text = "Close"
+
+[node name="BottomPad" type="CenterContainer" parent="Layout"]
+margin_top = 297.0
+margin_right = 425.0
+margin_bottom = 307.0
+rect_min_size = Vector2( 0, 10 )
+size_flags_horizontal = 3
+
+[connection signal="pressed" from="Layout/HBoxContainer/Hide" to="." method="_on_Hide_pressed"]
diff --git a/addons/gut/gui/GutBottomPanel.gd b/addons/gut/gui/GutBottomPanel.gd
new file mode 100644
index 0000000..50a32d8
--- /dev/null
+++ b/addons/gut/gui/GutBottomPanel.gd
@@ -0,0 +1,326 @@
+tool
+extends Control
+
+const RUNNER_JSON_PATH = 'res://.gut_editor_config.json'
+const RESULT_FILE = 'user://.gut_editor.bbcode'
+const RESULT_JSON = 'user://.gut_editor.json'
+const SHORTCUTS_PATH = 'res://.gut_editor_shortcuts.cfg'
+
+var TestScript = load('res://addons/gut/test.gd')
+var GutConfigGui = load('res://addons/gut/gui/gut_config_gui.gd')
+
+var _interface = null;
+var _is_running = false;
+var _gut_config = load('res://addons/gut/gut_config.gd').new()
+var _gut_config_gui = null
+var _gut_plugin = null
+var _light_color = Color(0, 0, 0, .5)
+var _panel_button = null
+var _last_selected_path = null
+
+
+onready var _ctrls = {
+ output = $layout/RSplit/CResults/Output,
+ run_button = $layout/ControlBar/RunAll,
+ settings = $layout/RSplit/sc/Settings,
+ shortcut_dialog = $BottomPanelShortcuts,
+ light = $layout/RSplit/CResults/ControlBar/Light,
+ results = {
+ passing = $layout/RSplit/CResults/ControlBar/Passing/value,
+ failing = $layout/RSplit/CResults/ControlBar/Failing/value,
+ pending = $layout/RSplit/CResults/ControlBar/Pending/value,
+ errors = $layout/RSplit/CResults/ControlBar/Errors/value,
+ warnings = $layout/RSplit/CResults/ControlBar/Warnings/value,
+ orphans = $layout/RSplit/CResults/ControlBar/Orphans/value
+ },
+ run_at_cursor = $layout/ControlBar/RunAtCursor
+}
+
+
+func _init():
+ _gut_config.load_panel_options(RUNNER_JSON_PATH)
+
+
+func _ready():
+ _gut_config_gui = GutConfigGui.new(_ctrls.settings)
+ _gut_config_gui.set_options(_gut_config.options)
+ _set_all_fonts_in_ftl(_ctrls.output, _gut_config.options.panel_options.font_name)
+ _set_font_size_for_rtl(_ctrls.output, _gut_config.options.panel_options.font_size)
+
+
+func _process(delta):
+ if(_is_running):
+ if(!_interface.is_playing_scene()):
+ _is_running = false
+ _ctrls.output.add_text("\ndone")
+ load_result_output()
+ _gut_plugin.make_bottom_panel_item_visible(self)
+
+# ---------------
+# Private
+# ---------------
+
+func load_shortcuts():
+ _ctrls.shortcut_dialog.load_shortcuts(SHORTCUTS_PATH)
+ _apply_shortcuts()
+
+
+# -----------------------------------
+func _set_font(rtl, font_name, custom_name):
+ if(font_name == null):
+ rtl.set('custom_fonts/' + custom_name, null)
+ else:
+ var dyn_font = DynamicFont.new()
+ var font_data = DynamicFontData.new()
+ font_data.font_path = 'res://addons/gut/fonts/' + font_name + '.ttf'
+ font_data.antialiased = true
+ dyn_font.font_data = font_data
+ rtl.set('custom_fonts/' + custom_name, dyn_font)
+
+
+func _set_all_fonts_in_ftl(ftl, base_name):
+ if(base_name == 'Default'):
+ _set_font(ftl, null, 'normal_font')
+ _set_font(ftl, null, 'bold_font')
+ _set_font(ftl, null, 'italics_font')
+ _set_font(ftl, null, 'bold_italics_font')
+ else:
+ _set_font(ftl, base_name + '-Regular', 'normal_font')
+ _set_font(ftl, base_name + '-Bold', 'bold_font')
+ _set_font(ftl, base_name + '-Italic', 'italics_font')
+ _set_font(ftl, base_name + '-BoldItalic', 'bold_italics_font')
+
+
+func _set_font_size_for_rtl(rtl, new_size):
+ if(rtl.get('custom_fonts/normal_font') != null):
+ rtl.get('custom_fonts/bold_italics_font').size = new_size
+ rtl.get('custom_fonts/bold_font').size = new_size
+ rtl.get('custom_fonts/italics_font').size = new_size
+ rtl.get('custom_fonts/normal_font').size = new_size
+# -----------------------------------
+
+
+func _is_test_script(script):
+ var from = script.get_base_script()
+ while(from and from.resource_path != 'res://addons/gut/test.gd'):
+ from = from.get_base_script()
+
+ return from != null
+
+
+func _update_last_run_label():
+ var text = ''
+
+ if( _gut_config.options.selected == null and
+ _gut_config.options.inner_class == null and
+ _gut_config.options.unit_test_name == null):
+ text = 'All'
+ else:
+ text = nvl(_gut_config.options.selected, '') + ' '
+ text += nvl(_gut_config.options.inner_class, '') + ' '
+ text += nvl(_gut_config.options.unit_test_name, '')
+
+
+
+func _show_errors(errs):
+ _ctrls.output.clear()
+ var text = "Cannot run tests, you have a conrfiguration error:\n"
+ for e in errs:
+ text += str('* ', e, "\n")
+ text += "[right]Check your settings here ----->[/right]"
+ _ctrls.output.bbcode_text = text
+
+
+func _run_tests():
+ var issues = _gut_config_gui.get_config_issues()
+ if(issues.size() > 0):
+ _show_errors(issues)
+ return
+
+ write_file(RESULT_FILE, 'Run in progress')
+ _gut_config.options = _gut_config_gui.get_options(_gut_config.options)
+ _set_all_fonts_in_ftl(_ctrls.output, _gut_config.options.panel_options.font_name)
+ _set_font_size_for_rtl(_ctrls.output, _gut_config.options.panel_options.font_size)
+
+ var w_result = _gut_config.write_options(RUNNER_JSON_PATH)
+ if(w_result != OK):
+ push_error(str('Could not write options to ', RUNNER_JSON_PATH, ': ', w_result))
+ return;
+
+ _ctrls.output.clear()
+
+ _update_last_run_label()
+ _interface.play_custom_scene('res://addons/gut/gui/GutRunner.tscn')
+
+ _is_running = true
+ _ctrls.output.add_text('running...')
+
+
+func _apply_shortcuts():
+ _ctrls.run_button.shortcut = _ctrls.shortcut_dialog.get_run_all()
+
+ _ctrls.run_at_cursor.get_script_button().shortcut = \
+ _ctrls.shortcut_dialog.get_run_current_script()
+ _ctrls.run_at_cursor.get_inner_button().shortcut = \
+ _ctrls.shortcut_dialog.get_run_current_inner()
+ _ctrls.run_at_cursor.get_test_button().shortcut = \
+ _ctrls.shortcut_dialog.get_run_current_test()
+
+ _panel_button.shortcut = _ctrls.shortcut_dialog.get_panel_button()
+
+
+func _run_all():
+ _gut_config.options.selected = null
+ _gut_config.options.inner_class = null
+ _gut_config.options.unit_test_name = null
+
+ _run_tests()
+
+
+# ---------------
+# Events
+# ---------------
+func _on_editor_script_changed(script):
+ if(script):
+ set_current_script(script)
+
+
+func _on_RunAll_pressed():
+ _on_RunTests_pressed()
+
+
+func _on_RunTests_pressed():
+ _run_all()
+
+
+func _on_CopyButton_pressed():
+ OS.clipboard = _ctrls.output.text
+
+
+func _on_ClearButton_pressed():
+ _ctrls.output.clear()
+
+
+func _on_Shortcuts_pressed():
+ _ctrls.shortcut_dialog.popup_centered()
+
+
+func _on_BottomPanelShortcuts_popup_hide():
+ _apply_shortcuts()
+ _ctrls.shortcut_dialog.save_shortcuts(SHORTCUTS_PATH)
+
+
+func _on_Light_draw():
+ var l = _ctrls.light
+ l.draw_circle(Vector2(l.rect_size.x / 2, l.rect_size.y / 2), l.rect_size.x / 2, _light_color)
+
+
+func _on_RunAtCursor_run_tests(what):
+ _gut_config.options.selected = what.script
+ _gut_config.options.inner_class = what.inner_class
+ _gut_config.options.unit_test_name = what.test_method
+
+ _run_tests()
+
+
+# ---------------
+# Public
+# ---------------
+
+func load_result_output():
+ _ctrls.output.bbcode_text = get_file_as_text(RESULT_FILE)
+ _ctrls.output.grab_focus()
+ _ctrls.output.scroll_to_line(_ctrls.output.get_line_count() -1)
+
+ var summary = get_file_as_text(RESULT_JSON)
+ var results = JSON.parse(summary)
+ if(results.error != OK):
+ return
+ var summary_json = results.result['test_scripts']['props']
+ _ctrls.results.passing.text = str(summary_json.passing)
+ _ctrls.results.passing.get_parent().visible = true
+
+ _ctrls.results.failing.text = str(summary_json.failures)
+ _ctrls.results.failing.get_parent().visible = true
+
+ _ctrls.results.pending.text = str(summary_json.pending)
+ _ctrls.results.pending.get_parent().visible = _ctrls.results.pending.text != '0'
+
+ _ctrls.results.errors.text = str(summary_json.errors)
+ _ctrls.results.errors.get_parent().visible = _ctrls.results.errors.text != '0'
+
+ _ctrls.results.warnings.text = str(summary_json.warnings)
+ _ctrls.results.warnings.get_parent().visible = _ctrls.results.warnings.text != '0'
+
+ _ctrls.results.orphans.text = str(summary_json.orphans)
+ _ctrls.results.orphans.get_parent().visible = _ctrls.results.orphans.text != '0' and !_gut_config.options.hide_orphans
+
+ if(summary_json.tests == 0):
+ _light_color = Color(1, 0, 0, .75)
+ elif(summary_json.failures != 0):
+ _light_color = Color(1, 0, 0, .75)
+ elif(summary_json.pending != 0):
+ _light_color = Color(1, 1, 0, .75)
+ else:
+ _light_color = Color(0, 1, 0, .75)
+ _ctrls.light.update()
+
+
+
+func set_current_script(script):
+ if(script):
+ if(_is_test_script(script)):
+ var file = script.resource_path.get_file()
+ _last_selected_path = script.resource_path.get_file()
+ _ctrls.run_at_cursor.activate_for_script(script.resource_path)
+
+
+func set_interface(value):
+ _interface = value
+ _interface.get_script_editor().connect("editor_script_changed", self, '_on_editor_script_changed')
+ _ctrls.run_at_cursor.set_script_editor(_interface.get_script_editor())
+ set_current_script(_interface.get_script_editor().get_current_script())
+
+
+func set_plugin(value):
+ _gut_plugin = value
+
+
+func set_panel_button(value):
+ _panel_button = value
+
+# ------------------------------------------------------------------------------
+# Write a file.
+# ------------------------------------------------------------------------------
+func write_file(path, content):
+ var f = File.new()
+ var result = f.open(path, f.WRITE)
+ if(result == OK):
+ f.store_string(content)
+ f.close()
+ return result
+
+
+# ------------------------------------------------------------------------------
+# Returns the text of a file or an empty string if the file could not be opened.
+# ------------------------------------------------------------------------------
+func get_file_as_text(path):
+ var to_return = ''
+ var f = File.new()
+ var result = f.open(path, f.READ)
+ if(result == OK):
+ to_return = f.get_as_text()
+ f.close()
+ return to_return
+
+
+# ------------------------------------------------------------------------------
+# return if_null if value is null otherwise return value
+# ------------------------------------------------------------------------------
+func nvl(value, if_null):
+ if(value == null):
+ return if_null
+ else:
+ return value
+
+
diff --git a/addons/gut/gui/GutBottomPanel.tscn b/addons/gut/gui/GutBottomPanel.tscn
new file mode 100644
index 0000000..0bb7fc3
--- /dev/null
+++ b/addons/gut/gui/GutBottomPanel.tscn
@@ -0,0 +1,386 @@
+[gd_scene load_steps=16 format=2]
+
+[ext_resource path="res://addons/gut/gui/GutBottomPanel.gd" type="Script" id=1]
+[ext_resource path="res://addons/gut/gui/BottomPanelShortcuts.tscn" type="PackedScene" id=2]
+[ext_resource path="res://addons/gut/gui/RunAtCursor.tscn" type="PackedScene" id=3]
+[ext_resource path="res://addons/gut/gui/play.png" type="Texture" id=4]
+
+[sub_resource type="InputEventKey" id=8]
+control = true
+scancode = 49
+
+[sub_resource type="ShortCut" id=9]
+shortcut = SubResource( 8 )
+
+[sub_resource type="StyleBoxEmpty" id=5]
+
+[sub_resource type="DynamicFontData" id=10]
+font_path = "res://addons/gut/fonts/CourierPrime-BoldItalic.ttf"
+
+[sub_resource type="DynamicFont" id=11]
+size = 30
+font_data = SubResource( 10 )
+
+[sub_resource type="DynamicFontData" id=12]
+font_path = "res://addons/gut/fonts/CourierPrime-Italic.ttf"
+
+[sub_resource type="DynamicFont" id=13]
+size = 30
+font_data = SubResource( 12 )
+
+[sub_resource type="DynamicFontData" id=14]
+font_path = "res://addons/gut/fonts/CourierPrime-Bold.ttf"
+
+[sub_resource type="DynamicFont" id=15]
+size = 30
+font_data = SubResource( 14 )
+
+[sub_resource type="DynamicFontData" id=16]
+font_path = "res://addons/gut/fonts/CourierPrime-Regular.ttf"
+
+[sub_resource type="DynamicFont" id=17]
+size = 30
+font_data = SubResource( 16 )
+
+[node name="GutBottomPanel" type="Control"]
+anchor_left = -0.0025866
+anchor_top = -0.00176575
+anchor_right = 0.997413
+anchor_bottom = 0.998234
+margin_left = 2.64868
+margin_top = 1.05945
+margin_right = 2.64862
+margin_bottom = 1.05945
+rect_min_size = Vector2( 0, 300 )
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="layout" type="VBoxContainer" parent="."]
+anchor_right = 1.0
+anchor_bottom = 1.0
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="ControlBar" type="HBoxContainer" parent="layout"]
+margin_right = 1023.0
+margin_bottom = 40.0
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="RunAll" type="Button" parent="layout/ControlBar"]
+margin_right = 150.0
+margin_bottom = 40.0
+rect_min_size = Vector2( 150, 0 )
+hint_tooltip = "Run all test scripts in the suite."
+size_flags_vertical = 11
+shortcut = SubResource( 9 )
+text = "Run All"
+icon = ExtResource( 4 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Label" type="Label" parent="layout/ControlBar"]
+margin_left = 154.0
+margin_top = 13.0
+margin_right = 213.0
+margin_bottom = 27.0
+hint_tooltip = "When a test script is edited, buttons are displayed to
+run the opened script or an Inner-Test-Class or a
+single test. The buttons change based on the location
+of the cursor in the file.
+
+These buttons will remain active when editing other
+items so that you can run tests without having to switch
+back to the test script.
+
+You can assign keyboard shortcuts for these buttons
+using the \"shortcuts\" button in the GUT panel."
+mouse_filter = 1
+text = "Current: "
+
+[node name="RunAtCursor" parent="layout/ControlBar" instance=ExtResource( 3 )]
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_left = 217.0
+margin_right = 456.0
+margin_bottom = 40.0
+rect_min_size = Vector2( 0, 40 )
+
+[node name="CenterContainer2" type="CenterContainer" parent="layout/ControlBar"]
+margin_left = 460.0
+margin_right = 699.0
+margin_bottom = 40.0
+size_flags_horizontal = 3
+
+[node name="FocusButton" type="Button" parent="layout/ControlBar"]
+show_behind_parent = true
+margin_left = 703.0
+margin_right = 703.0
+margin_bottom = 40.0
+custom_styles/normal = SubResource( 5 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="CenterContainer" type="CenterContainer" parent="layout/ControlBar"]
+margin_left = 707.0
+margin_right = 946.0
+margin_bottom = 40.0
+size_flags_horizontal = 3
+
+[node name="Shortcuts" type="Button" parent="layout/ControlBar"]
+margin_left = 950.0
+margin_right = 1022.0
+margin_bottom = 40.0
+hint_tooltip = "Set shortcuts for GUT buttons. Shortcuts do not work when the GUT panel is not visible."
+size_flags_vertical = 11
+text = "Shortcuts"
+
+[node name="RSplit" type="HSplitContainer" parent="layout"]
+margin_top = 44.0
+margin_right = 1023.0
+margin_bottom = 599.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="CResults" type="VBoxContainer" parent="layout/RSplit"]
+margin_right = 611.0
+margin_bottom = 555.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="ControlBar" type="HBoxContainer" parent="layout/RSplit/CResults"]
+margin_right = 611.0
+margin_bottom = 35.0
+rect_min_size = Vector2( 0, 35 )
+
+[node name="Light" type="Control" parent="layout/RSplit/CResults/ControlBar"]
+margin_right = 30.0
+margin_bottom = 35.0
+rect_min_size = Vector2( 30, 30 )
+
+[node name="Passing" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
+visible = false
+margin_left = 34.0
+margin_right = 107.0
+margin_bottom = 35.0
+
+[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Passing"]
+margin_right = 2.0
+margin_bottom = 35.0
+rect_min_size = Vector2( 2, 0 )
+
+[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Passing"]
+margin_left = 6.0
+margin_top = 10.0
+margin_right = 54.0
+margin_bottom = 24.0
+text = "Passing"
+
+[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Passing"]
+margin_left = 58.0
+margin_top = 10.0
+margin_right = 73.0
+margin_bottom = 24.0
+text = "---"
+
+[node name="Failing" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
+visible = false
+margin_left = 34.0
+margin_right = 100.0
+margin_bottom = 35.0
+
+[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Failing"]
+margin_right = 2.0
+margin_bottom = 35.0
+rect_min_size = Vector2( 2, 0 )
+
+[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Failing"]
+margin_left = 6.0
+margin_top = 10.0
+margin_right = 47.0
+margin_bottom = 24.0
+text = "Failing"
+
+[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Failing"]
+margin_left = 51.0
+margin_top = 10.0
+margin_right = 66.0
+margin_bottom = 24.0
+text = "---"
+
+[node name="Pending" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
+visible = false
+margin_left = 34.0
+margin_right = 110.0
+margin_bottom = 35.0
+
+[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Pending"]
+margin_right = 2.0
+margin_bottom = 35.0
+rect_min_size = Vector2( 2, 0 )
+
+[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Pending"]
+margin_left = 6.0
+margin_top = 10.0
+margin_right = 57.0
+margin_bottom = 24.0
+text = "Pending"
+
+[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Pending"]
+margin_left = 61.0
+margin_top = 10.0
+margin_right = 76.0
+margin_bottom = 24.0
+text = "---"
+
+[node name="Orphans" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
+visible = false
+margin_left = 34.0
+margin_right = 110.0
+margin_bottom = 35.0
+
+[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Orphans"]
+margin_right = 2.0
+margin_bottom = 35.0
+rect_min_size = Vector2( 2, 0 )
+
+[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Orphans"]
+margin_left = 6.0
+margin_top = 10.0
+margin_right = 57.0
+margin_bottom = 24.0
+text = "Orphans"
+
+[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Orphans"]
+margin_left = 61.0
+margin_top = 10.0
+margin_right = 76.0
+margin_bottom = 24.0
+text = "---"
+
+[node name="Errors" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
+visible = false
+margin_left = 34.0
+margin_right = 96.0
+margin_bottom = 35.0
+
+[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Errors"]
+margin_right = 2.0
+margin_bottom = 35.0
+rect_min_size = Vector2( 2, 0 )
+
+[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Errors"]
+margin_left = 6.0
+margin_top = 10.0
+margin_right = 43.0
+margin_bottom = 24.0
+hint_tooltip = "The number of GUT errors generated. This does not include engine errors."
+text = "Errors"
+
+[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Errors"]
+margin_left = 47.0
+margin_top = 10.0
+margin_right = 62.0
+margin_bottom = 24.0
+text = "---"
+
+[node name="Warnings" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
+visible = false
+margin_left = 34.0
+margin_right = 118.0
+margin_bottom = 35.0
+
+[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Warnings"]
+margin_right = 2.0
+margin_bottom = 35.0
+rect_min_size = Vector2( 2, 0 )
+
+[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Warnings"]
+margin_left = 6.0
+margin_top = 10.0
+margin_right = 65.0
+margin_bottom = 24.0
+text = "Warnings"
+__meta__ = {
+"_editor_description_": "The number of GUT Warnings generated. This does not include engine warnings."
+}
+
+[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Warnings"]
+margin_left = 69.0
+margin_top = 10.0
+margin_right = 84.0
+margin_bottom = 24.0
+text = "---"
+
+[node name="CenterContainer" type="CenterContainer" parent="layout/RSplit/CResults/ControlBar"]
+margin_left = 34.0
+margin_right = 488.0
+margin_bottom = 35.0
+size_flags_horizontal = 3
+
+[node name="CopyButton" type="Button" parent="layout/RSplit/CResults/ControlBar"]
+margin_left = 492.0
+margin_right = 547.0
+margin_bottom = 35.0
+text = " Copy "
+
+[node name="ClearButton" type="Button" parent="layout/RSplit/CResults/ControlBar"]
+margin_left = 551.0
+margin_right = 611.0
+margin_bottom = 35.0
+text = " Clear "
+
+[node name="Output" type="RichTextLabel" parent="layout/RSplit/CResults"]
+margin_top = 39.0
+margin_right = 611.0
+margin_bottom = 555.0
+focus_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+custom_fonts/bold_italics_font = SubResource( 11 )
+custom_fonts/italics_font = SubResource( 13 )
+custom_fonts/bold_font = SubResource( 15 )
+custom_fonts/normal_font = SubResource( 17 )
+bbcode_enabled = true
+scroll_following = true
+selection_enabled = true
+
+[node name="sc" type="ScrollContainer" parent="layout/RSplit"]
+margin_left = 623.0
+margin_right = 1023.0
+margin_bottom = 555.0
+rect_min_size = Vector2( 400, 0 )
+mouse_filter = 1
+size_flags_vertical = 3
+
+[node name="Settings" type="VBoxContainer" parent="layout/RSplit/sc"]
+margin_right = 388.0
+margin_bottom = 862.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="BottomPanelShortcuts" parent="." instance=ExtResource( 2 )]
+visible = false
+anchor_left = -0.000517324
+anchor_top = 0.000882874
+anchor_right = 0.233483
+anchor_bottom = 0.328883
+margin_left = 10.0649
+margin_top = -173.752
+margin_right = 31.6969
+margin_bottom = -125.552
+
+[connection signal="pressed" from="layout/ControlBar/RunAll" to="." method="_on_RunAll_pressed"]
+[connection signal="run_tests" from="layout/ControlBar/RunAtCursor" to="." method="_on_RunAtCursor_run_tests"]
+[connection signal="pressed" from="layout/ControlBar/FocusButton" to="." method="_on_FocusButton_pressed"]
+[connection signal="pressed" from="layout/ControlBar/Shortcuts" to="." method="_on_Shortcuts_pressed"]
+[connection signal="draw" from="layout/RSplit/CResults/ControlBar/Light" to="." method="_on_Light_draw"]
+[connection signal="pressed" from="layout/RSplit/CResults/ControlBar/CopyButton" to="." method="_on_CopyButton_pressed"]
+[connection signal="pressed" from="layout/RSplit/CResults/ControlBar/ClearButton" to="." method="_on_ClearButton_pressed"]
+[connection signal="popup_hide" from="BottomPanelShortcuts" to="." method="_on_BottomPanelShortcuts_popup_hide"]
diff --git a/addons/gut/gui/GutRunner.gd b/addons/gut/gui/GutRunner.gd
new file mode 100644
index 0000000..b2b16f2
--- /dev/null
+++ b/addons/gut/gui/GutRunner.gd
@@ -0,0 +1,98 @@
+extends Node2D
+
+var Gut = load('res://addons/gut/gut.gd')
+var ResultExporter = load('res://addons/gut/result_exporter.gd')
+var GutConfig = load('res://addons/gut/gut_config.gd')
+
+const RUNNER_JSON_PATH = 'res://.gut_editor_config.json'
+const RESULT_FILE = 'user://.gut_editor.bbcode'
+const RESULT_JSON = 'user://.gut_editor.json'
+
+var _gut_config = null
+var _gut = null;
+var _wrote_results = false
+# Flag for when this is being used at the command line. Otherwise it is
+# assumed this is being used by the panel and being launched with
+# play_custom_scene
+var _cmdln_mode = false
+
+onready var _gut_layer = $GutLayer
+
+
+func _ready():
+ if(_gut_config == null):
+ _gut_config = GutConfig.new()
+ _gut_config.load_options(RUNNER_JSON_PATH)
+
+
+ # The command line will call run_tests on its own. When used from the panel
+ # we have to kick off the tests ourselves b/c there's no way I know of to
+ # interact with the scene that was run via play_custom_scene.
+ if(!_cmdln_mode):
+ call_deferred('run_tests')
+
+
+func run_tests():
+ if(_gut == null):
+ _gut = Gut.new()
+
+ _gut.set_add_children_to(self)
+ if(_gut_config.options.gut_on_top):
+ _gut_layer.add_child(_gut)
+ else:
+ add_child(_gut)
+
+ if(!_cmdln_mode):
+ _gut.connect('tests_finished', self, '_on_tests_finished',
+ [_gut_config.options.should_exit, _gut_config.options.should_exit_on_success])
+
+ _gut_config.config_gut(_gut)
+ if(_gut_config.options.gut_on_top):
+ _gut.get_gui().goto_bottom_right_corner()
+
+ var run_rest_of_scripts = _gut_config.options.unit_test_name == ''
+ _gut.test_scripts(run_rest_of_scripts)
+
+
+func _write_results():
+ # bbcode_text appears to be empty. I'm not 100% sure why. Until that is
+ # figured out we have to just get the text which stinks.
+ var content = _gut.get_gui().get_text_box().text
+
+ var f = File.new()
+ var result = f.open(RESULT_FILE, f.WRITE)
+ if(result == OK):
+ f.store_string(content)
+ f.close()
+ else:
+ print('ERROR Could not save bbcode, result = ', result)
+
+ var exporter = ResultExporter.new()
+ var f_result = exporter.write_summary_file(_gut, RESULT_JSON)
+ _wrote_results = true
+
+
+func _exit_tree():
+ if(!_wrote_results and !_cmdln_mode):
+ _write_results()
+
+
+func _on_tests_finished(should_exit, should_exit_on_success):
+ _write_results()
+
+ if(should_exit):
+ get_tree().quit()
+ elif(should_exit_on_success and _gut.get_fail_count() == 0):
+ get_tree().quit()
+
+
+func get_gut():
+ if(_gut == null):
+ _gut = Gut.new()
+ return _gut
+
+func set_gut_config(which):
+ _gut_config = which
+
+func set_cmdln_mode(is_it):
+ _cmdln_mode = is_it
diff --git a/addons/gut/gui/GutRunner.tscn b/addons/gut/gui/GutRunner.tscn
new file mode 100644
index 0000000..077e411
--- /dev/null
+++ b/addons/gut/gui/GutRunner.tscn
@@ -0,0 +1,9 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://addons/gut/gui/GutRunner.gd" type="Script" id=1]
+
+[node name="GutRunner" type="Node2D"]
+script = ExtResource( 1 )
+
+[node name="GutLayer" type="CanvasLayer" parent="."]
+layer = 128
diff --git a/addons/gut/gui/GutSceneTheme.tres b/addons/gut/gui/GutSceneTheme.tres
new file mode 100644
index 0000000..565a6af
--- /dev/null
+++ b/addons/gut/gui/GutSceneTheme.tres
@@ -0,0 +1,11 @@
+[gd_resource type="Theme" load_steps=3 format=2]
+
+[sub_resource type="DynamicFontData" id=9]
+font_path = "res://addons/gut/fonts/AnonymousPro-Regular.ttf"
+
+[sub_resource type="DynamicFont" id=10]
+size = 14
+font_data = SubResource( 9 )
+
+[resource]
+default_font = SubResource( 10 )
diff --git a/addons/gut/gui/RunAtCursor.gd b/addons/gut/gui/RunAtCursor.gd
new file mode 100644
index 0000000..960414e
--- /dev/null
+++ b/addons/gut/gui/RunAtCursor.gd
@@ -0,0 +1,130 @@
+tool
+extends Control
+
+
+var ScriptTextEditors = load('res://addons/gut/gui/script_text_editor_controls.gd')
+
+onready var _ctrls = {
+ btn_script = $HBox/BtnRunScript,
+ btn_inner = $HBox/BtnRunInnerClass,
+ btn_method = $HBox/BtnRunMethod,
+ lbl_none = $HBox/LblNoneSelected,
+ arrow_1 = $HBox/Arrow1,
+ arrow_2 = $HBox/Arrow2
+}
+
+var _editors = null
+var _script_editor = null
+var _cur_editor = null
+var _last_line = -1
+var _cur_script_path = null
+var _last_info = null
+
+signal run_tests(what)
+
+
+func _ready():
+ _ctrls.lbl_none.visible = true
+ _ctrls.btn_script.visible = false
+ _ctrls.btn_inner.visible = false
+ _ctrls.btn_method.visible = false
+
+
+func _set_editor(which):
+ _last_line = -1
+ if(_cur_editor != null and _cur_editor.get_ref()):
+ _cur_editor.get_ref().disconnect('cursor_changed', self, '_on_cursor_changed')
+
+ if(which != null):
+ _cur_editor = weakref(which)
+ which.connect('cursor_changed', self, '_on_cursor_changed', [which])
+
+ _last_line = which.cursor_get_line()
+ _last_info = _editors.get_line_info()
+ _update_buttons(_last_info)
+
+
+
+func _update_buttons(info):
+ _ctrls.lbl_none.visible = _cur_script_path == null
+ _ctrls.btn_script.visible = _cur_script_path != null
+
+ _ctrls.btn_inner.visible = info.inner_class != null
+ _ctrls.arrow_1.visible = info.inner_class != null
+ _ctrls.btn_inner.text = str(info.inner_class)
+ _ctrls.btn_inner.hint_tooltip = str("Run all tests in Inner-Test-Class ", info.inner_class)
+
+ _ctrls.btn_method.visible = info.test_method != null
+ _ctrls.arrow_2.visible = info.test_method != null
+ _ctrls.btn_method.text = str(info.test_method)
+ _ctrls.btn_method.hint_tooltip = str("Run test ", info.test_method)
+
+ # The button's new size won't take effect until the next frame.
+ # This appears to be what was causing the button to not be clickable the
+ # first time.
+ call_deferred("_update_rect_size")
+
+
+func _update_rect_size():
+ rect_min_size.x = _ctrls.btn_method.rect_size.x + _ctrls.btn_method.rect_position.x
+
+func _on_cursor_changed(which):
+ if(which.cursor_get_line() != _last_line):
+ _last_line = which.cursor_get_line()
+ _last_info = _editors.get_line_info()
+ _update_buttons(_last_info)
+
+
+func set_script_editor(value):
+ _script_editor = value
+ _editors = ScriptTextEditors.new(value)
+
+
+func activate_for_script(path):
+ _ctrls.btn_script.visible = true
+ _ctrls.btn_script.text = path.get_file()
+ _ctrls.btn_script.hint_tooltip = str("Run all tests in script ", path)
+ _cur_script_path = path
+ _editors.refresh()
+ _set_editor(_editors.get_current_text_edit())
+
+
+func _on_BtnRunScript_pressed():
+ var info = _last_info.duplicate()
+ info.script = _cur_script_path.get_file()
+ info.inner_class = null
+ info.test_method = null
+ emit_signal("run_tests", info)
+
+
+func _on_BtnRunInnerClass_pressed():
+ var info = _last_info.duplicate()
+ info.script = _cur_script_path.get_file()
+ info.test_method = null
+ emit_signal("run_tests", info)
+
+
+func _on_BtnRunMethod_pressed():
+ var info = _last_info.duplicate()
+ info.script = _cur_script_path.get_file()
+ emit_signal("run_tests", info)
+
+
+func get_script_button():
+ return _ctrls.btn_script
+
+
+func get_inner_button():
+ return _ctrls.btn_inner
+
+
+func get_test_button():
+ return _ctrls.btn_method
+
+# not used, thought was configurable but it's just the script prefix
+func set_method_prefix(value):
+ _editors.set_method_prefix(value)
+
+# not used, thought was configurable but it's just the script prefix
+func set_inner_class_prefix(value):
+ _editors.set_inner_class_prefix(value)
diff --git a/addons/gut/gui/RunAtCursor.tscn b/addons/gut/gui/RunAtCursor.tscn
new file mode 100644
index 0000000..b4662df
--- /dev/null
+++ b/addons/gut/gui/RunAtCursor.tscn
@@ -0,0 +1,80 @@
+[gd_scene load_steps=4 format=2]
+
+[ext_resource path="res://addons/gut/gui/RunAtCursor.gd" type="Script" id=1]
+[ext_resource path="res://addons/gut/gui/play.png" type="Texture" id=2]
+[ext_resource path="res://addons/gut/gui/arrow.png" type="Texture" id=3]
+
+[node name="RunAtCursor" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_right = 1.0
+margin_bottom = -527.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="HBox" type="HBoxContainer" parent="."]
+anchor_right = 1.0
+anchor_bottom = 1.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="LblNoneSelected" type="Label" parent="HBox"]
+margin_top = 29.0
+margin_right = 50.0
+margin_bottom = 43.0
+text = "<None>"
+
+[node name="BtnRunScript" type="Button" parent="HBox"]
+visible = false
+margin_left = 54.0
+margin_right = 140.0
+margin_bottom = 73.0
+text = "<script>"
+icon = ExtResource( 2 )
+
+[node name="Arrow1" type="TextureButton" parent="HBox"]
+visible = false
+margin_left = 54.0
+margin_right = 78.0
+margin_bottom = 73.0
+rect_min_size = Vector2( 24, 0 )
+texture_normal = ExtResource( 3 )
+expand = true
+stretch_mode = 3
+
+[node name="BtnRunInnerClass" type="Button" parent="HBox"]
+visible = false
+margin_left = 134.0
+margin_right = 243.0
+margin_bottom = 73.0
+text = "<inner class>"
+icon = ExtResource( 2 )
+
+[node name="Arrow2" type="TextureButton" parent="HBox"]
+visible = false
+margin_left = 54.0
+margin_right = 78.0
+margin_bottom = 73.0
+rect_min_size = Vector2( 24, 0 )
+texture_normal = ExtResource( 3 )
+expand = true
+stretch_mode = 3
+
+[node name="BtnRunMethod" type="Button" parent="HBox"]
+visible = false
+margin_left = 247.0
+margin_right = 337.0
+margin_bottom = 73.0
+text = "<method>"
+icon = ExtResource( 2 )
+
+[connection signal="pressed" from="HBox/BtnRunScript" to="." method="_on_BtnRunScript_pressed"]
+[connection signal="pressed" from="HBox/BtnRunInnerClass" to="." method="_on_BtnRunInnerClass_pressed"]
+[connection signal="pressed" from="HBox/BtnRunMethod" to="." method="_on_BtnRunMethod_pressed"]
diff --git a/addons/gut/gui/Settings.tscn b/addons/gut/gui/Settings.tscn
new file mode 100644
index 0000000..819a56e
--- /dev/null
+++ b/addons/gut/gui/Settings.tscn
@@ -0,0 +1,7 @@
+[gd_scene format=2]
+
+[node name="Settings" type="VBoxContainer"]
+margin_right = 388.0
+margin_bottom = 586.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
diff --git a/addons/gut/gui/ShortcutButton.gd b/addons/gut/gui/ShortcutButton.gd
new file mode 100644
index 0000000..116659d
--- /dev/null
+++ b/addons/gut/gui/ShortcutButton.gd
@@ -0,0 +1,164 @@
+tool
+extends Control
+
+
+onready var _ctrls = {
+ shortcut_label = $Layout/lblShortcut,
+ set_button = $Layout/SetButton,
+ save_button = $Layout/SaveButton,
+ cancel_button = $Layout/CancelButton,
+ clear_button = $Layout/ClearButton
+}
+
+signal changed
+signal start_edit
+signal end_edit
+
+const NO_SHORTCUT = '<None>'
+
+var _source_event = InputEventKey.new()
+var _pre_edit_event = null
+var _key_disp = NO_SHORTCUT
+
+var _modifier_keys = [KEY_ALT, KEY_CONTROL, KEY_META, KEY_SHIFT]
+
+# Called when the node enters the scene tree for the first time.
+func _ready():
+ set_process_unhandled_key_input(false)
+
+
+func _display_shortcut():
+ _ctrls.shortcut_label.text = to_s()
+
+
+func _is_shift_only_modifier():
+ return _source_event.shift and \
+ !(_source_event.meta or _source_event.control or _source_event.alt)
+
+
+func _has_modifier():
+ return _source_event.alt or _source_event.control or _source_event.meta or _source_event.shift
+
+
+func _is_modifier(scancode):
+ return _modifier_keys.has(scancode)
+
+
+func _edit_mode(should):
+ set_process_unhandled_key_input(should)
+ _ctrls.set_button.visible = !should
+ _ctrls.save_button.visible = should
+ _ctrls.save_button.disabled = should
+ _ctrls.cancel_button.visible = should
+ _ctrls.clear_button.visible = !should
+
+ if(should and to_s() == ''):
+ _ctrls.shortcut_label.text = 'press buttons'
+ else:
+ _ctrls.shortcut_label.text = to_s()
+
+ if(should):
+ emit_signal("start_edit")
+ else:
+ emit_signal("end_edit")
+
+# ---------------
+# Events
+# ---------------
+func _unhandled_key_input(event):
+ if(event is InputEventKey):
+ if(event.pressed):
+ _source_event.alt = event.alt or event.scancode == KEY_ALT
+ _source_event.control = event.control or event.scancode == KEY_CONTROL
+ _source_event.meta = event.meta or event.scancode == KEY_META
+ _source_event.shift = event.shift or event.scancode == KEY_SHIFT
+
+ if(_has_modifier() and !_is_modifier(event.scancode)):
+ _source_event.scancode = event.scancode
+ _key_disp = OS.get_scancode_string(event.scancode)
+ else:
+# _source_event.set_scancode = null
+ _key_disp = NO_SHORTCUT
+ _display_shortcut()
+ _ctrls.save_button.disabled = !is_valid()
+
+
+func _on_SetButton_pressed():
+ _pre_edit_event = _source_event.duplicate(true)
+ _edit_mode(true)
+
+
+func _on_SaveButton_pressed():
+ _edit_mode(false)
+ _pre_edit_event = null
+ emit_signal('changed')
+
+
+func _on_CancelButton_pressed():
+ _edit_mode(false)
+ _source_event = _pre_edit_event
+ _key_disp = OS.get_scancode_string(_source_event.scancode)
+ if(_key_disp == ''):
+ _key_disp = NO_SHORTCUT
+ _display_shortcut()
+
+
+func _on_ClearButton_pressed():
+ clear_shortcut()
+
+# ---------------
+# Public
+# ---------------
+func to_s():
+ var modifiers = []
+ if(_source_event.alt):
+ modifiers.append('alt')
+ if(_source_event.control):
+ modifiers.append('ctrl')
+ if(_source_event.meta):
+ modifiers.append('meta')
+ if(_source_event.shift):
+ modifiers.append('shift')
+
+ if(_source_event.scancode != null):
+ modifiers.append(_key_disp)
+
+ var mod_text = ''
+ for i in range(modifiers.size()):
+ mod_text += modifiers[i]
+ if(i != modifiers.size() - 1):
+ mod_text += ' + '
+
+ return mod_text
+
+
+func is_valid():
+ return _has_modifier() and _key_disp != NO_SHORTCUT and !_is_shift_only_modifier()
+
+
+func get_shortcut():
+ var to_return = ShortCut.new()
+ to_return.shortcut = _source_event
+ return to_return
+
+
+func set_shortcut(sc):
+ if(sc == null or sc.shortcut == null):
+ clear_shortcut()
+ else:
+ _source_event = sc.shortcut
+ _key_disp = OS.get_scancode_string(_source_event.scancode)
+ _display_shortcut()
+
+
+func clear_shortcut():
+ _source_event = InputEventKey.new()
+ _key_disp = NO_SHORTCUT
+ _display_shortcut()
+
+
+func disable_set(should):
+ _ctrls.set_button.disabled = should
+
+func disable_clear(should):
+ _ctrls.clear_button.disabled = should
diff --git a/addons/gut/gui/ShortcutButton.tscn b/addons/gut/gui/ShortcutButton.tscn
new file mode 100644
index 0000000..3c94317
--- /dev/null
+++ b/addons/gut/gui/ShortcutButton.tscn
@@ -0,0 +1,80 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://addons/gut/gui/ShortcutButton.gd" type="Script" id=1]
+
+[node name="ShortcutButton" type="Control"]
+anchor_right = 0.123
+anchor_bottom = 0.04
+margin_right = 33.048
+margin_bottom = 1.0
+rect_min_size = Vector2( 125, 25 )
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Layout" type="HBoxContainer" parent="."]
+anchor_right = 1.0
+anchor_bottom = 1.0
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="lblShortcut" type="Label" parent="Layout"]
+margin_right = 50.0
+margin_bottom = 25.0
+size_flags_horizontal = 3
+size_flags_vertical = 7
+text = "<None>"
+align = 2
+valign = 1
+
+[node name="CenterContainer" type="CenterContainer" parent="Layout"]
+margin_left = 54.0
+margin_right = 64.0
+margin_bottom = 25.0
+rect_min_size = Vector2( 10, 0 )
+
+[node name="SetButton" type="Button" parent="Layout"]
+margin_left = 68.0
+margin_right = 128.0
+margin_bottom = 25.0
+rect_min_size = Vector2( 60, 0 )
+text = "Set"
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="SaveButton" type="Button" parent="Layout"]
+visible = false
+margin_left = 82.0
+margin_right = 142.0
+margin_bottom = 25.0
+rect_min_size = Vector2( 60, 0 )
+text = "Save"
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="CancelButton" type="Button" parent="Layout"]
+visible = false
+margin_left = 82.0
+margin_right = 142.0
+margin_bottom = 25.0
+rect_min_size = Vector2( 60, 0 )
+text = "Cancel"
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="ClearButton" type="Button" parent="Layout"]
+margin_left = 132.0
+margin_right = 192.0
+margin_bottom = 25.0
+rect_min_size = Vector2( 60, 0 )
+text = "Clear"
+
+[connection signal="pressed" from="Layout/SetButton" to="." method="_on_SetButton_pressed"]
+[connection signal="pressed" from="Layout/SaveButton" to="." method="_on_SaveButton_pressed"]
+[connection signal="pressed" from="Layout/CancelButton" to="." method="_on_CancelButton_pressed"]
+[connection signal="pressed" from="Layout/ClearButton" to="." method="_on_ClearButton_pressed"]
diff --git a/addons/gut/gui/arrow.png b/addons/gut/gui/arrow.png
new file mode 100644
index 0000000..09d8323
--- /dev/null
+++ b/addons/gut/gui/arrow.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f9918e125593427685252d339f9c49d2ce8ce0b0fff9d28089164d78fccd80e2
+size 645
diff --git a/addons/gut/gui/arrow.png.import b/addons/gut/gui/arrow.png.import
new file mode 100644
index 0000000..3336a6b
--- /dev/null
+++ b/addons/gut/gui/arrow.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/arrow.png-2b5b2d838b5b3467cf300ac2da1630d9.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/gut/gui/arrow.png"
+dest_files=[ "res://.import/arrow.png-2b5b2d838b5b3467cf300ac2da1630d9.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/gut/gui/gut_config_gui.gd b/addons/gut/gui/gut_config_gui.gd
new file mode 100644
index 0000000..dc58be4
--- /dev/null
+++ b/addons/gut/gui/gut_config_gui.gd
@@ -0,0 +1,408 @@
+# ------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+class DirectoryCtrl:
+ extends HBoxContainer
+
+ var text = '' setget set_text, get_text
+ var _txt_path = LineEdit.new()
+ var _btn_dir = Button.new()
+ var _dialog = FileDialog.new()
+
+ func _init():
+ _btn_dir.text = '...'
+ _btn_dir.connect('pressed', self, '_on_dir_button_pressed')
+
+ _txt_path.size_flags_horizontal = _txt_path.SIZE_EXPAND_FILL
+
+ _dialog.mode = _dialog.MODE_OPEN_DIR
+ _dialog.resizable = true
+ _dialog.connect("dir_selected", self, '_on_selected')
+ _dialog.connect("file_selected", self, '_on_selected')
+ _dialog.rect_size = Vector2(1000, 700)
+
+ func _on_selected(path):
+ set_text(path)
+
+
+ func _on_dir_button_pressed():
+ _dialog.current_dir = _txt_path.text
+ _dialog.popup_centered()
+
+
+ func _ready():
+ add_child(_txt_path)
+ add_child(_btn_dir)
+ add_child(_dialog)
+
+ func get_text():
+ return _txt_path.text
+
+ func set_text(t):
+ text = t
+ _txt_path.text = text
+
+ func get_line_edit():
+ return _txt_path
+
+# ------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+class FileCtrl:
+ extends DirectoryCtrl
+
+ func _init():
+ _dialog.mode = _dialog.MODE_OPEN_FILE
+
+# ------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+class Vector2Ctrl:
+ extends VBoxContainer
+
+ var value = Vector2(-1, -1) setget set_value, get_value
+ var disabled = false setget set_disabled, get_disabled
+ var x_spin = SpinBox.new()
+ var y_spin = SpinBox.new()
+
+ func _init():
+ add_child(_make_one('x: ', x_spin))
+ add_child(_make_one('y: ', y_spin))
+
+ func _make_one(txt, spinner):
+ var hbox = HBoxContainer.new()
+ var lbl = Label.new()
+ lbl.text = txt
+ hbox.add_child(lbl)
+ hbox.add_child(spinner)
+ spinner.min_value = -1
+ spinner.max_value = 10000
+ spinner.size_flags_horizontal = spinner.SIZE_EXPAND_FILL
+ return hbox
+
+ func set_value(v):
+ if(v != null):
+ x_spin.value = v[0]
+ y_spin.value = v[1]
+
+ # Returns array instead of vector2 b/c that is what is stored in
+ # in the dictionary and what is expected everywhere else.
+ func get_value():
+ return [x_spin.value, y_spin.value]
+
+ func set_disabled(should):
+ get_parent().visible = !should
+ x_spin.visible = !should
+ y_spin.visible = !should
+
+ func get_disabled():
+ pass
+
+
+
+# ------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+var _base_container = null
+var _base_control = null
+const DIRS_TO_LIST = 6
+var _cfg_ctrls = {}
+var _avail_fonts = ['AnonymousPro', 'CourierPrime', 'LobsterTwo', 'Default']
+
+
+func _init(cont):
+ _base_container = cont
+
+ _base_control = HBoxContainer.new()
+ _base_control.size_flags_horizontal = _base_control.SIZE_EXPAND_FILL
+ _base_control.mouse_filter = _base_control.MOUSE_FILTER_PASS
+
+ # I don't remember what this is all about at all. Could be
+ # garbage. Decided to spend more time typing this message
+ # than figuring it out.
+ var lbl = Label.new()
+ lbl.size_flags_horizontal = lbl.SIZE_EXPAND_FILL
+ lbl.mouse_filter = lbl.MOUSE_FILTER_STOP
+ _base_control.add_child(lbl)
+
+
+# ------------------
+# Private
+# ------------------
+func _new_row(key, disp_text, value_ctrl, hint):
+ var ctrl = _base_control.duplicate()
+ var lbl = ctrl.get_node("Label")
+
+ lbl.hint_tooltip = hint
+ lbl.text = disp_text
+ _base_container.add_child(ctrl)
+
+ _cfg_ctrls[key] = value_ctrl
+ ctrl.add_child(value_ctrl)
+
+ var rpad = CenterContainer.new()
+ rpad.rect_min_size.x = 5
+ ctrl.add_child(rpad)
+
+ return ctrl
+
+
+func _add_title(text):
+ var row = _base_control.duplicate()
+ var lbl = row.get_node('Label')
+
+ lbl.text = text
+ lbl.align = Label.ALIGN_CENTER
+ _base_container.add_child(row)
+
+ row.connect('draw', self, '_on_title_cell_draw', [row])
+
+
+func _add_number(key, value, disp_text, v_min, v_max, hint=''):
+ var value_ctrl = SpinBox.new()
+ value_ctrl.value = value
+ value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
+ value_ctrl.min_value = v_min
+ value_ctrl.max_value = v_max
+ _wire_select_on_focus(value_ctrl.get_line_edit())
+
+ _new_row(key, disp_text, value_ctrl, hint)
+
+
+func _add_select(key, value, values, disp_text, hint=''):
+ var value_ctrl = OptionButton.new()
+ var select_idx = 0
+ for i in range(values.size()):
+ value_ctrl.add_item(values[i])
+ if(value == values[i]):
+ select_idx = i
+ value_ctrl.selected = select_idx
+ value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
+
+ _new_row(key, disp_text, value_ctrl, hint)
+
+
+func _add_value(key, value, disp_text, hint=''):
+ var value_ctrl = LineEdit.new()
+ value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
+ value_ctrl.text = value
+ _wire_select_on_focus(value_ctrl)
+
+ _new_row(key, disp_text, value_ctrl, hint)
+
+
+func _add_boolean(key, value, disp_text, hint=''):
+ var value_ctrl = CheckBox.new()
+ value_ctrl.pressed = value
+
+ _new_row(key, disp_text, value_ctrl, hint)
+
+
+func _add_directory(key, value, disp_text, hint=''):
+ var value_ctrl = DirectoryCtrl.new()
+ value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
+ value_ctrl.text = value
+ _wire_select_on_focus(value_ctrl.get_line_edit())
+
+ _new_row(key, disp_text, value_ctrl, hint)
+
+
+func _add_file(key, value, disp_text, hint=''):
+ var value_ctrl = FileCtrl.new()
+ value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
+ value_ctrl.text = value
+ _wire_select_on_focus(value_ctrl.get_line_edit())
+
+ _new_row(key, disp_text, value_ctrl, hint)
+
+
+func _add_color(key, value, disp_text, hint=''):
+ var value_ctrl = ColorPickerButton.new()
+ value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
+ value_ctrl.color = value
+
+ _new_row(key, disp_text, value_ctrl, hint)
+
+
+func _add_vector2(key, value, disp_text, hint=''):
+ var value_ctrl = Vector2Ctrl.new()
+ value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
+ value_ctrl.value = value
+ _wire_select_on_focus(value_ctrl.x_spin.get_line_edit())
+ _wire_select_on_focus(value_ctrl.y_spin.get_line_edit())
+
+ _new_row(key, disp_text, value_ctrl, hint)
+# -----------------------------
+
+
+# ------------------
+# Events
+# ------------------
+func _wire_select_on_focus(which):
+ which.connect('focus_entered', self, '_on_ctrl_focus_highlight', [which])
+ which.connect('focus_exited', self, '_on_ctrl_focus_unhighlight', [which])
+
+
+func _on_ctrl_focus_highlight(which):
+ if(which.has_method('select_all')):
+ which.call_deferred('select_all')
+
+
+func _on_ctrl_focus_unhighlight(which):
+ if(which.has_method('select')):
+ which.select(0, 0)
+
+
+func _on_title_cell_draw(which):
+ which.draw_rect(Rect2(Vector2(0, 0), which.rect_size), Color(0, 0, 0, .15))
+
+
+# ------------------
+# Public
+# ------------------
+func get_config_issues():
+ var to_return = []
+ var has_directory = false
+ var dir = Directory.new()
+
+ for i in range(DIRS_TO_LIST):
+ var key = str('directory_', i)
+ var path = _cfg_ctrls[key].text
+ if(path != null and path != ''):
+ has_directory = true
+ if(!dir.dir_exists(path)):
+ to_return.append(str('Test directory ', path, ' does not exist.'))
+
+ if(!has_directory):
+ to_return.append('You do not have any directories set.')
+
+ if(_cfg_ctrls['prefix'].text == ''):
+ to_return.append("You must set a Script prefix or GUT won't find any scripts")
+
+ return to_return
+
+
+func set_options(options):
+ _add_title("Settings")
+ _add_number("log_level", options.log_level, "Log Level", 0, 3,
+ "Detail level for log messages.\n" + \
+ "\t0: Errors and failures only.\n" + \
+ "\t1: Adds all test names + warnings + info\n" + \
+ "\t2: Shows all asserts\n" + \
+ "\t3: Adds more stuff probably, maybe not.")
+ _add_boolean('ignore_pause', options.ignore_pause, 'Ignore Pause',
+ "Ignore calls to pause_before_teardown")
+ _add_boolean('hide_orphans', options.hide_orphans, 'Hide Orphans',
+ 'Do not display orphan counts in output.')
+ _add_boolean('should_exit', options.should_exit, 'Exit on Finish',
+ "Exit when tests finished.")
+ _add_boolean('should_exit_on_success', options.should_exit_on_success, 'Exit on Success',
+ "Exit if there are no failures. Does nothing if 'Exit on Finish' is enabled.")
+
+
+ _add_title("Panel Output")
+ _add_select('output_font_name', options.panel_options.font_name, _avail_fonts, 'Font',
+ "The name of the font to use when running tests and in the output panel to the left.")
+ _add_number('output_font_size', options.panel_options.font_size, 'Font Size', 5, 100,
+ "The font size to use when running tests and in the output panel to the left.")
+
+
+ _add_title('Runner Window')
+ _add_boolean("gut_on_top", options.gut_on_top, "On Top",
+ "The GUT Runner appears above children added during tests.")
+ _add_number('opacity', options.opacity, 'Opacity', 0, 100,
+ "The opacity of GUT when tests are running.")
+ _add_boolean('should_maximize', options.should_maximize, 'Maximize',
+ "Maximize GUT when tests are being run.")
+ _add_boolean('compact_mode', options.compact_mode, 'Compact Mode',
+ 'The runner will be in compact mode. This overrides Maximize.')
+
+ _add_title('Runner Appearance')
+ _add_select('font_name', options.font_name, _avail_fonts, 'Font',
+ "The font to use for text output in the Gut Runner.")
+ _add_number('font_size', options.font_size, 'Font Size', 5, 100,
+ "The font size for text output in the Gut Runner.")
+ _add_color('font_color', options.font_color, 'Font Color',
+ "The font color for text output in the Gut Runner.")
+ _add_color('background_color', options.background_color, 'Background Color',
+ "The background color for text output in the Gut Runner.")
+ _add_boolean('disable_colors', options.disable_colors, 'Disable Formatting',
+ 'Disable formatting and colors used in the Runner. Does not affect panel output.')
+
+ _add_title('Test Directories')
+ _add_boolean('include_subdirs', options.include_subdirs, 'Include Subdirs',
+ "Include subdirectories of the directories configured below.")
+ for i in range(DIRS_TO_LIST):
+ var value = ''
+ if(options.dirs.size() > i):
+ value = options.dirs[i]
+
+ _add_directory(str('directory_', i), value, str('Directory ', i))
+
+ _add_title("XML Output")
+ _add_value("junit_xml_file", options.junit_xml_file, "Output Path",
+ "Path and filename where GUT should create a JUnit compliant XML file. " +
+ "This file will contain the results of the last test run. To avoid " +
+ "overriding the file use Include Timestamp.")
+ _add_boolean("junit_xml_timestamp", options.junit_xml_timestamp, "Include Timestamp",
+ "Include a timestamp in the filename so that each run gets its own xml file.")
+
+
+ _add_title('Hooks')
+ _add_file('pre_run_script', options.pre_run_script, 'Pre-Run Hook',
+ 'This script will be run by GUT before any tests are run.')
+ _add_file('post_run_script', options.post_run_script, 'Post-Run Hook',
+ 'This script will be run by GUT after all tests are run.')
+
+
+ _add_title('Misc')
+ _add_value('prefix', options.prefix, 'Script Prefix',
+ "The filename prefix for all test scripts.")
+
+
+func get_options(base_opts):
+ var to_return = base_opts.duplicate()
+
+ # Settings
+ to_return.log_level = _cfg_ctrls.log_level.value
+ to_return.ignore_pause = _cfg_ctrls.ignore_pause.pressed
+ to_return.hide_orphans = _cfg_ctrls.hide_orphans.pressed
+ to_return.should_exit = _cfg_ctrls.should_exit.pressed
+ to_return.should_exit_on_success = _cfg_ctrls.should_exit_on_success.pressed
+
+ #Output
+ to_return.panel_options.font_name = _cfg_ctrls.output_font_name.get_item_text(
+ _cfg_ctrls.output_font_name.selected)
+ to_return.panel_options.font_size = _cfg_ctrls.output_font_size.value
+
+ # Runner Appearance
+ to_return.font_name = _cfg_ctrls.font_name.get_item_text(
+ _cfg_ctrls.font_name.selected)
+ to_return.font_size = _cfg_ctrls.font_size.value
+ to_return.should_maximize = _cfg_ctrls.should_maximize.pressed
+ to_return.compact_mode = _cfg_ctrls.compact_mode.pressed
+ to_return.opacity = _cfg_ctrls.opacity.value
+ to_return.background_color = _cfg_ctrls.background_color.color.to_html()
+ to_return.font_color = _cfg_ctrls.font_color.color.to_html()
+ to_return.disable_colors = _cfg_ctrls.disable_colors.pressed
+ to_return.gut_on_top = _cfg_ctrls.gut_on_top.pressed
+
+
+ # Directories
+ to_return.include_subdirs = _cfg_ctrls.include_subdirs.pressed
+ var dirs = []
+ for i in range(DIRS_TO_LIST):
+ var key = str('directory_', i)
+ var val = _cfg_ctrls[key].text
+ if(val != '' and val != null):
+ dirs.append(val)
+ to_return.dirs = dirs
+
+ # XML Output
+ to_return.junit_xml_file = _cfg_ctrls.junit_xml_file.text
+ to_return.junit_xml_timestamp = _cfg_ctrls.junit_xml_timestamp.pressed
+
+ # Hooks
+ to_return.pre_run_script = _cfg_ctrls.pre_run_script.text
+ to_return.post_run_script = _cfg_ctrls.post_run_script.text
+
+ # Misc
+ to_return.prefix = _cfg_ctrls.prefix.text
+
+ return to_return
diff --git a/addons/gut/gui/play.png b/addons/gut/gui/play.png
new file mode 100644
index 0000000..697f319
--- /dev/null
+++ b/addons/gut/gui/play.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:90041b369445e15dfaa4ec011cb4f1c0536a8d63295a574e0f8743f772dc418e
+size 676
diff --git a/addons/gut/gui/play.png.import b/addons/gut/gui/play.png.import
new file mode 100644
index 0000000..047fdc4
--- /dev/null
+++ b/addons/gut/gui/play.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/play.png-5c90e88e8136487a183a099d67a7de24.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/gut/gui/play.png"
+dest_files=[ "res://.import/play.png-5c90e88e8136487a183a099d67a7de24.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/gut/gui/script_text_editor_controls.gd b/addons/gut/gui/script_text_editor_controls.gd
new file mode 100644
index 0000000..98741e9
--- /dev/null
+++ b/addons/gut/gui/script_text_editor_controls.gd
@@ -0,0 +1,230 @@
+# Holds weakrefs to a ScriptTextEditor and related children nodes
+# that might be useful. Though the TextEdit is really the only one, but
+# since the tree may change, the first TextEdit under a CodeTextEditor is
+# the one we use...so we hold a ref to the CodeTextEditor too.
+class ScriptEditorControlRef:
+ var _text_edit = null
+ var _script_editor = null
+ var _code_editor = null
+
+ func _init(script_edit):
+ _script_editor = weakref(script_edit)
+ _populate_controls()
+
+
+ func _populate_controls():
+ # who knows if the tree will change so get the first instance of each
+ # type of control we care about. Chances are there won't be more than
+ # one of these in the future, but their position in the tree may change.
+ _code_editor = weakref(_get_first_child_named('CodeTextEditor', _script_editor.get_ref()))
+ _text_edit = weakref(_get_first_child_named("TextEdit", _code_editor.get_ref()))
+
+
+ func _get_first_child_named(obj_name, parent_obj):
+ if(parent_obj == null):
+ return null
+
+ var kids = parent_obj.get_children()
+ var index = 0
+ var to_return = null
+
+ while(index < kids.size() and to_return == null):
+ if(str(kids[index]).find(str("[", obj_name)) != -1):
+ to_return = kids[index]
+ else:
+ to_return = _get_first_child_named(obj_name, kids[index])
+ if(to_return == null):
+ index += 1
+
+ return to_return
+
+
+ func get_script_text_edit():
+ return _script_editor.get_ref()
+
+
+ func get_text_edit():
+ # ScriptTextEditors that are loaded when the project is opened
+ # do not have their children populated yet. So if we may have to
+ # _populate_controls again if we don't have one.
+ if(_text_edit == null):
+ _populate_controls()
+ return _text_edit.get_ref()
+
+
+ func get_script_editor():
+ return _script_editor
+
+
+ func is_visible():
+ var to_return = false
+ if(_script_editor.get_ref()):
+ to_return = _script_editor.get_ref().visible
+ return to_return
+
+# ##############################################################################
+#
+# ##############################################################################
+
+# Used to make searching for the controls easier and faster.
+var _script_editors_parent = null
+# reference the ScriptEditor instance
+var _script_editor = null
+# Array of ScriptEditorControlRef containing all the opened ScriptTextEditors
+# and related controls at the time of the last refresh.
+var _script_editor_controls = []
+
+var _method_prefix = 'test_'
+var _inner_class_prefix = 'Test'
+
+func _init(script_edit):
+ _script_editor = script_edit
+ refresh()
+
+
+func _is_script_editor(obj):
+ return str(obj).find('[ScriptTextEditor') != -1
+
+
+# Find the first ScriptTextEditor and then get its parent. Done this way
+# because who knows if the parent object will change. This is somewhat
+# future proofed.
+func _find_script_editors_parent():
+ var _first_editor = _get_first_child_of_type_name("ScriptTextEditor", _script_editor)
+ if(_first_editor != null):
+ _script_editors_parent = _first_editor.get_parent()
+
+
+func _populate_editors():
+ if(_script_editors_parent == null):
+ return
+
+ _script_editor_controls.clear()
+ for child in _script_editors_parent.get_children():
+ if(_is_script_editor(child)):
+ var ref = ScriptEditorControlRef.new(child)
+ _script_editor_controls.append(ref)
+
+# Yes, this is the same as the one above but with a different name. This was
+# easier than trying to find a place where it could be used by both.
+func _get_first_child_of_type_name(obj_name, parent_obj):
+ if(parent_obj == null):
+ return null
+
+ var kids = parent_obj.get_children()
+ var index = 0
+ var to_return = null
+
+ while(index < kids.size() and to_return == null):
+ if(str(kids[index]).find(str("[", obj_name)) != -1):
+ to_return = kids[index]
+ else:
+ to_return = _get_first_child_of_type_name(obj_name, kids[index])
+ if(to_return == null):
+ index += 1
+
+ return to_return
+
+
+func _get_func_name_from_line(text):
+ text = text.strip_edges()
+ var left = text.split("(")[0]
+ var func_name = left.split(" ")[1]
+ return func_name
+
+
+func _get_class_name_from_line(text):
+ text = text.strip_edges()
+ var right = text.split(" ")[1]
+ var the_name = right.rstrip(":")
+ return the_name
+
+func refresh():
+ if(_script_editors_parent == null):
+ _find_script_editors_parent()
+
+ if(_script_editors_parent != null):
+ _populate_editors()
+
+
+func get_current_text_edit():
+ var cur_script_editor = null
+ var idx = 0
+
+ while(idx < _script_editor_controls.size() and cur_script_editor == null):
+ if(_script_editor_controls[idx].is_visible()):
+ cur_script_editor = _script_editor_controls[idx]
+ else:
+ idx += 1
+
+ var to_return = null
+ if(cur_script_editor != null):
+ to_return = cur_script_editor.get_text_edit()
+
+ return to_return
+
+
+func get_script_editor_controls():
+ var to_return = []
+ for ctrl_ref in _script_editor_controls:
+ to_return.append(ctrl_ref.get_script_text_edit())
+
+ return to_return
+
+
+func get_line_info():
+ var editor = get_current_text_edit()
+ if(editor == null):
+ return
+
+ var info = {
+ script = null,
+ inner_class = null,
+ test_method = null
+ }
+
+ var line = editor.cursor_get_line()
+ var done_func = false
+ var done_inner = false
+ while(line > 0 and (!done_func or !done_inner)):
+ if(editor.can_fold(line)):
+ var text = editor.get_line(line)
+ var strip_text = text.strip_edges(true, false) # only left
+
+ if(!done_func and strip_text.begins_with("func ")):
+ var func_name = _get_func_name_from_line(text)
+ if(func_name.begins_with(_method_prefix)):
+ info.test_method = func_name
+ done_func = true
+ # If the func line is left justified then there won't be any
+ # inner classes above it.
+ if(strip_text == text):
+ done_inner = true
+
+ if(!done_inner and strip_text.begins_with("class")):
+ var inner_name = _get_class_name_from_line(text)
+ if(inner_name.begins_with(_inner_class_prefix)):
+ info.inner_class = inner_name
+ done_inner = true
+ # if we found an inner class then we are already past
+ # any test the cursor could be in.
+ done_func = true
+ line -= 1
+
+ return info
+
+
+func get_method_prefix():
+ return _method_prefix
+
+
+func set_method_prefix(method_prefix):
+ _method_prefix = method_prefix
+
+
+func get_inner_class_prefix():
+ return _inner_class_prefix
+
+
+func set_inner_class_prefix(inner_class_prefix):
+ _inner_class_prefix = inner_class_prefix