Fresh start: replace with naxIO/netfox-cs-sample foundation
Complete replacement of the tactical-shooter project with the netfox-cs-sample (MIT) — a CS 1.6 inspired multiplayer FPS built with Godot 4 and netfox. ## What's new - Full CS-style gameplay: teams (T/CT), rounds, economy, buy menu - 6 weapons: Knife, Glock, USP, AK-47, M4A1, AWP - Bomb plant/defuse with 2 bombsites - Flashbang & smoke grenades - Proper netfox rollback netcode at 64 tick - Network popup UI for host/join - HUD, crosshair, round timer, scoreboard - All netfox singletons registered as autoloads (works in exported builds) ## Architecture - Listen-server (host from client, no dedicated server binary) - Multiplayer-fps game lives at examples/multiplayer-fps/ - Netfox addons registered as autoloads for exported build compat - Godot 4.7 with Forward+ renderer ## Removed - Old headless-server architecture (client_main, server_main, player.gd, etc.) - Custom netfox bootstrap with ENet fallback - Old ChaffGames FPS template (2,420 lines, 844 KB) - SimulationServer GDExtension stub - Godot-jolt physics (netfox sample uses default Godot physics) - Duplicate weapon_data.gd, anti_cheat.gd, round_manager.gd, etc. - Server browser API Python venv (87 MB) - test_range map and modular assets ## Preserved - Git history - Server config at config/default_server_config.cfg - Windows export preset - Build directory (gitignored) Co-authored-by: naxIO <naxIO@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
[gd_scene format=3 uid="uid://4cj8dmy01ewy"]
|
||||
|
||||
[node name="comparison-popup" type="Window"]
|
||||
initial_position = 1
|
||||
size = Vector2i(384, 256)
|
||||
|
||||
[node name="Panel" type="Panel" parent="."]
|
||||
custom_minimum_size = Vector2(384, 256)
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Panel"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="GridContainer" type="GridContainer" parent="Panel/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
columns = 2
|
||||
|
||||
[node name="Got Label" type="Label" parent="Panel/VBoxContainer/GridContainer"]
|
||||
layout_mode = 2
|
||||
text = "Got
|
||||
"
|
||||
|
||||
[node name="Expected Label" type="Label" parent="Panel/VBoxContainer/GridContainer"]
|
||||
layout_mode = 2
|
||||
text = "Expected"
|
||||
|
||||
[node name="Got" type="TextEdit" parent="Panel/VBoxContainer/GridContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Expected" type="TextEdit" parent="Panel/VBoxContainer/GridContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Tools" type="HBoxContainer" parent="Panel/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="Reset Button" type="Button" parent="Panel/VBoxContainer/Tools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Reset"
|
||||
|
||||
[node name="OK Button" type="Button" parent="Panel/VBoxContainer/Tools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "OK"
|
||||
@@ -0,0 +1,40 @@
|
||||
[gd_scene format=3 uid="uid://dhly5ta07dd6r"]
|
||||
|
||||
[node name="message-popup" type="Window"]
|
||||
initial_position = 1
|
||||
size = Vector2i(384, 256)
|
||||
|
||||
[node name="Panel" type="Panel" parent="."]
|
||||
custom_minimum_size = Vector2(384, 256)
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Panel"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Contents" type="TextEdit" parent="Panel/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Tools" type="HBoxContainer" parent="Panel/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="Reset Button" type="Button" parent="Panel/VBoxContainer/Tools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Reset"
|
||||
|
||||
[node name="OK Button" type="Button" parent="Panel/VBoxContainer/Tools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "OK"
|
||||
@@ -0,0 +1,292 @@
|
||||
@tool
|
||||
extends Panel
|
||||
|
||||
var visibility_popup: VestUI.VisibilityPopup
|
||||
|
||||
@onready var _tree := %Tree as Tree
|
||||
@onready var _spinner := %Spinner as Control
|
||||
@onready var _spinner_icon := %"Spinner Icon" as TextureRect
|
||||
@onready var _spinner_label := %"Spinner Label" as Label
|
||||
@onready var _animation_player := $PanelContainer/Spinner/AnimationPlayer as AnimationPlayer
|
||||
|
||||
var _results: VestResult.Suite = null
|
||||
var _render_results: VestResult.Suite = null
|
||||
var _search_string: String = ""
|
||||
|
||||
signal on_collapse_changed()
|
||||
|
||||
func get_results() -> VestResult.Suite:
|
||||
return _results
|
||||
|
||||
func set_results(results: VestResult.Suite) -> void:
|
||||
if _results != results:
|
||||
_results = results
|
||||
_render()
|
||||
|
||||
func get_search_string() -> String:
|
||||
return _search_string
|
||||
|
||||
func set_search_string(search_string: String) -> void:
|
||||
if _search_string != search_string:
|
||||
_search_string = search_string
|
||||
_render()
|
||||
|
||||
func clear() -> void:
|
||||
set_results(null)
|
||||
|
||||
func set_spinner(text: String, icon: Texture2D = null, animated: bool = true) -> void:
|
||||
clear()
|
||||
_spinner_icon.texture = icon
|
||||
_spinner_label.text = text
|
||||
_spinner.show()
|
||||
if animated:
|
||||
_animation_player.play("spin")
|
||||
else:
|
||||
_animation_player.stop()
|
||||
|
||||
func collapse() -> void:
|
||||
var root := _tree.get_root()
|
||||
if not root: return
|
||||
|
||||
for item in root.get_children():
|
||||
item.set_collapsed_recursive(true)
|
||||
|
||||
func expand() -> void:
|
||||
var root := _tree.get_root()
|
||||
if not root: return
|
||||
|
||||
for item in root.get_children():
|
||||
item.set_collapsed_recursive(false)
|
||||
|
||||
func toggle_collapsed() -> void:
|
||||
if is_any_collapsed():
|
||||
expand()
|
||||
else:
|
||||
collapse()
|
||||
|
||||
func is_any_collapsed() -> bool:
|
||||
var at := _tree.get_root()
|
||||
var queue := [] as Array[TreeItem]
|
||||
|
||||
while at:
|
||||
queue.append_array(at.get_children())
|
||||
|
||||
if at.collapsed:
|
||||
return true
|
||||
at = queue.pop_back()
|
||||
|
||||
return false
|
||||
|
||||
func _ready():
|
||||
_tree.item_collapsed.connect(func(_item):
|
||||
on_collapse_changed.emit()
|
||||
)
|
||||
|
||||
func _clear():
|
||||
_tree.clear()
|
||||
for connection in _tree.item_activated.get_connections():
|
||||
connection["signal"].disconnect(connection["callable"])
|
||||
_spinner.hide()
|
||||
|
||||
func _render():
|
||||
_clear()
|
||||
if _results != null:
|
||||
_render_results = VestResult.Suite._from_wire(_results._to_wire()) # HACK: Duplicate
|
||||
_filter_visibility(_render_results)
|
||||
_filter_search(_render_results, _search_string)
|
||||
_render_result(_render_results, _tree)
|
||||
|
||||
func _filter_visibility(results: VestResult.Suite) -> void:
|
||||
var cases_to_remove := []
|
||||
var suites_to_remove := []
|
||||
|
||||
for case in results.cases:
|
||||
if not _check_visibility(case):
|
||||
cases_to_remove.append(case)
|
||||
|
||||
for subsuite in results.subsuites:
|
||||
if not _check_visibility(subsuite):
|
||||
suites_to_remove.append(subsuite)
|
||||
else:
|
||||
_filter_visibility(subsuite)
|
||||
|
||||
for case in cases_to_remove:
|
||||
results.cases.erase(case)
|
||||
for subsuite in suites_to_remove:
|
||||
results.subsuites.erase(subsuite)
|
||||
|
||||
func _filter_search(results: VestResult.Suite, needle: String) -> void:
|
||||
if not needle:
|
||||
# Search string empty, do nothing
|
||||
return
|
||||
|
||||
var scores := {}
|
||||
var parents := {}
|
||||
var at := results
|
||||
var queue: Array[VestResult.Suite] = []
|
||||
var leaves: Array[VestResult.Suite] = []
|
||||
|
||||
# Calculate scores
|
||||
while at:
|
||||
for subsuite in at.subsuites:
|
||||
queue.append(subsuite)
|
||||
parents[subsuite] = at
|
||||
|
||||
if at.subsuites.is_empty():
|
||||
leaves.append(at)
|
||||
|
||||
scores[at] = VestUI.fuzzy_score(needle, at.suite.name)
|
||||
for case in at.cases:
|
||||
scores[case] = maxf(
|
||||
VestUI.fuzzy_score(needle, case.case.description),
|
||||
VestUI.fuzzy_score(needle, case.case.method_name + "()")
|
||||
)
|
||||
|
||||
at = queue.pop_back() as VestResult.Suite
|
||||
|
||||
# Propagate best scores from leaves
|
||||
for leaf in leaves:
|
||||
at = leaf
|
||||
|
||||
# Calculate best score for leaf
|
||||
var best_score := scores[at] as float
|
||||
for case in at.cases:
|
||||
best_score = maxf(best_score, scores[case])
|
||||
|
||||
# Propagate upwards in tree
|
||||
while at:
|
||||
scores[at] = maxf(scores[at], best_score)
|
||||
best_score = maxf(best_score, scores[at])
|
||||
at = parents.get(at, null)
|
||||
|
||||
# Remove results that don't match the search string
|
||||
at = results
|
||||
queue.clear()
|
||||
while at:
|
||||
at.cases.sort_custom(func(a, b): return scores[a] > scores[b])
|
||||
at.subsuites.sort_custom(func(a, b): return scores[a] > scores[b])
|
||||
|
||||
queue.append_array(at.subsuites)
|
||||
at = queue.pop_back()
|
||||
|
||||
func _check_visibility(what: Variant) -> bool:
|
||||
if what is VestResult.Case:
|
||||
var case := what as VestResult.Case
|
||||
return visibility_popup.get_visibility_for(case.status)
|
||||
elif what is VestResult.Suite:
|
||||
var suite := what as VestResult.Suite
|
||||
for status in suite.get_unique_statuses():
|
||||
if visibility_popup.get_visibility_for(status):
|
||||
return true
|
||||
return false
|
||||
else:
|
||||
push_warning("Checking visibility for unknown item: %s" % [what])
|
||||
return true
|
||||
|
||||
func _render_result(what: Object, tree: Tree, parent: TreeItem = null):
|
||||
if what is VestResult.Suite:
|
||||
var item := tree.create_item(parent)
|
||||
item.set_text(0, what.suite.name)
|
||||
item.set_text(1, what.get_aggregate_status_string().capitalize())
|
||||
|
||||
item.set_icon(0, VestUI.get_status_icon(what))
|
||||
item.set_icon_max_width(0, VestUI.get_icon_size())
|
||||
|
||||
tree.item_activated.connect(func():
|
||||
if tree.get_selected() == item:
|
||||
_navigate(what.suite.definition_file, what.suite.definition_line)
|
||||
)
|
||||
|
||||
for subsuite in what.subsuites:
|
||||
_render_result(subsuite, tree, item)
|
||||
for case in what.cases:
|
||||
_render_result(case, tree, item)
|
||||
elif what is VestResult.Case:
|
||||
var item := tree.create_item(parent)
|
||||
item.set_text(0, what.case.description)
|
||||
item.set_text(1, what.get_status_string().capitalize())
|
||||
item.collapsed = what.status == VestResult.TEST_PASS
|
||||
|
||||
item.set_icon(0, VestUI.get_status_icon(what))
|
||||
item.set_icon_max_width(0, VestUI.get_icon_size())
|
||||
|
||||
_render_data(what, tree, item)
|
||||
|
||||
tree.item_activated.connect(func():
|
||||
if tree.get_selected() == item:
|
||||
_navigate(what.case.definition_file, what.case.definition_line)
|
||||
)
|
||||
else:
|
||||
push_error("Rendering unknown object: %s" % [what])
|
||||
|
||||
func _render_data(case: VestResult.Case, tree: Tree, parent: TreeItem):
|
||||
var data := case.data.duplicate()
|
||||
|
||||
for message in case.messages:
|
||||
var item := tree.create_item(parent)
|
||||
item.set_text(0, message)
|
||||
|
||||
tree.item_activated.connect(func():
|
||||
if tree.get_selected() == item:
|
||||
# TODO: popup_dialog()
|
||||
add_child(VestMessagePopup.of(message).window)
|
||||
)
|
||||
|
||||
if data == null or data.is_empty():
|
||||
return
|
||||
|
||||
if data.has("messages"):
|
||||
var header_item := tree.create_item(parent)
|
||||
header_item.set_text(0, "Messages")
|
||||
|
||||
for message in data["messages"]:
|
||||
tree.create_item(header_item).set_text(0, message)
|
||||
|
||||
data.erase("messages")
|
||||
|
||||
if data.has("benchmarks"):
|
||||
var header_item := tree.create_item(parent)
|
||||
header_item.set_text(0, "Benchmarks")
|
||||
|
||||
for benchmark in data["benchmarks"]:
|
||||
var benchmark_item = tree.create_item(header_item)
|
||||
benchmark_item.set_text(0, benchmark["name"])
|
||||
if benchmark.has("duration"): benchmark_item.set_text(1, benchmark["duration"])
|
||||
|
||||
for measurement in benchmark.keys():
|
||||
if measurement == "name": continue
|
||||
|
||||
var measurement_item := tree.create_item(benchmark_item)
|
||||
measurement_item.set_text(0, str(measurement).capitalize())
|
||||
measurement_item.set_text(1, str(benchmark[measurement]))
|
||||
|
||||
data.erase("benchmarks")
|
||||
|
||||
if data.has("expect") and data.has("got"):
|
||||
var header_item := tree.create_item(parent)
|
||||
header_item.set_text(0, "Got:")
|
||||
header_item.set_text(1, "Expected:")
|
||||
|
||||
var got_string := JSON.stringify(data["got"], " ")
|
||||
var expect_string := JSON.stringify(data["expect"], " ")
|
||||
|
||||
var comparison_item := tree.create_item(header_item)
|
||||
comparison_item.set_text(0, got_string)
|
||||
comparison_item.set_text(1, expect_string)
|
||||
|
||||
tree.item_activated.connect(func():
|
||||
# TODO: popup_dialog()
|
||||
if tree.get_selected() in [header_item, comparison_item]:
|
||||
add_child(VestComparisonPopup.of(expect_string, got_string).window)
|
||||
)
|
||||
|
||||
data.erase("got")
|
||||
data.erase("expect")
|
||||
|
||||
for key in data:
|
||||
var item := tree.create_item(parent)
|
||||
item.set_text(0, var_to_str(key))
|
||||
item.set_text(1, var_to_str(data[key]))
|
||||
|
||||
func _navigate(file: String, line: int):
|
||||
Vest._get_editor_interface().edit_script(load(file), line)
|
||||
@@ -0,0 +1 @@
|
||||
uid://brlr0bv7le1uq
|
||||
@@ -0,0 +1,76 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://lqudrgj64q3x"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/vest/ui/results-panel.gd" id="1_ujqyc"]
|
||||
[ext_resource type="Texture2D" uid="uid://dwhlyf5eyiect" path="res://addons/vest/icons/debug.svg" id="2_1y2jb"]
|
||||
|
||||
[sub_resource type="Animation" id="Animation_wkibd"]
|
||||
resource_name = "spin"
|
||||
loop_mode = 1
|
||||
step = 0.0416667
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("Spinner Icon:modulate")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"times": PackedFloat32Array(0, 0.5, 1),
|
||||
"transitions": PackedFloat32Array(-2, -2, -2),
|
||||
"update": 0,
|
||||
"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 0.501961), Color(1, 1, 1, 1)]
|
||||
}
|
||||
|
||||
[sub_resource type="AnimationLibrary" id="AnimationLibrary_ne2nn"]
|
||||
_data = {
|
||||
"spin": SubResource("Animation_wkibd")
|
||||
}
|
||||
|
||||
[node name="Results Panel" type="Panel"]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource("1_ujqyc")
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Tree" type="Tree" parent="PanelContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
columns = 2
|
||||
hide_root = true
|
||||
|
||||
[node name="Spinner" type="HBoxContainer" parent="PanelContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
|
||||
[node name="Spinner Icon" type="TextureRect" parent="PanelContainer/Spinner"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
texture = ExtResource("2_1y2jb")
|
||||
expand_mode = 3
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="Spinner Label" type="Label" parent="PanelContainer/Spinner"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Waiting for results..."
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="PanelContainer/Spinner"]
|
||||
autoplay = "spin"
|
||||
libraries = {
|
||||
"": SubResource("AnimationLibrary_ne2nn")
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
extends RefCounted
|
||||
class_name VestComparisonPopup
|
||||
|
||||
var window: Window
|
||||
var expected: TextEdit
|
||||
var got: TextEdit
|
||||
var ok_button: Button
|
||||
var reset_button: Button
|
||||
|
||||
var _expected: String = ""
|
||||
var _got: String = ""
|
||||
|
||||
# For some reason, when instantiating in Godot 4.0.4, the script doesn't attach, even though it's
|
||||
# added in the editor.
|
||||
#
|
||||
# This means that _ready doesn't run, and set_contents isn't recognized.
|
||||
#
|
||||
# The workaround is this weird pattern where the class is instantiated with a reference to the
|
||||
# scriptless scene ( Window ), and takes over.
|
||||
|
||||
# NOTE: This class may be rewritten, as we went from supporting 4.0.4 to 4.1.4
|
||||
|
||||
static func of(p_expected: String, p_got: String):
|
||||
var popup_scene := (load("res://addons/vest/ui/comparison-popup.tscn") as PackedScene).instantiate(PackedScene.GEN_EDIT_STATE_MAIN)
|
||||
var popup := VestComparisonPopup.new(popup_scene)
|
||||
|
||||
popup.set_contents(p_expected, p_got)
|
||||
|
||||
return popup
|
||||
|
||||
func set_contents(p_expected: String, p_got: String):
|
||||
_expected = p_expected
|
||||
_got = p_got
|
||||
|
||||
reset()
|
||||
|
||||
func reset():
|
||||
expected.text = _expected
|
||||
got.text = _got
|
||||
|
||||
func close():
|
||||
window.queue_free()
|
||||
free()
|
||||
|
||||
func _init(p_window: Window):
|
||||
p_window.title = "vest - Comparison"
|
||||
p_window.position = (DisplayServer.screen_get_size(-1) - p_window.size) / 2
|
||||
|
||||
window = p_window
|
||||
ok_button = window.get_node("%OK Button")
|
||||
reset_button = window.get_node("%Reset Button")
|
||||
expected = window.get_node("%Expected")
|
||||
got = window.get_node("%Got")
|
||||
|
||||
window.close_requested.connect(func(): close())
|
||||
ok_button.pressed.connect(func(): window.queue_free())
|
||||
reset_button.pressed.connect(func(): reset())
|
||||
@@ -0,0 +1 @@
|
||||
uid://dscuovb44m4t6
|
||||
@@ -0,0 +1,51 @@
|
||||
extends RefCounted
|
||||
class_name VestMessagePopup
|
||||
|
||||
var window: Window
|
||||
var contents: TextEdit
|
||||
var ok_button: Button
|
||||
var reset_button: Button
|
||||
|
||||
var _text: String = ""
|
||||
|
||||
# For some reason, when instantiating in Godot 4.0.4, the script doesn't attach, even though it's
|
||||
# added in the editor.
|
||||
#
|
||||
# This means that _ready doesn't run, and set_contents isn't recognized.
|
||||
#
|
||||
# The workaround is this weird pattern where the class is instantiated with a reference to the
|
||||
# scriptless scene ( Window ), and takes over.
|
||||
|
||||
# NOTE: This class may be rewritten, as we went from supporting 4.0.4 to 4.1.4
|
||||
|
||||
static func of(text: String):
|
||||
var popup_scene := (load("res://addons/vest/ui/message-popup.tscn") as PackedScene).instantiate(PackedScene.GEN_EDIT_STATE_MAIN)
|
||||
var popup := VestMessagePopup.new(popup_scene)
|
||||
|
||||
popup.set_contents(text)
|
||||
|
||||
return popup
|
||||
|
||||
func set_contents(text: String):
|
||||
_text = text
|
||||
contents.text = _text
|
||||
|
||||
func reset():
|
||||
contents.text = _text
|
||||
|
||||
func close():
|
||||
window.queue_free()
|
||||
free()
|
||||
|
||||
func _init(p_window: Window):
|
||||
p_window.title = "vest - Message"
|
||||
p_window.position = (DisplayServer.screen_get_size(-1) - p_window.size) / 2
|
||||
|
||||
window = p_window
|
||||
ok_button = window.get_node("%OK Button")
|
||||
reset_button = window.get_node("%Reset Button")
|
||||
contents = window.get_node("%Contents")
|
||||
|
||||
window.close_requested.connect(func(): close())
|
||||
ok_button.pressed.connect(func(): window.queue_free())
|
||||
reset_button.pressed.connect(func(): reset())
|
||||
@@ -0,0 +1 @@
|
||||
uid://dw33d1tq6738
|
||||
@@ -0,0 +1,225 @@
|
||||
@tool
|
||||
extends Control
|
||||
class_name VestUI
|
||||
|
||||
const VisibilityPopup := preload("res://addons/vest/ui/visibility-popup.gd")
|
||||
const ResultsPanel := preload("res://addons/vest/ui/results-panel.gd")
|
||||
|
||||
@onready var run_all_button := %"Run All Button" as Button
|
||||
@onready var debug_button := %"Debug Button" as Button
|
||||
@onready var run_on_save_button := %"Run on Save Button" as Button
|
||||
@onready var filter_results_button := %"Filter Results Button" as Button
|
||||
@onready var visibility_popup := %"Visibility Popup" as VisibilityPopup
|
||||
@onready var clear_button := %"Clear Button" as Button
|
||||
@onready var expand_toggle_button := %"Expand Toggle Button" as Button
|
||||
@onready var search_button := %"Search Button" as Button
|
||||
@onready var search_input := %"Search Input" as LineEdit
|
||||
|
||||
@onready var refresh_mixins_button := %"Refresh Mixins Button" as Button
|
||||
@onready var results_panel := %"Results Panel" as ResultsPanel
|
||||
@onready var summary_label := %"Tests Summary Label" as Label
|
||||
@onready var summary_icon := %"Test Summary Icon" as TextureRect
|
||||
@onready var glob_line_edit := %"Glob LineEdit" as LineEdit
|
||||
|
||||
@onready var run_summary := %"Run Summary" as Control
|
||||
@onready var progress_indicator := %"Progress Indicator" as Control
|
||||
@onready var progress_animator := $"VBoxContainer/Bottom Line/Progress Indicator/Control/AnimationPlayer" as AnimationPlayer
|
||||
|
||||
var _run_on_save: bool = false
|
||||
var _results: VestResult.Suite = null
|
||||
|
||||
static var _icon_size := 16
|
||||
static var _instance: VestUI
|
||||
|
||||
static func get_icon_size() -> int:
|
||||
return _icon_size
|
||||
|
||||
static func _get_ui() -> VestUI:
|
||||
return _instance
|
||||
|
||||
func handle_resource_saved(resource: Resource):
|
||||
if not resource is Script or not visible:
|
||||
return
|
||||
|
||||
if _run_on_save:
|
||||
run_all()
|
||||
|
||||
func run_all(is_debug: bool = false):
|
||||
Vest._register_scene_tree(get_tree())
|
||||
var runner := VestDaemonRunner.new()
|
||||
runner.on_partial_result.connect(func(results):
|
||||
results_panel.set_results(results)
|
||||
)
|
||||
|
||||
var test_glob := glob_line_edit.text
|
||||
Vest.__.LocalSettings.test_glob = test_glob
|
||||
Vest.__.LocalSettings.flush()
|
||||
|
||||
results_panel.set_spinner("Waiting for results...", Vest.Icons.debug)
|
||||
progress_indicator.show()
|
||||
progress_animator.play("spin")
|
||||
run_summary.hide()
|
||||
|
||||
var test_start := Vest.time()
|
||||
var results: VestResult.Suite
|
||||
if not is_debug:
|
||||
results = await runner.run_glob(glob_line_edit.text)
|
||||
else:
|
||||
results = await runner.with_debug().run_glob(glob_line_edit.text)
|
||||
|
||||
var test_duration := Vest.time() - test_start
|
||||
|
||||
# Render individual results
|
||||
ingest_results(results, test_duration)
|
||||
|
||||
func run_script(script: Script, is_debug: bool = false, only_mode: int = Vest.__.ONLY_AUTO) -> void:
|
||||
if not get_tree():
|
||||
push_warning("UI has no tree!")
|
||||
Vest._register_scene_tree(get_tree())
|
||||
var runner := VestDaemonRunner.new()
|
||||
|
||||
results_panel.set_spinner("Waiting for results...", Vest.Icons.debug)
|
||||
progress_indicator.show()
|
||||
progress_animator.play("spin")
|
||||
run_summary.hide()
|
||||
|
||||
var test_start := Vest.time()
|
||||
var results: VestResult.Suite
|
||||
if not is_debug:
|
||||
results = await runner.run_script(script, only_mode)
|
||||
else:
|
||||
results = await runner.with_debug().run_script(script, only_mode)
|
||||
|
||||
var test_duration := Vest.time() - test_start
|
||||
|
||||
# Render individual results
|
||||
ingest_results(results, test_duration)
|
||||
|
||||
func ingest_results(results: VestResult.Suite, duration: float = -1.) -> void:
|
||||
clear_results()
|
||||
_results = results
|
||||
|
||||
if results:
|
||||
results_panel.set_results(results)
|
||||
_render_summary(results, duration)
|
||||
else:
|
||||
results_panel.set_spinner("Test run failed!", Vest.Icons.result_fail, false)
|
||||
|
||||
func clear_results():
|
||||
results_panel.clear()
|
||||
summary_label.text = ""
|
||||
summary_icon.visible = false
|
||||
|
||||
func _ready():
|
||||
_icon_size = int(16. * Vest._get_editor_scale())
|
||||
results_panel.visibility_popup = visibility_popup
|
||||
|
||||
run_all_button.pressed.connect(run_all)
|
||||
run_on_save_button.toggled.connect(func(toggled):
|
||||
_run_on_save = toggled
|
||||
)
|
||||
clear_button.pressed.connect(clear_results)
|
||||
refresh_mixins_button.pressed.connect(func(): VestMixins.refresh())
|
||||
|
||||
glob_line_edit.text = Vest.__.LocalSettings.test_glob
|
||||
glob_line_edit.text_changed.connect(func(text: String):
|
||||
Vest.__.LocalSettings.test_glob = text
|
||||
)
|
||||
|
||||
debug_button.pressed.connect(func(): run_all(true))
|
||||
|
||||
filter_results_button.pressed.connect(func():
|
||||
visibility_popup.position = filter_results_button.get_screen_position() + Vector2.RIGHT * filter_results_button.size.x
|
||||
visibility_popup.show()
|
||||
)
|
||||
|
||||
visibility_popup.on_change.connect(func():
|
||||
clear_results()
|
||||
results_panel.set_results(_results)
|
||||
|
||||
if visibility_popup.is_empty():
|
||||
filter_results_button.icon = Vest.Icons.hidden
|
||||
else:
|
||||
filter_results_button.icon = Vest.Icons.visible
|
||||
)
|
||||
|
||||
results_panel.on_collapse_changed.connect(func():
|
||||
if (results_panel.is_any_collapsed()):
|
||||
expand_toggle_button.icon = Vest.Icons.expand
|
||||
else:
|
||||
expand_toggle_button.icon = Vest.Icons.collapse
|
||||
)
|
||||
|
||||
expand_toggle_button.pressed.connect(func():
|
||||
results_panel.toggle_collapsed()
|
||||
)
|
||||
|
||||
search_button.pressed.connect(func():
|
||||
search_input.show()
|
||||
search_input.grab_focus()
|
||||
)
|
||||
|
||||
search_input.focus_exited.connect(func():
|
||||
if not search_input.text and get_viewport().gui_get_focus_owner() != search_button:
|
||||
search_input.hide()
|
||||
, CONNECT_DEFERRED)
|
||||
|
||||
search_input.text_changed.connect(func(text: String):
|
||||
results_panel.set_search_string(text)
|
||||
)
|
||||
|
||||
_instance = self
|
||||
|
||||
func _render_summary(results: VestResult.Suite, test_duration: float):
|
||||
progress_indicator.hide()
|
||||
run_summary.show()
|
||||
|
||||
if test_duration > 0:
|
||||
summary_label.text = "Ran %d tests in %s" % [results.size(), VestUI.format_duration(test_duration)]
|
||||
else:
|
||||
summary_label.text = "Ran %d tests" % [results.size()]
|
||||
summary_icon.visible = true
|
||||
summary_icon.texture = VestUI.get_status_icon(results)
|
||||
summary_icon.custom_minimum_size = Vector2i.ONE * VestUI.get_icon_size() # TODO: Check
|
||||
|
||||
static func get_status_icon(what: Variant) -> Texture2D:
|
||||
if what is VestResult.Suite:
|
||||
return get_status_icon(what.get_aggregate_status())
|
||||
elif what is VestResult.Case:
|
||||
if what.data.has("benchmarks"):
|
||||
if what.status == VestResult.TEST_FAIL:
|
||||
return Vest.Icons.benchmark_fail
|
||||
else:
|
||||
return Vest.Icons.benchmark
|
||||
else:
|
||||
return get_status_icon(what.status)
|
||||
elif what is int:
|
||||
match(what):
|
||||
VestResult.TEST_VOID: return Vest.Icons.result_void
|
||||
VestResult.TEST_TODO: return Vest.Icons.result_todo
|
||||
VestResult.TEST_SKIP: return Vest.Icons.result_skip
|
||||
VestResult.TEST_FAIL: return Vest.Icons.result_fail
|
||||
VestResult.TEST_PASS: return Vest.Icons.result_pass
|
||||
return null
|
||||
|
||||
static func format_duration(duration: float) -> String:
|
||||
if duration > 60.:
|
||||
return "%.2fmin" % [duration / 60.]
|
||||
elif duration > 1.:
|
||||
return "%.2fs" % duration
|
||||
elif duration > 0.001:
|
||||
return "%.2fms" % [duration * 1000.]
|
||||
else:
|
||||
return "%.2fµs" % [duration * 1000_000.0]
|
||||
|
||||
static func fuzzy_score(needle: String, haystack: String) -> float:
|
||||
var ineedle := needle.to_lower()
|
||||
var ihaystack := haystack.to_lower()
|
||||
return ineedle.similarity(ihaystack) + float(ineedle.is_subsequence_of(ihaystack))
|
||||
|
||||
static func fuzzy_match(needle: String, haystack: String) -> bool:
|
||||
return fuzzy_score(needle, haystack) > 0.0
|
||||
|
||||
static func fuzzy_sorter(needle: String) -> Callable:
|
||||
return func(a, b):
|
||||
return fuzzy_score(needle, a) < fuzzy_score(needle, b)
|
||||
@@ -0,0 +1 @@
|
||||
uid://i36by1fk3oss
|
||||
@@ -0,0 +1,232 @@
|
||||
[gd_scene load_steps=16 format=3 uid="uid://bp8g7j7774mwi"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/vest/ui/vest-ui.gd" id="1_cct3x"]
|
||||
[ext_resource type="Texture2D" uid="uid://r4y6ihamgino" path="res://addons/vest/icons/run.svg" id="2_q3lni"]
|
||||
[ext_resource type="PackedScene" uid="uid://lqudrgj64q3x" path="res://addons/vest/ui/results-panel.tscn" id="2_r41u0"]
|
||||
[ext_resource type="Texture2D" uid="uid://dwhlyf5eyiect" path="res://addons/vest/icons/debug.svg" id="3_mcbjt"]
|
||||
[ext_resource type="Texture2D" uid="uid://ctdipwc8sklwo" path="res://addons/vest/icons/clear.svg" id="4_rjham"]
|
||||
[ext_resource type="Texture2D" uid="uid://dt75n55vr4kq0" path="res://addons/vest/icons/refresh.svg" id="5_k7hxl"]
|
||||
[ext_resource type="Texture2D" uid="uid://bmqjme87oq3xx" path="res://addons/vest/icons/pass.svg" id="6_mef67"]
|
||||
[ext_resource type="Texture2D" uid="uid://c711af0y1s2ct" path="res://addons/vest/icons/run-save.svg" id="6_wy3v7"]
|
||||
[ext_resource type="Texture2D" uid="uid://bpyua1ic4p47h" path="res://addons/vest/icons/visibility.svg" id="6_xkbs5"]
|
||||
[ext_resource type="Texture2D" uid="uid://sx0233b14cll" path="res://addons/vest/icons/expand.svg" id="9_hg4sy"]
|
||||
[ext_resource type="Texture2D" uid="uid://cl6im4a1uj6nq" path="res://addons/vest/icons/search.svg" id="10_2uogy"]
|
||||
[ext_resource type="Texture2D" uid="uid://csfyamlc15vp0" path="res://addons/vest/icons/spinner.svg" id="10_7lh4k"]
|
||||
[ext_resource type="PackedScene" uid="uid://dbgbcglqh42ya" path="res://addons/vest/ui/visibility-popup.tscn" id="13_72bup"]
|
||||
|
||||
[sub_resource type="Animation" id="Animation_cyqg3"]
|
||||
resource_name = "spin"
|
||||
loop_mode = 1
|
||||
step = 0.0416667
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("Spinner:rotation")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"times": PackedFloat32Array(0, 1),
|
||||
"transitions": PackedFloat32Array(0.5, 1),
|
||||
"update": 0,
|
||||
"values": [0.0, 6.28319]
|
||||
}
|
||||
|
||||
[sub_resource type="AnimationLibrary" id="AnimationLibrary_ed1ow"]
|
||||
_data = {
|
||||
"spin": SubResource("Animation_cyqg3")
|
||||
}
|
||||
|
||||
[node name="Vest UI" type="Control"]
|
||||
custom_minimum_size = Vector2(0, 144)
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_cct3x")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Results Panel" parent="VBoxContainer" instance=ExtResource("2_r41u0")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Bottom Line" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Buttons" type="HBoxContainer" parent="VBoxContainer/Bottom Line"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Run All Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Run tests"
|
||||
text = " "
|
||||
icon = ExtResource("2_q3lni")
|
||||
icon_alignment = 1
|
||||
expand_icon = true
|
||||
|
||||
[node name="Debug Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Debug tests"
|
||||
text = " "
|
||||
icon = ExtResource("3_mcbjt")
|
||||
icon_alignment = 1
|
||||
expand_icon = true
|
||||
|
||||
[node name="Clear Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Clear results"
|
||||
text = " "
|
||||
icon = ExtResource("4_rjham")
|
||||
icon_alignment = 1
|
||||
expand_icon = true
|
||||
|
||||
[node name="Run on Save Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Run on Save"
|
||||
toggle_mode = true
|
||||
text = " "
|
||||
icon = ExtResource("6_wy3v7")
|
||||
icon_alignment = 1
|
||||
expand_icon = true
|
||||
|
||||
[node name="Filter Results Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Filter results"
|
||||
text = " "
|
||||
icon = ExtResource("6_xkbs5")
|
||||
icon_alignment = 1
|
||||
expand_icon = true
|
||||
|
||||
[node name="Visibility Popup" parent="VBoxContainer/Bottom Line/Buttons/Filter Results Button" instance=ExtResource("13_72bup")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
|
||||
[node name="Search Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = " "
|
||||
icon = ExtResource("10_2uogy")
|
||||
icon_alignment = 1
|
||||
expand_icon = true
|
||||
|
||||
[node name="Search Input" type="LineEdit" parent="VBoxContainer/Bottom Line/Buttons"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(192, 0)
|
||||
layout_mode = 2
|
||||
placeholder_text = "Search..."
|
||||
|
||||
[node name="Expand Toggle Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = " "
|
||||
icon = ExtResource("9_hg4sy")
|
||||
icon_alignment = 1
|
||||
expand_icon = true
|
||||
|
||||
[node name="VSeparator3" type="VSeparator" parent="VBoxContainer/Bottom Line"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Run Summary" type="HBoxContainer" parent="VBoxContainer/Bottom Line"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Test Summary Icon" type="TextureRect" parent="VBoxContainer/Bottom Line/Run Summary"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
texture = ExtResource("6_mef67")
|
||||
expand_mode = 5
|
||||
stretch_mode = 4
|
||||
|
||||
[node name="Tests Summary Label" type="Label" parent="VBoxContainer/Bottom Line/Run Summary"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(220, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Progress Indicator" type="HBoxContainer" parent="VBoxContainer/Bottom Line"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Control" type="Control" parent="VBoxContainer/Bottom Line/Progress Indicator"]
|
||||
custom_minimum_size = Vector2(32, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Spinner" type="TextureRect" parent="VBoxContainer/Bottom Line/Progress Indicator/Control"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -16.0
|
||||
offset_top = -15.5
|
||||
offset_right = 16.0
|
||||
offset_bottom = 16.5
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
rotation = 2.9185
|
||||
pivot_offset = Vector2(16, 16)
|
||||
texture = ExtResource("10_7lh4k")
|
||||
expand_mode = 5
|
||||
stretch_mode = 5
|
||||
flip_h = true
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="VBoxContainer/Bottom Line/Progress Indicator/Control"]
|
||||
autoplay = "spin"
|
||||
libraries = {
|
||||
"": SubResource("AnimationLibrary_ed1ow")
|
||||
}
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/Bottom Line/Progress Indicator"]
|
||||
layout_mode = 2
|
||||
text = "Running tests..."
|
||||
|
||||
[node name="VSeparator2" type="VSeparator" parent="VBoxContainer/Bottom Line"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Glob" type="HBoxContainer" parent="VBoxContainer/Bottom Line"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 3.0
|
||||
|
||||
[node name="Glob Label" type="Label" parent="VBoxContainer/Bottom Line/Glob"]
|
||||
layout_mode = 2
|
||||
text = "Test glob:"
|
||||
|
||||
[node name="Glob LineEdit" type="LineEdit" parent="VBoxContainer/Bottom Line/Glob"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "res://tests/*.test.gd"
|
||||
|
||||
[node name="VSeparator4" type="VSeparator" parent="VBoxContainer/Bottom Line"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Refresh Mixins Button" type="Button" parent="VBoxContainer/Bottom Line"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Refresh Mixins"
|
||||
text = " "
|
||||
icon = ExtResource("5_k7hxl")
|
||||
icon_alignment = 1
|
||||
expand_icon = true
|
||||
@@ -0,0 +1,70 @@
|
||||
@tool
|
||||
extends PopupPanel
|
||||
|
||||
@onready var _container := %"Statuses Container" as Control
|
||||
@onready var _toggle_button := %"Toggle Button" as Button
|
||||
|
||||
var _visibilities: Dictionary = {}
|
||||
|
||||
signal on_change()
|
||||
|
||||
func get_visibility_for(status: int) -> bool:
|
||||
return _visibilities.get(status, true)
|
||||
|
||||
func is_empty() -> bool:
|
||||
# Return true if all visibilites are set to false
|
||||
for status in _visibilities:
|
||||
if _visibilities[status]:
|
||||
return false
|
||||
return true
|
||||
|
||||
func _init() -> void:
|
||||
# Default visibility to true
|
||||
for status in range(VestResult.TEST_MAX):
|
||||
_visibilities[status] = true
|
||||
|
||||
func _ready() -> void:
|
||||
_render()
|
||||
_toggle_button.pressed.connect(_toggle_all)
|
||||
|
||||
func _toggle_all() -> void:
|
||||
var visibility := is_empty()
|
||||
for status in _visibilities.keys():
|
||||
_visibilities[status] = visibility
|
||||
_render()
|
||||
on_change.emit()
|
||||
|
||||
func _render() -> void:
|
||||
if _container.get_child_count() != _visibilities.size():
|
||||
# Remove children
|
||||
for child in _container.get_children():
|
||||
child.queue_free()
|
||||
|
||||
for status in _visibilities.keys():
|
||||
var checkbox := CheckBox.new()
|
||||
checkbox.toggle_mode = true
|
||||
checkbox.text = VestResult.get_status_string(status).capitalize()
|
||||
checkbox.icon = VestUI.get_status_icon(status)
|
||||
checkbox.expand_icon = true
|
||||
|
||||
checkbox.pressed.connect(func():
|
||||
_visibilities[status] = not _visibilities[status]
|
||||
_render()
|
||||
on_change.emit()
|
||||
)
|
||||
|
||||
_container.add_child(checkbox)
|
||||
|
||||
# Update checkbox statuses
|
||||
for idx in range(_container.get_child_count()):
|
||||
var status := _visibilities.keys()[idx] as int
|
||||
var checkbox := _container.get_child(idx) as CheckBox
|
||||
checkbox.set_pressed_no_signal(_visibilities[status])
|
||||
|
||||
# Update toggle button
|
||||
if is_empty():
|
||||
_toggle_button.text = "All"
|
||||
_toggle_button.icon = Vest.Icons.visible
|
||||
else:
|
||||
_toggle_button.text = "None"
|
||||
_toggle_button.icon = Vest.Icons.hidden
|
||||
@@ -0,0 +1 @@
|
||||
uid://hnnddmijt1mg
|
||||
@@ -0,0 +1,31 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://dbgbcglqh42ya"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/vest/ui/visibility-popup.gd" id="1_21m7j"]
|
||||
[ext_resource type="Texture2D" uid="uid://buuq6qptg3vll" path="res://addons/vest/icons/hidden.svg" id="2_urx7m"]
|
||||
|
||||
[sub_resource type="Theme" id="Theme_l01ra"]
|
||||
Button/colors/icon_normal_color = Color(1, 1, 1, 0.501961)
|
||||
Button/colors/icon_pressed_color = Color(1, 1, 1, 1)
|
||||
|
||||
[node name="Visibility Popup" type="PopupPanel"]
|
||||
size = Vector2i(128, 214)
|
||||
visible = true
|
||||
theme = SubResource("Theme_l01ra")
|
||||
script = ExtResource("1_21m7j")
|
||||
|
||||
[node name="Buttons Container" type="VBoxContainer" parent="."]
|
||||
offset_left = 4.0
|
||||
offset_top = 4.0
|
||||
offset_right = 124.0
|
||||
offset_bottom = 210.0
|
||||
|
||||
[node name="Statuses Container" type="VBoxContainer" parent="Buttons Container"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Toggle Button" type="Button" parent="Buttons Container"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "None"
|
||||
icon = ExtResource("2_urx7m")
|
||||
expand_icon = true
|
||||
Reference in New Issue
Block a user