Added footsteps, new tree, various other tweaks

This commit is contained in:
derek
2024-12-05 11:47:34 -06:00
parent 816ae85938
commit 023879ea9f
389 changed files with 20484 additions and 234 deletions

View File

@@ -0,0 +1,15 @@
@tool
extends EditorProperty
var _ui: Control
func _init():
_ui = preload("./ui/stack_panel.tscn").instantiate()
add_child(_ui)
set_bottom_editor(_ui)
func set_node(object) -> void:
_ui.set_node(object)

View File

@@ -0,0 +1,19 @@
@tool
extends EditorInspectorPlugin
const Editor = preload("./editor_property.gd")
const Scatter = preload("../../scatter.gd")
func _can_handle(object):
return is_instance_of(object, Scatter)
func _parse_property(object, type, name, hint_type, hint_string, usage_flags, wide):
if name == "modifier_stack":
var editor_property = Editor.new()
editor_property.set_node(object)
add_property_editor("modifier_stack", editor_property)
return true
return false

View File

@@ -0,0 +1,19 @@
@tool
extends Button
@onready var _popup: PopupPanel = $ModifiersPopup
func _ready() -> void:
_popup.popup_hide.connect(_on_popup_closed)
func _toggled(button_pressed):
if button_pressed:
_popup.position = global_position + Vector2(0.0, size.y)
_popup.popup()
func _on_popup_closed() -> void:
button_pressed = false

View File

@@ -0,0 +1,59 @@
@tool
extends Control
signal value_changed
var _scatter
var _previous
var _locked := false
func set_scatter(scatter_node) -> void:
_scatter = scatter_node
func set_parameter_name(_text: String) -> void:
pass
func set_hint_string(_hint: String) -> void:
pass
func set_value(val) -> void:
_locked = true
_set_value(val)
_previous = get_value()
_locked = false
func get_value():
pass
func get_editor_theme() -> Theme:
if not _scatter:
return ThemeDB.get_default_theme()
var editor_interface: Variant
if Engine.get_version_info().minor >= 2:
editor_interface = EditorInterface
return editor_interface.get_editor_theme()
else:
editor_interface = _scatter.editor_plugin.get_editor_interface()
return editor_interface.get_base_control().get_theme()
func _set_value(_val):
pass
func _on_value_changed(_val) -> void:
if not _locked:
var value = get_value()
if value != _previous:
value_changed.emit(value, _previous)
_previous = value

View File

@@ -0,0 +1,51 @@
[gd_scene load_steps=4 format=3 uid="uid://cf4lrr5tnlwnw"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lylt6"]
content_margin_left = 0.0
content_margin_top = 0.0
content_margin_right = 0.0
content_margin_bottom = 0.0
bg_color = Color(1, 1, 1, 0.54902)
corner_radius_top_left = 2
corner_radius_top_right = 2
corner_radius_bottom_right = 2
corner_radius_bottom_left = 2
corner_detail = 6
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_8hejw"]
content_margin_left = 0.0
content_margin_top = 0.0
content_margin_right = 0.0
content_margin_bottom = 0.0
bg_color = Color(1, 1, 1, 0.784314)
corner_radius_top_left = 2
corner_radius_top_right = 2
corner_radius_bottom_right = 2
corner_radius_bottom_left = 2
corner_detail = 6
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dmtgy"]
content_margin_left = 0.0
content_margin_top = 0.0
content_margin_right = 0.0
content_margin_bottom = 0.0
bg_color = Color(1, 1, 1, 1)
corner_radius_top_left = 2
corner_radius_top_right = 2
corner_radius_bottom_right = 2
corner_radius_bottom_left = 2
corner_detail = 6
[node name="Button" type="Button"]
custom_minimum_size = Vector2(20, 20)
size_flags_horizontal = 3
focus_mode = 0
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_pressed_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 12
theme_override_styles/normal = SubResource("StyleBoxFlat_lylt6")
theme_override_styles/hover = SubResource("StyleBoxFlat_8hejw")
theme_override_styles/pressed = SubResource("StyleBoxFlat_dmtgy")
toggle_mode = true
text = "00"

View File

@@ -0,0 +1,347 @@
# warning-ignore-all:return_value_discarded
@tool
extends Control
signal curve_updated
@export var grid_color := Color(1, 1, 1, 0.2)
@export var grid_color_sub := Color(1, 1, 1, 0.1)
@export var curve_color := Color(1, 1, 1, 0.9)
@export var point_color := Color.WHITE
@export var selected_point_color := Color.ORANGE
@export var point_radius := 4.0
@export var text_color := Color(0.9, 0.9, 0.9)
@export var columns := 4
@export var rows := 2
@export var dynamic_row_count := true
var curve: Curve
var gt: Transform2D
var _hover_point := -1:
set(val):
if val != _hover_point:
_hover_point = val
queue_redraw()
var _selected_point := -1:
set(val):
if val != _selected_point:
_selected_point = val
queue_redraw()
var _selected_tangent := -1:
set(val):
if val != _selected_tangent:
_selected_tangent = val
queue_redraw()
var _dragging := false
var _hover_radius := 50.0 # Squared
var _tangents_length := 30.0
var _font: Font
func _ready() -> void:
#rect_min_size.y *= EditorUtil.get_editor_scale()
var plugin := EditorPlugin.new()
var editor_theme := plugin.get_editor_interface().get_base_control().get_theme()
if editor_theme:
_font = editor_theme.get_font("Main", "EditorFonts")
else:
_font = ThemeDB.fallback_font
plugin.queue_free()
queue_redraw()
connect("resized", _on_resized)
func set_curve(c: Curve) -> void:
curve = c
queue_redraw()
func get_curve() -> Curve:
return curve
func _gui_input(event) -> void:
if event is InputEventKey:
if _selected_point != -1 and event.scancode == KEY_DELETE:
remove_point(_selected_point)
elif event is InputEventMouseButton:
if event.double_click:
add_point(_to_curve_space(event.position))
elif event.pressed and event.button_index == MOUSE_BUTTON_MIDDLE:
var i = get_point_at(event.position)
if i != -1:
remove_point(i)
elif event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
set_selected_tangent(get_tangent_at(event.position))
if _selected_tangent == -1:
set_selected_point(get_point_at(event.position))
if _selected_point != -1:
_dragging = true
elif _dragging and not event.pressed:
_dragging = false
emit_signal("curve_updated")
elif event is InputEventMouseMotion:
if _dragging:
var curve_amplitude: float = curve.get_max_value() - curve.get_min_value()
# Snap to "round" coordinates when holding Ctrl.
# Be more precise when holding Shift as well.
var snap_threshold: float
if event.ctrl_pressed:
snap_threshold = 0.025 if event.shift else 0.1
else:
snap_threshold = 0.0
if _selected_tangent == -1: # Drag point
var point_pos: Vector2 = _to_curve_space(event.position).snapped(Vector2(snap_threshold, snap_threshold * curve_amplitude))
# The index may change if the point is dragged across another one
var i: int = curve.set_point_offset(_selected_point, point_pos.x)
set_hover(i)
set_selected_point(i)
# This is to prevent the user from losing a point out of view.
if point_pos.y < curve.get_min_value():
point_pos.y = curve.get_min_value()
elif point_pos.y > curve.get_max_value():
point_pos.y = curve.get_max_value()
curve.set_point_value(_selected_point, point_pos.y)
else: # Drag tangent
var point_pos: Vector2 = curve.get_point_position(_selected_point)
var control_pos: Vector2 = _to_curve_space(event.position).snapped(Vector2(snap_threshold, snap_threshold * curve_amplitude))
var dir: Vector2 = (control_pos - point_pos).normalized()
var tangent: float
if not is_zero_approx(dir.x):
tangent = dir.y / dir.x
else:
tangent = 1 if dir.y >= 0 else -1
tangent *= 9999
var link: bool = not Input.is_key_pressed(KEY_SHIFT)
if _selected_tangent == 0:
curve.set_point_left_tangent(_selected_point, tangent)
# Note: if a tangent is set to linear, it shouldn't be linked to the other
if link and _selected_point != (curve.get_point_count() - 1) and curve.get_point_right_mode(_selected_point) != Curve.TANGENT_LINEAR:
curve.set_point_right_tangent(_selected_point, tangent)
else:
curve.set_point_right_tangent(_selected_point, tangent)
if link and _selected_point != 0 and curve.get_point_left_mode(_selected_point) != Curve.TANGENT_LINEAR:
curve.set_point_left_tangent(_selected_point, tangent)
queue_redraw()
else:
set_hover(get_point_at(event.position))
func add_point(pos: Vector2) -> void:
if not curve:
return
pos.y = clamp(pos.y, 0.0, 1.0)
curve.add_point(pos)
queue_redraw()
emit_signal("curve_updated")
func remove_point(idx: int) -> void:
if not curve:
return
if idx == _selected_point:
set_selected_point(-1)
if idx == _hover_point:
set_hover(-1)
curve.remove_point(idx)
queue_redraw()
emit_signal("curve_updated")
func get_point_at(pos: Vector2) -> int:
if not curve:
return -1
for i in curve.get_point_count():
var p := _to_view_space(curve.get_point_position(i))
if p.distance_squared_to(pos) <= _hover_radius:
return i
return -1
func get_tangent_at(pos: Vector2) -> int:
if not curve or _selected_point < 0:
return -1
if _selected_point != 0:
var control_pos: Vector2 = _get_tangent_view_pos(_selected_point, 0)
if control_pos.distance_squared_to(pos) < _hover_radius:
return 0
if _selected_point != curve.get_point_count() - 1:
var control_pos = _get_tangent_view_pos(_selected_point, 1)
if control_pos.distance_squared_to(pos) < _hover_radius:
return 1
return -1
func _draw() -> void:
if not curve:
return
var text_height = _font.get_height()
var min_outer := Vector2(0, size.y)
var max_outer := Vector2(size.x, 0)
var min_inner := Vector2(text_height, size.y - text_height)
var max_inner := Vector2(size.x - text_height, text_height)
var width: float = max_inner.x - min_inner.x
var height: float = max_inner.y - min_inner.y
var curve_min: float = curve.get_min_value()
var curve_max: float = curve.get_max_value()
# Main area
draw_line(Vector2(0, max_inner.y), Vector2(max_outer.x, max_inner.y), grid_color)
draw_line(Vector2(0, min_inner.y), Vector2(max_outer.x, min_inner.y), grid_color)
draw_line(Vector2(min_inner.x, max_outer.y), Vector2(min_inner.x, min_outer.y), grid_color)
draw_line(Vector2(max_inner.x, max_outer.y), Vector2(max_inner.x, min_outer.y), grid_color)
# Grid and scale
## Vertical lines
var x_offset = 1.0 / columns
var margin = 4
for i in columns + 1:
var x = width * (i * x_offset) + min_inner.x
draw_line(Vector2(x, max_outer.y), Vector2(x, min_outer.y), grid_color_sub)
draw_string(_font, Vector2(x + margin, min_outer.y - margin), str(snapped(i * x_offset, 0.01)), 0, -1, -1, text_color)
## Horizontal lines
var y_offset = 1.0 / rows
for i in rows + 1:
var y = height * (i * y_offset) + min_inner.y
draw_line(Vector2(min_outer.x, y), Vector2(max_outer.x, y), grid_color_sub)
var y_value = i * ((curve_max - curve_min) / rows) + curve_min
draw_string(_font, Vector2(min_inner.x + margin, y - margin), str(snapped(y_value, 0.01)), 0, -1, -1, text_color)
# Plot curve
var steps = 100
var offset = 1.0 / steps
x_offset = width / steps
var a: float
var a_y: float
var b: float
var b_y: float
a = curve.sample_baked(0.0)
a_y = remap(a, curve_min, curve_max, min_inner.y, max_inner.y)
for i in steps - 1:
b = curve.sample_baked((i + 1) * offset)
b_y = remap(b, curve_min, curve_max, min_inner.y, max_inner.y)
draw_line(Vector2(min_inner.x + x_offset * i, a_y), Vector2(min_inner.x + x_offset * (i + 1), b_y), curve_color)
a_y = b_y
# Draw points
for i in curve.get_point_count():
var pos: Vector2 = _to_view_space(curve.get_point_position(i))
if _selected_point == i:
draw_circle(pos, point_radius, selected_point_color)
else:
draw_circle(pos, point_radius, point_color);
if _hover_point == i:
draw_arc(pos, point_radius + 4.0, 0.0, 2 * PI, 12, point_color, 1.0, true)
# Draw tangents
if _selected_point >= 0:
var i: int = _selected_point
var pos: Vector2 = _to_view_space(curve.get_point_position(i))
if i != 0:
var control_pos: Vector2 = _get_tangent_view_pos(i, 0)
draw_line(pos, control_pos, selected_point_color)
draw_rect(Rect2(control_pos, Vector2(1, 1)).grow(2), selected_point_color)
if i != curve.get_point_count() - 1:
var control_pos: Vector2 = _get_tangent_view_pos(i, 1)
draw_line(pos, control_pos, selected_point_color)
draw_rect(Rect2(control_pos, Vector2(1, 1)).grow(2), selected_point_color)
func _to_view_space(pos: Vector2) -> Vector2:
var h = _font.get_height()
pos.x = remap(pos.x, 0.0, 1.0, h, size.x - h)
pos.y = remap(pos.y, curve.get_min_value(), curve.get_max_value(), size.y - h, h)
return pos
func _to_curve_space(pos: Vector2) -> Vector2:
var h = _font.get_height()
pos.x = remap(pos.x, h, size.x - h, 0.0, 1.0)
pos.y = remap(pos.y, size.y - h, h, curve.get_min_value(), curve.get_max_value())
return pos
func _get_tangent_view_pos(i: int, tangent: int) -> Vector2:
var dir: Vector2
if tangent == 0:
dir = -Vector2(1.0, curve.get_point_left_tangent(i))
else:
dir = Vector2(1.0, curve.get_point_right_tangent(i))
var point_pos = _to_view_space(curve.get_point_position(i))
var control_pos = _to_view_space(curve.get_point_position(i) + dir)
return point_pos + _tangents_length * (control_pos - point_pos).normalized()
func set_hover(val: int) -> void:
if val != _hover_point:
_hover_point = val
queue_redraw()
func set_selected_point(val: int) -> void:
if val != _selected_point:
_selected_point = val
queue_redraw()
func set_selected_tangent(val: int) -> void:
if val != _selected_tangent:
_selected_tangent = val
queue_redraw()
func _on_resized() -> void:
if dynamic_row_count:
rows = (int(size.y / custom_minimum_size.y) + 1) * 2

View File

@@ -0,0 +1,23 @@
@tool
extends "../base_parameter.gd"
var _button
func _ready() -> void:
_button = get_node("Button")
_button.toggled.connect(_on_value_changed)
func enable(enabled: bool) -> void:
_button.disabled = not enabled
_button.flat = not enabled
func get_value() -> bool:
return _button.button_pressed
func _set_value(val: bool) -> void:
_button.button_pressed = val

View File

@@ -0,0 +1,19 @@
[gd_scene load_steps=3 format=3 uid="uid://w6ycb4oveqhd"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/components/header/parameter_button.gd" id="1_f6puy"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/toggle_button.gd" id="2_167vc"]
[node name="MarginContainer" type="MarginContainer"]
offset_right = 40.0
offset_bottom = 40.0
size_flags_horizontal = 4
size_flags_vertical = 4
script = ExtResource( "1_f6puy" )
[node name="Button" type="Button" parent="."]
offset_right = 40.0
offset_bottom = 40.0
focus_mode = 0
toggle_mode = true
icon_alignment = 1
script = ExtResource( "2_167vc" )

View File

@@ -0,0 +1,17 @@
@tool
extends "../base_parameter.gd"
@onready var _spinbox = $SpinBox
func _ready() -> void:
_spinbox.value_changed.connect(_on_value_changed)
func get_value() -> int:
return int(_spinbox.get_value())
func _set_value(val: int) -> void:
_spinbox.set_value(val)

View File

@@ -0,0 +1,17 @@
[gd_scene load_steps=2 format=3 uid="uid://c36gqn03pvlnr"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/components/header/parameter_spinbox.gd" id="1_f0oq6"]
[node name="ParameterSpinbox" type="MarginContainer"]
offset_right = 83.0625
offset_bottom = 31.0
size_flags_horizontal = 4
size_flags_vertical = 4
script = ExtResource( "1_f0oq6" )
[node name="SpinBox" type="SpinBox" parent="."]
offset_right = 83.0
offset_bottom = 31.0
min_value = -100.0
allow_greater = true
allow_lesser = true

View File

@@ -0,0 +1,131 @@
@tool
extends "base_parameter.gd"
@onready var _label: Label = $Label
@onready var _grid_1: Control = $%GridContainer1
@onready var _grid_2: Control = $%GridContainer2
@onready var _grid_3: Control = $%GridContainer3
@onready var _grid_4: Control = $%GridContainer4
@onready var _menu_button: MenuButton = $%MenuButton
var _buttons: Array[Button]
var _popup: PopupMenu
var _layer_count := 32
func _ready() -> void:
_buttons = []
var grids = [_grid_1, _grid_2, _grid_3, _grid_4]
for g in grids:
for c in g.get_children():
if c is Button:
var layer_number = c.text.to_int()
if layer_number > _layer_count:
c.visible = false
continue
_buttons.push_front(c)
c.focus_mode = Control.FOCUS_NONE
c.pressed.connect(_on_button_pressed)
_popup = _menu_button.get_popup()
_popup.clear()
var layer_name := ""
for i in _layer_count:
if i != 0 and i % 4 == 0:
_popup.add_separator("", 100 + i)
layer_name = ProjectSettings.get_setting("layer_names/3d_physics/layer_" + str(i + 1))
if layer_name.is_empty():
layer_name = "Layer " + str(i + 1)
_popup.add_check_item(layer_name, _layer_count - 1 - i)
_sync_popup_state()
_popup.id_pressed.connect(_on_id_pressed)
func set_parameter_name(text: String) -> void:
_label.text = text
func _set_value(val: int) -> void:
var binary_string: String = _dec2bin(val)
var length = binary_string.length()
if length < _layer_count:
binary_string = binary_string.pad_zeros(_layer_count)
elif length > _layer_count:
binary_string = binary_string.substr(length - _layer_count, length)
for i in _layer_count:
_buttons[i].button_pressed = binary_string[i] == "1"
_sync_popup_state()
func get_value() -> int:
var binary_string = ""
for b in _buttons:
binary_string += "1" if b.button_pressed else "0"
var val = _bin2dec(binary_string)
return val
func _dec2bin(value: int) -> String:
if value == 0:
return "0"
var binary_string = ""
while value != 0:
var m = value % 2
binary_string = str(m) + binary_string
# warning-ignore:integer_division
value = value / 2
return binary_string
func _bin2dec(binary_string: String) -> int:
var decimal_value = 0
var count = binary_string.length() - 1
for i in binary_string.length():
decimal_value += pow(2, count) * binary_string[i].to_int()
count -= 1
return decimal_value
func _sync_popup_state() -> void:
if not _popup:
return
for i in _layer_count:
var idx = _popup.get_item_index(i)
_popup.set_item_checked(idx, _buttons[i].button_pressed)
func _on_button_pressed() -> void:
_on_value_changed(null)
_sync_popup_state()
func _on_id_pressed(id: int) -> void:
var idx = _popup.get_item_index(id)
var checked = not _popup.is_item_checked(idx)
_buttons[id].button_pressed = checked
_popup.set_item_checked(idx, checked)
_on_button_pressed()
func _on_enable_all_pressed() -> void:
_set_value(4294967295)
_on_value_changed(null)
func _on_clear_pressed() -> void:
_set_value(0)
_on_value_changed(null)

View File

@@ -0,0 +1,335 @@
[gd_scene load_steps=6 format=3 uid="uid://chondv2lhs4pl"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/components/parameter_bitmask.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://cf4lrr5tnlwnw" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/components/bitmask_button.tscn" id="2"]
[ext_resource type="Texture2D" uid="uid://n66mufjib4ds" path="res://addons/proton_scatter/icons/menu.svg" id="3"]
[ext_resource type="Texture2D" uid="uid://bosx22dy64f11" path="res://addons/proton_scatter/icons/clear.svg" id="4"]
[ext_resource type="Texture2D" uid="uid://uytbptu3a34s" path="res://addons/proton_scatter/icons/select_all.svg" id="4_h30jm"]
[node name="parameter_bitmask" type="VBoxContainer"]
anchors_preset = 10
anchor_right = 1.0
offset_bottom = 178.0
script = ExtResource("1")
[node name="Label" type="Label" parent="."]
layout_mode = 2
text = "Parameter name"
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer"]
layout_mode = 2
alignment = 2
[node name="MenuButton" type="MenuButton" parent="MarginContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
icon = ExtResource("3")
item_count = 39
popup/item_0/text = "Layer 1"
popup/item_0/checkable = 1
popup/item_0/id = 31
popup/item_1/text = "Layer 2"
popup/item_1/checkable = 1
popup/item_1/id = 30
popup/item_2/text = "Layer 3"
popup/item_2/checkable = 1
popup/item_2/id = 29
popup/item_3/text = "Layer 4"
popup/item_3/checkable = 1
popup/item_3/id = 28
popup/item_4/text = ""
popup/item_4/id = 104
popup/item_4/separator = true
popup/item_5/text = "Layer 5"
popup/item_5/checkable = 1
popup/item_5/id = 27
popup/item_6/text = "Layer 6"
popup/item_6/checkable = 1
popup/item_6/id = 26
popup/item_7/text = "Layer 7"
popup/item_7/checkable = 1
popup/item_7/id = 25
popup/item_8/text = "Layer 8"
popup/item_8/checkable = 1
popup/item_8/id = 24
popup/item_9/text = ""
popup/item_9/id = 108
popup/item_9/separator = true
popup/item_10/text = "Layer 9"
popup/item_10/checkable = 1
popup/item_10/id = 23
popup/item_11/text = "Layer 10"
popup/item_11/checkable = 1
popup/item_11/id = 22
popup/item_12/text = "Layer 11"
popup/item_12/checkable = 1
popup/item_12/id = 21
popup/item_13/text = "Layer 12"
popup/item_13/checkable = 1
popup/item_13/id = 20
popup/item_14/text = ""
popup/item_14/id = 112
popup/item_14/separator = true
popup/item_15/text = "Layer 13"
popup/item_15/checkable = 1
popup/item_15/id = 19
popup/item_16/text = "Layer 14"
popup/item_16/checkable = 1
popup/item_16/id = 18
popup/item_17/text = "Layer 15"
popup/item_17/checkable = 1
popup/item_17/id = 17
popup/item_18/text = "Layer 16"
popup/item_18/checkable = 1
popup/item_18/id = 16
popup/item_19/text = ""
popup/item_19/id = 116
popup/item_19/separator = true
popup/item_20/text = "Layer 17"
popup/item_20/checkable = 1
popup/item_20/id = 15
popup/item_21/text = "Layer 18"
popup/item_21/checkable = 1
popup/item_21/id = 14
popup/item_22/text = "Layer 19"
popup/item_22/checkable = 1
popup/item_22/id = 13
popup/item_23/text = "Layer 20"
popup/item_23/checkable = 1
popup/item_23/id = 12
popup/item_24/text = ""
popup/item_24/id = 120
popup/item_24/separator = true
popup/item_25/text = "Layer 21"
popup/item_25/checkable = 1
popup/item_25/id = 11
popup/item_26/text = "Layer 22"
popup/item_26/checkable = 1
popup/item_26/id = 10
popup/item_27/text = "Layer 23"
popup/item_27/checkable = 1
popup/item_27/id = 9
popup/item_28/text = "Layer 24"
popup/item_28/checkable = 1
popup/item_28/id = 8
popup/item_29/text = ""
popup/item_29/id = 124
popup/item_29/separator = true
popup/item_30/text = "Layer 25"
popup/item_30/checkable = 1
popup/item_30/id = 7
popup/item_31/text = "Layer 26"
popup/item_31/checkable = 1
popup/item_31/id = 6
popup/item_32/text = "Layer 27"
popup/item_32/checkable = 1
popup/item_32/id = 5
popup/item_33/text = "Layer 28"
popup/item_33/checkable = 1
popup/item_33/id = 4
popup/item_34/text = ""
popup/item_34/id = 128
popup/item_34/separator = true
popup/item_35/text = "Layer 29"
popup/item_35/checkable = 1
popup/item_35/id = 3
popup/item_36/text = "Layer 30"
popup/item_36/checkable = 1
popup/item_36/id = 2
popup/item_37/text = "Layer 31"
popup/item_37/checkable = 1
popup/item_37/id = 1
popup/item_38/text = "Layer 32"
popup/item_38/checkable = 1
popup/item_38/id = 0
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/HBoxContainer"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/HBoxContainer/VBoxContainer"]
layout_mode = 2
[node name="GridContainer1" type="GridContainer" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
columns = 4
[node name="Button1" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/GridContainer1" instance=ExtResource("2")]
layout_mode = 2
text = "1"
[node name="Button2" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/GridContainer1" instance=ExtResource("2")]
layout_mode = 2
text = "2"
[node name="Button3" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/GridContainer1" instance=ExtResource("2")]
layout_mode = 2
text = "3"
[node name="Button4" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/GridContainer1" instance=ExtResource("2")]
layout_mode = 2
text = "4"
[node name="Button5" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/GridContainer1" instance=ExtResource("2")]
layout_mode = 2
text = "5"
[node name="Button6" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/GridContainer1" instance=ExtResource("2")]
layout_mode = 2
text = "6"
[node name="Button7" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/GridContainer1" instance=ExtResource("2")]
layout_mode = 2
text = "7"
[node name="Button8" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/GridContainer1" instance=ExtResource("2")]
layout_mode = 2
text = "8"
[node name="VSeparator" type="VSeparator" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
[node name="GridContainer2" type="GridContainer" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
columns = 4
[node name="Button9" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/GridContainer2" instance=ExtResource("2")]
layout_mode = 2
text = "9"
[node name="Button10" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/GridContainer2" instance=ExtResource("2")]
layout_mode = 2
text = "10"
[node name="Button11" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/GridContainer2" instance=ExtResource("2")]
layout_mode = 2
text = "11"
[node name="Button12" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/GridContainer2" instance=ExtResource("2")]
layout_mode = 2
text = "12"
[node name="Button13" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/GridContainer2" instance=ExtResource("2")]
layout_mode = 2
text = "13"
[node name="Button14" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/GridContainer2" instance=ExtResource("2")]
layout_mode = 2
text = "14"
[node name="Button15" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/GridContainer2" instance=ExtResource("2")]
layout_mode = 2
text = "15"
[node name="Button16" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/GridContainer2" instance=ExtResource("2")]
layout_mode = 2
text = "16"
[node name="HSeparator" type="HSeparator" parent="MarginContainer/HBoxContainer/VBoxContainer"]
layout_mode = 2
[node name="HBoxContainer2" type="HBoxContainer" parent="MarginContainer/HBoxContainer/VBoxContainer"]
layout_mode = 2
[node name="GridContainer3" type="GridContainer" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer2"]
unique_name_in_owner = true
layout_mode = 2
columns = 4
[node name="Button17" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer2/GridContainer3" instance=ExtResource("2")]
layout_mode = 2
text = "17"
[node name="Button18" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer2/GridContainer3" instance=ExtResource("2")]
layout_mode = 2
text = "18"
[node name="Button19" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer2/GridContainer3" instance=ExtResource("2")]
layout_mode = 2
text = "19"
[node name="Button20" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer2/GridContainer3" instance=ExtResource("2")]
layout_mode = 2
text = "20"
[node name="Button21" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer2/GridContainer3" instance=ExtResource("2")]
layout_mode = 2
text = "21"
[node name="Button22" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer2/GridContainer3" instance=ExtResource("2")]
layout_mode = 2
text = "22"
[node name="Button23" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer2/GridContainer3" instance=ExtResource("2")]
layout_mode = 2
text = "23"
[node name="Button24" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer2/GridContainer3" instance=ExtResource("2")]
layout_mode = 2
text = "24"
[node name="VSeparator2" type="VSeparator" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer2"]
layout_mode = 2
[node name="GridContainer4" type="GridContainer" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer2"]
unique_name_in_owner = true
layout_mode = 2
columns = 4
[node name="Button25" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer2/GridContainer4" instance=ExtResource("2")]
layout_mode = 2
text = "25"
[node name="Button26" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer2/GridContainer4" instance=ExtResource("2")]
layout_mode = 2
text = "26"
[node name="Button27" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer2/GridContainer4" instance=ExtResource("2")]
layout_mode = 2
text = "27"
[node name="Button28" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer2/GridContainer4" instance=ExtResource("2")]
layout_mode = 2
text = "28"
[node name="Button29" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer2/GridContainer4" instance=ExtResource("2")]
layout_mode = 2
text = "29"
[node name="Button30" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer2/GridContainer4" instance=ExtResource("2")]
layout_mode = 2
text = "30"
[node name="Button31" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer2/GridContainer4" instance=ExtResource("2")]
layout_mode = 2
text = "31"
[node name="Button32" parent="MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer2/GridContainer4" instance=ExtResource("2")]
layout_mode = 2
text = "32"
[node name="VBoxContainer2" type="VBoxContainer" parent="MarginContainer/HBoxContainer"]
layout_mode = 2
alignment = 1
[node name="EnableAll" type="Button" parent="MarginContainer/HBoxContainer/VBoxContainer2"]
layout_mode = 2
size_flags_vertical = 3
focus_mode = 0
icon = ExtResource("4_h30jm")
flat = true
expand_icon = true
[node name="ClearButton" type="Button" parent="MarginContainer/HBoxContainer/VBoxContainer2"]
layout_mode = 2
size_flags_vertical = 3
focus_mode = 0
icon = ExtResource("4")
flat = true
[connection signal="pressed" from="MarginContainer/HBoxContainer/VBoxContainer2/EnableAll" to="." method="_on_enable_all_pressed"]
[connection signal="pressed" from="MarginContainer/HBoxContainer/VBoxContainer2/ClearButton" to="." method="_on_clear_pressed"]

View File

@@ -0,0 +1,23 @@
@tool
extends "base_parameter.gd"
@onready var _label: Label = $Label
@onready var _check_box: CheckBox = $CheckBox
func _ready() -> void:
# warning-ignore:return_value_discarded
_check_box.connect("toggled", _on_value_changed)
func set_parameter_name(text: String) -> void:
_label.text = text
func get_value() -> bool:
return _check_box.button_pressed
func _set_value(val: bool) -> void:
_check_box.button_pressed = val

View File

@@ -0,0 +1,21 @@
[gd_scene load_steps=2 format=3 uid="uid://10wqs13p5i3d"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/components/parameter_bool.gd" id="1"]
[node name="ParameterScalar" type="HBoxContainer"]
anchor_right = 1.0
script = ExtResource( "1" )
[node name="Label" type="Label" parent="."]
offset_top = 2.0
offset_right = 996.0
offset_bottom = 28.0
size_flags_horizontal = 3
text = "Parameter name"
[node name="CheckBox" type="CheckBox" parent="."]
offset_left = 1000.0
offset_right = 1024.0
offset_bottom = 31.0
focus_mode = 0
mouse_filter = 1

View File

@@ -0,0 +1,25 @@
@tool
extends "base_parameter.gd"
const Util = preload("../../../../../common/util.gd")
@onready var _label: Label = $Label
@onready var _panel: Control = $MarginContainer/CurvePanel
func set_parameter_name(text: String) -> void:
_label.text = text
func get_value() -> Curve:
return _panel.get_curve()
func _set_value(val: Curve) -> void:
_panel.set_curve(val)
func _on_curve_updated() -> void:
_on_value_changed(get_value())

View File

@@ -0,0 +1,26 @@
[gd_scene load_steps=3 format=3 uid="uid://dqjwibwhdmgsb"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/components/parameter_curve.gd" id="1"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/components/curve_panel.gd" id="2"]
[node name="ParameterCurve" type="VBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("1")
[node name="Label" type="Label" parent="."]
layout_mode = 2
text = "Curve name"
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
[node name="CurvePanel" type="PanelContainer" parent="MarginContainer"]
custom_minimum_size = Vector2(0, 100)
layout_mode = 2
script = ExtResource("2")
selected_point_color = Color(0.878431, 0.47451, 0, 1)
rows = 4
[connection signal="curve_updated" from="MarginContainer/CurvePanel" to="." method="_on_curve_updated"]

View File

@@ -0,0 +1,54 @@
@tool
extends "base_parameter.gd"
@onready var _label: Label = $%Label
@onready var _select_button: Button = $%FileButton
@onready var _dialog: FileDialog = $%FileDialog
@onready var _texture: Button = $%TextureButton
@onready var _preview_root: Control = $%PreviewRoot
var _path := ""
var _is_texture := false
func set_parameter_name(text: String) -> void:
_label.text = text
func set_hint_string(hint: String) -> void:
_is_texture = hint == "Texture"
_set_value(get_value())
func _set_value(val: String) -> void:
_path = val
_select_button.text = val.get_file()
_preview_root.visible = false
if val.is_empty():
_select_button.text = "Select a file"
if _is_texture:
var texture = load(get_value())
if texture is Texture:
_texture.icon = texture
_preview_root.visible = true
func get_value() -> String:
return _path
func _on_clear_button_pressed() -> void:
_set_value("")
_on_value_changed("")
func _on_select_button_pressed() -> void:
_dialog.popup_centered()
func _on_file_selected(file: String) -> void:
_set_value(file)
_on_value_changed(file)

View File

@@ -0,0 +1,67 @@
[gd_scene load_steps=3 format=3 uid="uid://cvgj4rdc0mxxq"]
[ext_resource type="Texture2D" uid="uid://bosx22dy64f11" path="res://addons/proton_scatter/icons/clear.svg" id="1"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/components/parameter_file.gd" id="2"]
[node name="ParameterFile" type="VBoxContainer"]
anchors_preset = 10
anchor_right = 1.0
offset_bottom = 31.0
size_flags_vertical = 0
theme_override_constants/separation = 0
script = ExtResource("2")
[node name="HBoxContainer" type="HBoxContainer" parent="."]
layout_mode = 2
[node name="Label" type="Label" parent="HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "Parameter name"
[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="FileButton" type="Button" parent="HBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "Select a file"
[node name="ClearButton" type="Button" parent="HBoxContainer/HBoxContainer"]
layout_mode = 2
icon = ExtResource("1")
[node name="PreviewRoot" type="HBoxContainer" parent="."]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="Control" type="Control" parent="PreviewRoot"]
layout_mode = 2
size_flags_horizontal = 3
[node name="TextureButton" type="Button" parent="PreviewRoot"]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 100)
layout_mode = 2
flat = true
expand_icon = true
[node name="Control" type="Control" parent="."]
layout_mode = 2
[node name="FileDialog" type="FileDialog" parent="Control"]
unique_name_in_owner = true
title = "Open a File"
size = Vector2i(400, 600)
ok_button_text = "Open"
file_mode = 0
filters = PackedStringArray("*.bmp", "*.dds", "*.exr", "*.hdr", "*.jpg", "*.jpeg", "*.png", "*.tga", "*.svg", "*.svgz", "*.webp")
[connection signal="pressed" from="HBoxContainer/HBoxContainer/FileButton" to="." method="_on_select_button_pressed"]
[connection signal="pressed" from="HBoxContainer/HBoxContainer/ClearButton" to="." method="_on_clear_button_pressed"]
[connection signal="pressed" from="PreviewRoot/TextureButton" to="." method="_on_select_button_pressed"]
[connection signal="file_selected" from="Control/FileDialog" to="." method="_on_file_selected"]

View File

@@ -0,0 +1,88 @@
@tool
extends "base_parameter.gd"
@onready var _label: Label = $%Label
@onready var _select_button: Button = $%SelectButton
@onready var _popup: ConfirmationDialog = $%ConfirmationDialog
@onready var _tree: Tree = $%Tree
var _full_path: NodePath
var _root: Node
var _selected: Node
func set_root(root) -> void:
_root = root
func set_parameter_name(text: String) -> void:
_label.text = text
func _set_value(val) -> void:
if val == null:
return
_full_path = val
if val.is_empty():
return
_select_button.text = val.get_name(val.get_name_count() - 1)
if _root and _root.has_node(val):
_selected = _root.get_node(val)
if val.is_empty():
_select_button.text = "Select a node"
func get_value() -> NodePath:
#if _root and _selected:
# _full_path = String(_root.get_path_to(_selected))
return _full_path
func _populate_tree() -> void:
_tree.clear()
var scene_root: Node = get_tree().get_edited_scene_root()
var editor_theme: Theme = get_editor_theme()
_create_items_recursive(scene_root, null, editor_theme)
func _create_items_recursive(node: Node, parent: TreeItem, editor_theme: Theme) -> void:
if parent and not node.owner:
return # Hidden node.
var node_item = _tree.create_item(parent)
node_item.set_text(0, node.get_name())
node_item.set_meta("node", node)
var node_icon: Texture2D
var node_class := node.get_class()
if is_instance_valid(editor_theme):
if editor_theme.has_icon(node_class, "EditorIcons"):
node_icon = editor_theme.get_icon(node_class, "EditorIcons")
else:
node_icon = editor_theme.get_icon("Node", "EditorIcons")
node_item.set_icon(0, node_icon)
for child in node.get_children():
_create_items_recursive(child, node_item, editor_theme)
func _on_select_button_pressed() -> void:
_populate_tree()
_popup.popup_centered(Vector2i(400, 600))
func _on_clear_button_pressed() -> void:
_select_button.text = "Select a node"
_full_path = NodePath()
func _on_node_selected():
var node = _tree.get_selected().get_meta("node")
_set_value(_root.get_path_to(node))
_on_value_changed(get_value())

View File

@@ -0,0 +1,66 @@
[gd_scene load_steps=3 format=3 uid="uid://bku7i3ct7ftui"]
[ext_resource type="Texture2D" uid="uid://bosx22dy64f11" path="res://addons/proton_scatter/icons/clear.svg" id="1"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/components/parameter_node_selector.gd" id="2"]
[node name="NodeSelector" type="MarginContainer"]
anchors_preset = 10
anchor_right = 1.0
script = ExtResource("2")
[node name="HBoxContainer" type="HBoxContainer" parent="."]
layout_mode = 2
offset_right = 1152.0
offset_bottom = 31.0
[node name="Label" type="Label" parent="HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
offset_top = 2.0
offset_right = 560.0
offset_bottom = 28.0
size_flags_horizontal = 3
text = "Parameter name"
[node name="SelectButton" type="Button" parent="HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
offset_left = 564.0
offset_right = 1124.0
offset_bottom = 31.0
size_flags_horizontal = 3
text = "Select Node"
flat = true
[node name="ClearButton" type="Button" parent="HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
offset_left = 1128.0
offset_right = 1152.0
offset_bottom = 31.0
icon = ExtResource("1")
[node name="ConfirmationDialog" type="ConfirmationDialog" parent="."]
unique_name_in_owner = true
size = Vector2i(400, 500)
[node name="ScrollContainer" type="ScrollContainer" parent="ConfirmationDialog"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 8.0
offset_top = 8.0
offset_right = -960.0
offset_bottom = -597.0
[node name="Tree" type="Tree" parent="ConfirmationDialog/ScrollContainer"]
unique_name_in_owner = true
layout_mode = 2
offset_right = 184.0
offset_bottom = 43.0
size_flags_horizontal = 3
size_flags_vertical = 3
[connection signal="pressed" from="HBoxContainer/SelectButton" to="." method="_on_select_button_pressed"]
[connection signal="pressed" from="HBoxContainer/ClearButton" to="." method="_on_clear_button_pressed"]
[connection signal="confirmed" from="ConfirmationDialog" to="." method="_on_node_selected"]

View File

@@ -0,0 +1,119 @@
# warning-ignore-all:return_value_discarded
@tool
extends "base_parameter.gd"
var _is_int := false
var _is_enum := false
@onready var _label: Label = $Label
@onready var _spinbox: SpinBox = $%SpinBox
@onready var _option: OptionButton = $%OptionButton
func _ready() -> void:
_spinbox.value_changed.connect(_on_value_changed)
_option.item_selected.connect(_on_value_changed)
mark_as_int(_is_int)
func mark_as_int(val: bool) -> void:
_is_int = val
if _is_int and _spinbox:
_spinbox.step = 1
func mark_as_enum(val: bool) -> void:
_is_enum = val
func toggle_option_item(idx: int, value := false) -> void:
_option.set_item_disabled(idx, not value)
func set_parameter_name(text: String) -> void:
_label.text = text
func set_hint_string(hint: String) -> void:
# No hint provided, ignore.
if hint.is_empty():
return
if hint == "float":
_spinbox.step = 0.01
return
if hint == "int":
_spinbox.step = 1
return
# One integer provided
if hint.is_valid_int():
_set_range(0, hint.to_int())
return
# Multiple items provided, check their types
var tokens = hint.split(",")
var all_int = true
var all_float = true
for t in tokens:
if not t.is_valid_int():
all_int = false
if not t.is_valid_float():
all_float = false
# All items are integer
if all_int and tokens.size() >= 2:
_set_range(tokens[0].to_int(), tokens[1].to_int())
return
# All items are float
if all_float:
if tokens.size() >= 2:
_set_range(tokens[0].to_float(), tokens[1].to_float())
if tokens.size() >= 3:
_spinbox.step = tokens[2].to_float()
return
# All items are strings, make it a dropdown
_spinbox.visible = false
_option.visible = true
_is_enum = true
_is_int = true
for i in tokens.size():
_option.add_item(_sanitize_option_name(tokens[i]), i)
set_value(int(_spinbox.get_value()))
func get_value():
if _is_enum:
return _option.get_selected_id()
if _is_int:
return int(_spinbox.get_value())
return _spinbox.get_value()
func _set_value(val) -> void:
if _is_int:
val = int(val)
if _is_enum:
_option.select(val)
else:
_spinbox.set_value(val)
func _set_range(start, end) -> void:
if start < end:
_spinbox.min_value = start
_spinbox.max_value = end
_spinbox.allow_greater = false
_spinbox.allow_lesser = false
func _sanitize_option_name(token: String) -> String:
return token.left(token.find(":"))

View File

@@ -0,0 +1,55 @@
[gd_scene load_steps=2 format=3 uid="uid://bspbhkrpgak0e"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/components/parameter_scalar.gd" id="1"]
[node name="ParameterScalar" type="HBoxContainer"]
anchors_preset = 10
anchor_right = 1.0
script = ExtResource("1")
[node name="Label" type="Label" parent="."]
layout_mode = 2
offset_top = 2.0
offset_right = 1833.0
offset_bottom = 28.0
size_flags_horizontal = 3
text = "Parameter name"
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
offset_left = 1837.0
offset_right = 1920.0
offset_bottom = 31.0
mouse_filter = 2
[node name="Panel" type="Panel" parent="MarginContainer"]
visible = false
layout_mode = 2
offset_right = 83.0
offset_bottom = 31.0
mouse_filter = 2
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer"]
layout_mode = 2
offset_right = 83.0
offset_bottom = 31.0
mouse_filter = 2
[node name="SpinBox" type="SpinBox" parent="MarginContainer/MarginContainer"]
unique_name_in_owner = true
layout_mode = 2
offset_right = 83.0
offset_bottom = 31.0
mouse_filter = 1
min_value = -100.0
step = 0.001
allow_greater = true
allow_lesser = true
[node name="OptionButton" type="OptionButton" parent="MarginContainer/MarginContainer"]
unique_name_in_owner = true
visible = false
layout_mode = 2
offset_right = 83.0
offset_bottom = 31.0
focus_mode = 0

View File

@@ -0,0 +1,27 @@
@tool
extends "base_parameter.gd"
@onready var _label: Label = $Label
@onready var _line_edit: LineEdit = $MarginContainer/MarginContainer/LineEdit
func _ready() -> void:
_line_edit.connect("text_entered", _on_value_changed)
_line_edit.connect("focus_exited", _on_focus_exited)
func set_parameter_name(text: String) -> void:
_label.text = text
func _set_value(val: String) -> void:
_line_edit.text = val
func get_value() -> String:
return _line_edit.get_text()
func _on_focus_exited() -> void:
_on_value_changed(get_value())

View File

@@ -0,0 +1,56 @@
[gd_scene load_steps=4 format=3]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/components/parameter_string.gd" id="1"]
[sub_resource type="StyleBoxFlat" id=1]
bg_color = Color( 0, 0, 0, 0.392157 )
[sub_resource type="StyleBoxFlat" id=2]
bg_color = Color( 0.6, 0.6, 0.6, 0 )
[node name="ParameterString" type="HBoxContainer"]
anchor_right = 1.0
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Label" type="Label" parent="."]
margin_top = 2.0
margin_right = 638.0
margin_bottom = 16.0
size_flags_horizontal = 3
text = "Parameter name"
valign = 1
[node name="MarginContainer" type="MarginContainer" parent="."]
margin_left = 642.0
margin_right = 1280.0
margin_bottom = 18.0
mouse_filter = 2
size_flags_horizontal = 3
[node name="Panel" type="Panel" parent="MarginContainer"]
margin_right = 638.0
margin_bottom = 18.0
mouse_filter = 2
custom_styles/panel = SubResource( 1 )
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer"]
margin_right = 638.0
margin_bottom = 18.0
mouse_filter = 2
custom_constants/margin_right = 4
custom_constants/margin_top = 2
custom_constants/margin_left = 4
custom_constants/margin_bottom = 2
[node name="LineEdit" type="LineEdit" parent="MarginContainer/MarginContainer"]
margin_left = 4.0
margin_top = 2.0
margin_right = 634.0
margin_bottom = 16.0
mouse_filter = 1
custom_styles/focus = SubResource( 2 )
custom_styles/normal = SubResource( 2 )
clear_button_enabled = true

View File

@@ -0,0 +1,47 @@
# warning-ignore-all:return_value_discarded
@tool
extends "base_parameter.gd"
@onready var _label: Label = $Label
@onready var _x: SpinBox = $%X
@onready var _y: SpinBox = $%Y
@onready var _link: Button = $%LinkButton
func _ready() -> void:
_x.value_changed.connect(_on_spinbox_value_changed)
_y.value_changed.connect(_on_spinbox_value_changed)
func set_parameter_name(text: String) -> void:
_label.text = text
func get_value() -> Vector2:
var vec2 = Vector2.ZERO
vec2.x = _x.get_value()
vec2.y = _y.get_value()
return vec2
func _set_value(val: Vector2) -> void:
_x.set_value(val.x)
_y.set_value(val.y)
func _on_clear_pressed():
var old = get_value()
set_value(Vector2.ZERO)
_previous = old
_on_value_changed(Vector2.ZERO)
func _on_spinbox_value_changed(value: float) -> void:
if _link.button_pressed:
var old = get_value()
set_value(Vector2(value, value))
_previous = old
_on_value_changed(get_value())

View File

@@ -0,0 +1,108 @@
[gd_scene load_steps=4 format=3 uid="uid://bjn8ydwp80y7q"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/components/parameter_vector2.gd" id="1"]
[ext_resource type="Texture2D" uid="uid://bosx22dy64f11" path="res://addons/proton_scatter/icons/clear.svg" id="2"]
[ext_resource type="Texture2D" uid="uid://gbrmse47gdxb" path="res://addons/proton_scatter/icons/link.svg" id="3_u2lry"]
[node name="ParameterVector2" type="HBoxContainer"]
anchors_preset = 10
anchor_right = 1.0
script = ExtResource("1")
[node name="Label" type="Label" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 5
text = "Parameter name"
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 0
mouse_filter = 2
[node name="Panel" type="Panel" parent="MarginContainer"]
layout_mode = 2
mouse_filter = 2
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer"]
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 4
mouse_filter = 2
theme_override_constants/margin_left = 6
theme_override_constants/margin_right = 6
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/MarginContainer"]
layout_mode = 2
[node name="GridContainer" type="GridContainer" parent="MarginContainer/MarginContainer/HBoxContainer"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/MarginContainer/HBoxContainer/GridContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="MarginContainer/MarginContainer/HBoxContainer/GridContainer/HBoxContainer"]
modulate = Color(1, 0.447059, 0.368627, 1)
layout_mode = 2
text = "x"
[node name="X" type="SpinBox" parent="MarginContainer/MarginContainer/HBoxContainer/GridContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
mouse_filter = 1
min_value = -100.0
step = 0.001
allow_greater = true
allow_lesser = true
[node name="HBoxContainer2" type="HBoxContainer" parent="MarginContainer/MarginContainer/HBoxContainer/GridContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="MarginContainer/MarginContainer/HBoxContainer/GridContainer/HBoxContainer2"]
modulate = Color(0.564706, 0.992157, 0.298039, 1)
layout_mode = 2
text = "y"
[node name="Y" type="SpinBox" parent="MarginContainer/MarginContainer/HBoxContainer/GridContainer/HBoxContainer2"]
unique_name_in_owner = true
layout_mode = 2
mouse_filter = 1
min_value = -100.0
step = 0.001
allow_greater = true
allow_lesser = true
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/MarginContainer/HBoxContainer"]
layout_mode = 2
[node name="Control" type="Control" parent="MarginContainer/MarginContainer/HBoxContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="ClearButton" type="Button" parent="MarginContainer/MarginContainer/HBoxContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 4
focus_mode = 0
mouse_filter = 1
icon = ExtResource("2")
flat = true
[node name="Control2" type="Control" parent="MarginContainer/MarginContainer/HBoxContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="LinkButton" type="Button" parent="MarginContainer/MarginContainer/HBoxContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 4
focus_mode = 0
mouse_filter = 1
toggle_mode = true
icon = ExtResource("3_u2lry")
flat = true
[node name="Control3" type="Control" parent="MarginContainer/MarginContainer/HBoxContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[connection signal="pressed" from="MarginContainer/MarginContainer/HBoxContainer/VBoxContainer/ClearButton" to="." method="_on_clear_pressed"]

View File

@@ -0,0 +1,49 @@
@tool
extends "base_parameter.gd"
@onready var _label: Label = $Label
@onready var _x: SpinBox = $%X
@onready var _y: SpinBox = $%Y
@onready var _z: SpinBox = $%Z
@onready var _link: Button = $%LinkButton
func _ready() -> void:
_x.value_changed.connect(_on_spinbox_value_changed)
_y.value_changed.connect(_on_spinbox_value_changed)
_z.value_changed.connect(_on_spinbox_value_changed)
func set_parameter_name(text: String) -> void:
_label.text = text
func get_value() -> Vector3:
var vec3 = Vector3.ZERO
vec3.x = _x.get_value()
vec3.y = _y.get_value()
vec3.z = _z.get_value()
return vec3
func _set_value(val: Vector3) -> void:
_x.set_value(val.x)
_y.set_value(val.y)
_z.set_value(val.z)
func _on_clear_pressed():
var old = get_value()
set_value(Vector3.ZERO)
_previous = old
_on_value_changed(Vector3.ZERO)
func _on_spinbox_value_changed(value: float) -> void:
if _link.button_pressed:
var old = get_value()
set_value(Vector3(value, value, value))
_previous = old
_on_value_changed(get_value())

View File

@@ -0,0 +1,127 @@
[gd_scene load_steps=4 format=3 uid="uid://cdpfgf0447ph4"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/components/parameter_vector3.gd" id="1"]
[ext_resource type="Texture2D" uid="uid://bosx22dy64f11" path="res://addons/proton_scatter/icons/clear.svg" id="2"]
[ext_resource type="Texture2D" uid="uid://gbrmse47gdxb" path="res://addons/proton_scatter/icons/link.svg" id="3_gq2ti"]
[node name="ParameterVector3" type="HBoxContainer"]
anchors_preset = 10
anchor_right = 1.0
script = ExtResource("1")
[node name="Label" type="Label" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 5
text = "Parameter name"
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 0
mouse_filter = 2
[node name="Panel" type="Panel" parent="MarginContainer"]
layout_mode = 2
mouse_filter = 2
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer"]
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 4
mouse_filter = 2
theme_override_constants/margin_left = 6
theme_override_constants/margin_right = 6
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/MarginContainer"]
layout_mode = 2
[node name="GridContainer" type="GridContainer" parent="MarginContainer/MarginContainer/HBoxContainer"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/MarginContainer/HBoxContainer/GridContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="MarginContainer/MarginContainer/HBoxContainer/GridContainer/HBoxContainer"]
modulate = Color(1, 0.447059, 0.368627, 1)
layout_mode = 2
text = "x"
[node name="X" type="SpinBox" parent="MarginContainer/MarginContainer/HBoxContainer/GridContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
mouse_filter = 1
min_value = -100.0
step = 0.001
allow_greater = true
allow_lesser = true
[node name="HBoxContainer2" type="HBoxContainer" parent="MarginContainer/MarginContainer/HBoxContainer/GridContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="MarginContainer/MarginContainer/HBoxContainer/GridContainer/HBoxContainer2"]
modulate = Color(0.564706, 0.992157, 0.298039, 1)
layout_mode = 2
text = "y"
[node name="Y" type="SpinBox" parent="MarginContainer/MarginContainer/HBoxContainer/GridContainer/HBoxContainer2"]
unique_name_in_owner = true
layout_mode = 2
mouse_filter = 1
min_value = -100.0
step = 0.001
allow_greater = true
allow_lesser = true
[node name="HBoxContainer3" type="HBoxContainer" parent="MarginContainer/MarginContainer/HBoxContainer/GridContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="MarginContainer/MarginContainer/HBoxContainer/GridContainer/HBoxContainer3"]
modulate = Color(0.14902, 0.8, 1, 1)
layout_mode = 2
text = "z"
[node name="Z" type="SpinBox" parent="MarginContainer/MarginContainer/HBoxContainer/GridContainer/HBoxContainer3"]
unique_name_in_owner = true
layout_mode = 2
mouse_filter = 1
min_value = -100.0
step = 0.001
allow_greater = true
allow_lesser = true
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
alignment = 1
[node name="Control3" type="Control" parent="MarginContainer/MarginContainer/HBoxContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="ClearButton" type="Button" parent="MarginContainer/MarginContainer/HBoxContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 4
focus_mode = 0
mouse_filter = 1
icon = ExtResource("2")
flat = true
[node name="Control" type="Control" parent="MarginContainer/MarginContainer/HBoxContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="LinkButton" type="Button" parent="MarginContainer/MarginContainer/HBoxContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 4
focus_mode = 0
mouse_filter = 1
toggle_mode = true
icon = ExtResource("3_gq2ti")
flat = true
[node name="Control2" type="Control" parent="MarginContainer/MarginContainer/HBoxContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[connection signal="pressed" from="MarginContainer/MarginContainer/HBoxContainer/VBoxContainer/ClearButton" to="." method="_on_clear_pressed"]

View File

@@ -0,0 +1,96 @@
@tool
extends Container
# DragContainer
# Custom containner similar to a VBoxContainer, but the user can rearrange the
# children order via drag and drop. This is only used in the inspector plugin
# for the modifier stack and won't work with arbitrary control nodes.
signal child_moved(last_index: int, new_index: int)
var _separation: int = 0
var _drag_offset = null
var _dragged_child = null
var _old_index: int
var _new_index: int
var _map := [] # Stores the y top position of each child in the stack
func _ready() -> void:
_separation = get_theme_constant("separation", "VBoxContainer")
func _notification(what):
if what == NOTIFICATION_SORT_CHILDREN or what == NOTIFICATION_RESIZED:
_update_layout()
func _can_drop_data(at_position, data) -> bool:
if data.get_parent() != self:
return false
# Drag just started
if not _dragged_child:
_dragged_child = data
_drag_offset = at_position - data.position
_old_index = data.get_index()
_new_index = _old_index
# Dragged control only follow the y mouse position
data.position.y = at_position.y - _drag_offset.y
# Check if the children order should be changed
var computed_index = 0
for pos_y in _map:
if pos_y > data.position.y - 16:
break
computed_index += 1
# Prevents edge case when dragging the last item below its current position
computed_index = clamp(computed_index, 0, get_child_count() - 1)
if computed_index != data.get_index():
move_child(data, computed_index)
_new_index = computed_index
return true
# Called once at the end of the drag
func _drop_data(at_position, data) -> void:
_drag_offset = null
_dragged_child = null
_update_layout()
if _old_index != _new_index:
child_moved.emit(_old_index, _new_index)
# Detects if the user drops the children outside the container and treats it
# as if the drop happened the moment the mouse left the container.
func _unhandled_input(event):
if not _dragged_child:
return
if event is InputEventMouseButton and not event.pressed:
_drop_data(_dragged_child.position, _dragged_child)
func _update_layout() -> void:
_map.clear()
var offset := Vector2.ZERO
for c in get_children():
if c is Control:
_map.push_back(offset.y)
var child_min_size = c.get_combined_minimum_size()
var possible_space = Rect2(offset, Vector2(size.x, child_min_size.y))
if c != _dragged_child:
fit_child_in_rect(c, possible_space)
offset.y += c.size.y + _separation
custom_minimum_size.y = offset.y - _separation

View File

@@ -0,0 +1,203 @@
@tool
extends Control
signal value_changed
signal removed
signal documentation_requested
signal duplication_requested
const ParameterBool := preload("./components/parameter_bool.tscn")
const ParameterScalar := preload("./components/parameter_scalar.tscn")
const ParameterNodeSelector = preload("./components/parameter_node_selector.tscn")
const ParameterFile = preload("./components/parameter_file.tscn")
const ParameterCurve = preload("./components/parameter_curve.tscn")
const ParameterBitmask = preload("./components/parameter_bitmask.tscn")
const ParameterString = preload("./components/parameter_string.tscn")
const ParameterVector3 = preload("./components/parameter_vector3.tscn")
const ParameterVector2 = preload("./components/parameter_vector2.tscn")
const PARAMETER_IGNORE_LIST := [
"enabled",
"override_global_seed",
"custom_seed",
"restrict_height",
"reference_frame",
]
var _scatter
var _modifier
@onready var _parameters: Control = $%ParametersRoot
@onready var _name: Label = $%ModifierName
@onready var _expand: Button = $%Expand
@onready var _enabled: Button = $%Enabled
@onready var _remove: Button = $%Remove
@onready var _warning: Button = $%Warning
@onready var _warning_dialog: AcceptDialog = $WarningDialog
@onready var _drag_control: Control = $%DragControl
@onready var _override_ui = $%OverrideGlobalSeed
@onready var _custom_seed_ui = $%CustomSeed
@onready var _restrict_height_ui = $%RestrictHeight
@onready var _transform_space_ui = $%TransformSpace
func _ready() -> void:
_name.text = name
_enabled.toggled.connect(_on_enable_toggled)
_remove.pressed.connect(_on_remove_pressed)
_warning.pressed.connect(_on_warning_icon_pressed)
_expand.toggled.connect(_on_expand_toggled)
$%MenuButton.get_popup().id_pressed.connect(_on_menu_item_pressed)
func _get_drag_data(at_position: Vector2):
var drag_control_position = _drag_control.global_position - global_position
var drag_rect := Rect2(drag_control_position, _drag_control.size)
if drag_rect.has_point(at_position):
return self
return null
func set_root(val) -> void:
_scatter = val
# Loops through all exposed parameters and create an UI component for each of
# them. For special properties (listed in PARAMATER_IGNORE_LIST), a special
# UI is created.
func create_ui_for(modifier) -> void:
_modifier = modifier
_modifier.warning_changed.connect(_on_warning_changed)
_on_warning_changed()
_name.text = modifier.display_name
_enabled.button_pressed = modifier.enabled
# Enable or disable irrelevant controls for this modifier
_override_ui.enable(modifier.can_override_seed)
_restrict_height_ui.enable(modifier.can_restrict_height)
_transform_space_ui.mark_as_enum(true)
_transform_space_ui.toggle_option_item(0, modifier.global_reference_frame_available)
_transform_space_ui.toggle_option_item(1, modifier.local_reference_frame_available)
_transform_space_ui.toggle_option_item(2, modifier.individual_instances_reference_frame_available)
if not modifier.global_reference_frame_available and \
not modifier.local_reference_frame_available and \
not modifier.individual_instances_reference_frame_available:
_transform_space_ui.visible = false
# Setup header connections
_override_ui.value_changed.connect(_on_parameter_value_changed.bind("override_global_seed", _override_ui))
_custom_seed_ui.value_changed.connect(_on_parameter_value_changed.bind("custom_seed", _custom_seed_ui))
_restrict_height_ui.value_changed.connect(_on_parameter_value_changed.bind("restrict_height", _restrict_height_ui))
_transform_space_ui.value_changed.connect(_on_parameter_value_changed.bind("reference_frame", _transform_space_ui))
# Restore header values
_override_ui.set_value(modifier.override_global_seed)
_custom_seed_ui.set_value(modifier.custom_seed)
_restrict_height_ui.set_value(modifier.restrict_height)
_transform_space_ui.set_value(modifier.reference_frame)
# Loop over the other properties and create a ui component for each of them
for property in modifier.get_property_list():
if property.usage != PROPERTY_USAGE_DEFAULT + PROPERTY_USAGE_SCRIPT_VARIABLE:
continue
if property.name in PARAMETER_IGNORE_LIST:
continue
var parameter_ui
match property.type:
TYPE_BOOL:
parameter_ui = ParameterBool.instantiate()
TYPE_FLOAT:
parameter_ui = ParameterScalar.instantiate()
TYPE_INT:
if property.hint == PROPERTY_HINT_LAYERS_3D_PHYSICS:
parameter_ui = ParameterBitmask.instantiate()
else:
parameter_ui = ParameterScalar.instantiate()
parameter_ui.mark_as_int(true)
TYPE_STRING:
if property.hint_string == "File" or property.hint_string == "Texture":
parameter_ui = ParameterFile.instantiate()
else:
parameter_ui = ParameterString.instantiate()
TYPE_VECTOR3:
parameter_ui = ParameterVector3.instantiate()
TYPE_VECTOR2:
parameter_ui = ParameterVector2.instantiate()
TYPE_NODE_PATH:
parameter_ui = ParameterNodeSelector.instantiate()
parameter_ui.set_root(_scatter)
TYPE_OBJECT:
if property.class_name == &"Curve":
parameter_ui = ParameterCurve.instantiate()
if parameter_ui:
_parameters.add_child(parameter_ui)
parameter_ui.set_parameter_name(property.name.capitalize())
parameter_ui.set_value(modifier.get(property.name))
parameter_ui.set_hint_string(property.hint_string)
parameter_ui.set_scatter(_scatter)
parameter_ui.value_changed.connect(_on_parameter_value_changed.bind(property.name, parameter_ui))
_expand.button_pressed = _modifier.expanded
func _restore_value(name, val, ui) -> void:
_modifier.set(name, val)
ui.set_value(val)
value_changed.emit()
func _on_expand_toggled(toggled: bool) -> void:
$%ParametersContainer.visible = toggled
_modifier.expanded = toggled
func _on_remove_pressed() -> void:
removed.emit()
func _on_parameter_value_changed(value, previous, parameter_name, ui) -> void:
if _scatter.undo_redo:
_scatter.undo_redo.create_action("Change value " + parameter_name.capitalize())
_scatter.undo_redo.add_undo_method(self, "_restore_value", parameter_name, previous, ui)
_scatter.undo_redo.add_do_method(self, "_restore_value", parameter_name, value, ui)
_scatter.undo_redo.commit_action()
else:
_modifier.set(parameter_name, value)
value_changed.emit()
func _on_enable_toggled(pressed: bool):
_modifier.enabled = pressed
value_changed.emit()
func _on_removed_pressed() -> void:
removed.emit()
func _on_warning_changed() -> void:
var warning = _modifier.get_warning()
_warning.visible = (warning != "")
_warning_dialog.dialog_text = warning
func _on_warning_icon_pressed() -> void:
_warning_dialog.popup_centered()
func _on_menu_item_pressed(id) -> void:
match id:
0:
documentation_requested.emit()
2:
duplication_requested.emit()
3:
_on_remove_pressed()
_:
pass

View File

@@ -0,0 +1,255 @@
[gd_scene load_steps=21 format=3 uid="uid://blpobpd0eweog"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/modifier_panel.gd" id="1"]
[ext_resource type="Texture2D" uid="uid://cu2t8yylseggu" path="res://addons/proton_scatter/icons/arrow_right.svg" id="2_2djuo"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/toggle_button.gd" id="4"]
[ext_resource type="Texture2D" uid="uid://t8c6kjbvst0s" path="res://addons/proton_scatter/icons/arrow_down.svg" id="4_7nlfc"]
[ext_resource type="Texture2D" uid="uid://dahwdjl2er75o" path="res://addons/proton_scatter/icons/close.svg" id="5"]
[ext_resource type="Texture2D" uid="uid://n66mufjib4ds" path="res://addons/proton_scatter/icons/menu.svg" id="6_lmo8k"]
[ext_resource type="Texture2D" uid="uid://d2ajwyebaobjt" path="res://addons/proton_scatter/icons/duplicate.svg" id="7_f6nan"]
[ext_resource type="Texture2D" uid="uid://do8d3urxirjoa" path="res://addons/proton_scatter/icons/doc.svg" id="7_owhij"]
[ext_resource type="Texture2D" uid="uid://dj0y6peid681t" path="res://addons/proton_scatter/icons/warning.svg" id="9"]
[ext_resource type="Texture2D" uid="uid://ba6cx70dyeuhg" path="res://addons/proton_scatter/icons/drag_area.svg" id="9_t6pse"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/override_seed_button.gd" id="10_ptukr"]
[ext_resource type="Texture2D" uid="uid://dmmefjvrdhf78" path="res://addons/proton_scatter/icons/dice.svg" id="11_qwhro"]
[ext_resource type="PackedScene" uid="uid://w6ycb4oveqhd" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/components/header/parameter_button.tscn" id="11_y7srw"]
[ext_resource type="Texture2D" uid="uid://cmvfdl1wnrw4" path="res://addons/proton_scatter/icons/restrict_volume.svg" id="12_lx60d"]
[ext_resource type="Texture2D" uid="uid://dt0ctlr32stnn" path="res://addons/proton_scatter/icons/local.svg" id="13_txjs8"]
[ext_resource type="PackedScene" uid="uid://c36gqn03pvlnr" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/components/header/parameter_spinbox.tscn" id="13_vhfch"]
[ext_resource type="Texture2D" uid="uid://p2v2cqm7k60o" path="res://addons/proton_scatter/icons/restrict_volume_lock.svg" id="15_0w0as"]
[ext_resource type="Texture2D" uid="uid://71efqwg3d70v" path="res://addons/proton_scatter/icons/global.svg" id="16_ocvvf"]
[ext_resource type="PackedScene" uid="uid://bspbhkrpgak0e" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/components/parameter_scalar.tscn" id="17_aoulv"]
[ext_resource type="Texture2D" uid="uid://vxd0iun0wq8i" path="res://addons/proton_scatter/icons/individual_instances.svg" id="19_ln8a3"]
[node name="ModifierPanel" type="MarginContainer"]
anchors_preset = 10
anchor_right = 1.0
grow_horizontal = 2
theme_type_variation = &"fg"
script = ExtResource("1")
[node name="Panel" type="Panel" parent="."]
layout_mode = 2
mouse_filter = 2
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
mouse_filter = 2
theme_override_constants/margin_left = 4
theme_override_constants/margin_top = 4
theme_override_constants/margin_right = 4
theme_override_constants/margin_bottom = 4
metadata/_edit_layout_mode = 1
metadata/_edit_use_custom_anchors = false
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 0
[node name="Expand" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Toggle the parameters view"
focus_mode = 0
mouse_filter = 1
toggle_mode = true
icon = ExtResource("2_2djuo")
flat = true
icon_alignment = 1
script = ExtResource("4")
default_icon = ExtResource("2_2djuo")
pressed_icon = ExtResource("4_7nlfc")
[node name="ModifierName" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "ModifierPanel"
vertical_alignment = 1
clip_text = true
[node name="Buttons" type="HBoxContainer" parent="MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 2
alignment = 1
[node name="Warning" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer/Buttons"]
unique_name_in_owner = true
visible = false
layout_mode = 2
size_flags_horizontal = 0
focus_mode = 0
mouse_filter = 1
icon = ExtResource("9")
flat = true
[node name="MenuButton" type="MenuButton" parent="MarginContainer/VBoxContainer/HBoxContainer/Buttons"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Show options"
icon = ExtResource("6_lmo8k")
item_count = 4
popup/item_0/text = "Show documentation"
popup/item_0/icon = ExtResource("7_owhij")
popup/item_0/id = 0
popup/item_1/text = ""
popup/item_1/id = 1
popup/item_1/separator = true
popup/item_2/text = "Duplicate"
popup/item_2/icon = ExtResource("7_f6nan")
popup/item_2/id = 2
popup/item_3/text = "Delete"
popup/item_3/icon = ExtResource("5")
popup/item_3/id = 3
[node name="Enabled" type="CheckBox" parent="MarginContainer/VBoxContainer/HBoxContainer/Buttons"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Toggle the modifier.
If the modifier is disabled, it will not contribute to the final result but will still remain in the stack.
Use this feature to quickly see how the modifier affects the overall stack."
focus_mode = 0
mouse_filter = 1
[node name="Remove" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer/Buttons"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 4
tooltip_text = "Delete the modifier.
This will remove it from the stack."
focus_mode = 0
mouse_filter = 1
icon = ExtResource("5")
flat = true
icon_alignment = 1
[node name="VSeparator" type="VSeparator" parent="MarginContainer/VBoxContainer/HBoxContainer/Buttons"]
modulate = Color(1, 1, 1, 0.54902)
layout_mode = 2
[node name="DragControl" type="TextureRect" parent="MarginContainer/VBoxContainer/HBoxContainer/Buttons"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Drag and move this button to change the stack order.
Modifiers are processed from top to bottom."
mouse_default_cursor_shape = 6
texture = ExtResource("9_t6pse")
stretch_mode = 3
[node name="ParametersContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer"]
unique_name_in_owner = true
visible = false
layout_mode = 2
theme_override_constants/margin_left = 3
theme_override_constants/margin_top = 3
theme_override_constants/margin_right = 3
theme_override_constants/margin_bottom = 3
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/ParametersContainer"]
layout_mode = 2
[node name="ParametersRoot" type="VBoxContainer" parent="MarginContainer/VBoxContainer/ParametersContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
[node name="HSeparator" type="HSeparator" parent="MarginContainer/VBoxContainer/ParametersContainer/VBoxContainer"]
layout_mode = 2
[node name="CommonHeader" type="HBoxContainer" parent="MarginContainer/VBoxContainer/ParametersContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 4
alignment = 1
[node name="ExpandButton" type="MarginContainer" parent="MarginContainer/VBoxContainer/ParametersContainer/VBoxContainer/CommonHeader"]
layout_mode = 2
script = ExtResource("10_ptukr")
[node name="OverrideGlobalSeed" parent="MarginContainer/VBoxContainer/ParametersContainer/VBoxContainer/CommonHeader/ExpandButton" instance=ExtResource("11_y7srw")]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Button" parent="MarginContainer/VBoxContainer/ParametersContainer/VBoxContainer/CommonHeader/ExpandButton/OverrideGlobalSeed" index="0"]
layout_mode = 2
tooltip_text = "Random seed.
Enable to force a custom seed on this modifier only. If this option is disabled, the Global Seed from the ProtonScatter node will be used instead."
icon = ExtResource("11_qwhro")
icon_alignment = 0
default_icon = ExtResource("11_qwhro")
pressed_icon = ExtResource("11_qwhro")
[node name="SpinBoxRoot" type="HBoxContainer" parent="MarginContainer/VBoxContainer/ParametersContainer/VBoxContainer/CommonHeader/ExpandButton"]
visible = false
layout_mode = 2
mouse_filter = 2
[node name="VSeparator" type="VSeparator" parent="MarginContainer/VBoxContainer/ParametersContainer/VBoxContainer/CommonHeader/ExpandButton/SpinBoxRoot"]
modulate = Color(1, 1, 1, 0)
layout_mode = 2
mouse_filter = 2
theme_override_constants/separation = 28
[node name="CustomSeed" parent="MarginContainer/VBoxContainer/ParametersContainer/VBoxContainer/CommonHeader/ExpandButton/SpinBoxRoot" instance=ExtResource("13_vhfch")]
unique_name_in_owner = true
layout_mode = 2
[node name="Control" type="Control" parent="MarginContainer/VBoxContainer/ParametersContainer/VBoxContainer/CommonHeader"]
layout_mode = 2
size_flags_horizontal = 3
[node name="RestrictHeight" parent="MarginContainer/VBoxContainer/ParametersContainer/VBoxContainer/CommonHeader" instance=ExtResource("11_y7srw")]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 1
size_flags_vertical = 3
[node name="Button" parent="MarginContainer/VBoxContainer/ParametersContainer/VBoxContainer/CommonHeader/RestrictHeight" index="0"]
layout_mode = 2
tooltip_text = "Restrict height.
If enabled, the modifier will try to remain in the local XZ plane instead of using the full volume defined by the ScatterShapes."
icon = ExtResource("12_lx60d")
default_icon = ExtResource("12_lx60d")
pressed_icon = ExtResource("15_0w0as")
[node name="TransformSpace" parent="MarginContainer/VBoxContainer/ParametersContainer/VBoxContainer/CommonHeader" instance=ExtResource("17_aoulv")]
unique_name_in_owner = true
layout_mode = 2
[node name="Label" parent="MarginContainer/VBoxContainer/ParametersContainer/VBoxContainer/CommonHeader/TransformSpace" index="0"]
visible = false
text = ""
[node name="SpinBox" parent="MarginContainer/VBoxContainer/ParametersContainer/VBoxContainer/CommonHeader/TransformSpace/MarginContainer/MarginContainer" index="0"]
visible = false
[node name="OptionButton" parent="MarginContainer/VBoxContainer/ParametersContainer/VBoxContainer/CommonHeader/TransformSpace/MarginContainer/MarginContainer" index="1"]
visible = true
item_count = 3
fit_to_longest_item = false
popup/item_0/text = "Global"
popup/item_0/icon = ExtResource("16_ocvvf")
popup/item_0/id = 0
popup/item_1/text = "Local"
popup/item_1/icon = ExtResource("13_txjs8")
popup/item_1/id = 1
popup/item_2/text = "Individual"
popup/item_2/icon = ExtResource("19_ln8a3")
popup/item_2/id = 2
[node name="WarningDialog" type="AcceptDialog" parent="."]
title = "Warning"
unresizable = true
popup_window = true
[editable path="MarginContainer/VBoxContainer/ParametersContainer/VBoxContainer/CommonHeader/ExpandButton/OverrideGlobalSeed"]
[editable path="MarginContainer/VBoxContainer/ParametersContainer/VBoxContainer/CommonHeader/RestrictHeight"]
[editable path="MarginContainer/VBoxContainer/ParametersContainer/VBoxContainer/CommonHeader/TransformSpace"]

View File

@@ -0,0 +1,14 @@
@tool
extends Control
@onready var _button: Button = $OverrideGlobalSeed/Button
@onready var _spinbox_root: Control = $SpinBoxRoot
func _ready():
_button.toggled.connect(_on_toggled)
func _on_toggled(enabled: bool) -> void:
_spinbox_root.visible = enabled

View File

@@ -0,0 +1,18 @@
@tool
extends Button
@export var default_icon: Texture
@export var pressed_icon: Texture
func _ready() -> void:
toggled.connect(_on_toggled)
_on_toggled(button_pressed)
func _on_toggled(pressed: bool) -> void:
if pressed:
icon = pressed_icon
else:
icon = default_icon

View File

@@ -0,0 +1,9 @@
@tool
extends VBoxContainer
@onready var label: Label = $Header/Label
func set_category_name(text) -> void:
label.text = text

View File

@@ -0,0 +1,26 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier_list_popup/category.gd" id="1"]
[node name="VBoxContainer" type="VBoxContainer"]
margin_right = 40.0
margin_bottom = 40.0
rect_pivot_offset = Vector2( -591.851, -77.5574 )
size_flags_horizontal = 3
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Header" type="VBoxContainer" parent="."]
margin_right = 40.0
margin_bottom = 22.0
[node name="Label" type="Label" parent="Header"]
margin_right = 40.0
margin_bottom = 14.0
[node name="HSeparator" type="HSeparator" parent="Header"]
margin_top = 18.0
margin_right = 40.0
margin_bottom = 22.0

View File

@@ -0,0 +1,113 @@
@tool
extends PopupPanel
signal add_modifier
var _modifiers := []
@onready var _category_root: Control = $MarginContainer/CategoryRoot
func _ready() -> void:
_rebuild_ui()
func _rebuild_ui():
for c in _category_root.get_children():
c.queue_free()
_discover_modifiers()
for modifier in _modifiers:
var instance = modifier.new()
if instance.enabled:
var category = _get_or_create_category(instance.category)
var button = _create_button(instance.display_name)
category.add_child(button, true)
button.pressed.connect(_on_pressed.bind(modifier))
for category in _category_root.get_children():
var header = category.get_child(0)
_sort_children_by_name(category)
category.move_child(header, 0)
func _create_button(display_name) -> Button:
var button = Button.new()
button.name = display_name
button.text = display_name
button.alignment = HORIZONTAL_ALIGNMENT_LEFT
return button
func _sort_children_by_name(node: Node) -> void:
var dict := {}
var names := []
for child in node.get_children():
names.push_back(child.name)
dict[child.name] = child
names.sort_custom(func(a, b): return String(a) < String(b))
for i in names.size():
var n = names[i]
node.move_child(dict[n], i)
func _get_or_create_category(text: String) -> Control:
if _category_root.has_node(text):
return _category_root.get_node(text) as Control
var c = preload("category.tscn").instantiate()
c.name = text
_category_root.add_child(c, true)
c.set_category_name(text)
return c
func _discover_modifiers() -> void:
if _modifiers.is_empty():
var path = _get_root_folder() + "/src/modifiers/"
_discover_modifiers_recursive(path)
func _discover_modifiers_recursive(path) -> void:
var dir = DirAccess.open(path)
dir.list_dir_begin()
var path_root = dir.get_current_dir() + "/"
while true:
var file = dir.get_next()
if file == "":
break
if file == "base_modifier.gd":
continue
if dir.current_is_dir():
_discover_modifiers_recursive(path_root + file)
continue
if not file.ends_with(".gd") and not file.ends_with(".gdc"):
continue
var full_path = path_root + file
var script = load(full_path)
if not script or not script.can_instantiate():
print("Error: Failed to load script ", file)
continue
_modifiers.push_back(script)
dir.list_dir_end()
func _get_root_folder() -> String:
var script: Script = get_script()
var path: String = script.get_path().get_base_dir()
var folders = path.right(-6) # Remove the res://
var tokens = folders.split('/')
return "res://" + tokens[0] + "/" + tokens[1]
func _on_pressed(modifier) -> void:
add_modifier.emit(modifier.new())
visible = false

View File

@@ -0,0 +1,20 @@
[gd_scene load_steps=2 format=3 uid="uid://belutr5odecw2"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier_list_popup/popup.gd" id="1"]
[node name="ModifiersPopup" type="PopupPanel"]
size = Vector2i(597, 322)
visible = true
script = ExtResource("1")
[node name="MarginContainer" type="MarginContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 4.0
offset_top = 4.0
offset_right = -431.0
offset_bottom = -282.0
[node name="CategoryRoot" type="HBoxContainer" parent="MarginContainer"]
offset_right = 589.0
offset_bottom = 314.0

View File

@@ -0,0 +1,87 @@
@tool
extends Window
signal load_preset
signal delete_preset
@onready var _no_presets: Label = $MarginContainer/VBoxContainer/ScrollContainer/VBoxContainer/NoPresets
@onready var _root: VBoxContainer = $MarginContainer/VBoxContainer/ScrollContainer/VBoxContainer/PresetsRoot
@onready var _confirmation_dialog: ConfirmationDialog = $ConfirmationDialog
var _selected: String
var _selected_ui: Node
func _ready():
_rebuild_ui()
about_to_popup.connect(_rebuild_ui)
func _rebuild_ui():
for c in _root.get_children():
c.queue_free()
_root.visible = false
var presets = _find_all_presets()
if presets.empty():
_no_presets.visible = true
return
_no_presets.visible = false
_root.visible = true
for p in presets:
var ui = preload("./preset.tscn").instantiate()
_root.add_child(ui)
ui.set_preset_name(p)
ui.load_preset.connect(_on_load_preset.bind(p))
ui.delete_preset.connect(_on_delete_preset.bind(p, ui))
func _find_all_presets() -> Array:
var root := _get_root_folder() + "/presets/"
var res := []
var dir = DirAccess.open(root)
if not dir:
return res
dir.list_dir_begin()
while true:
var file = dir.get_next()
if file == "":
break
if file.ends_with(".tscn"):
res.push_back(file.get_basename())
dir.list_dir_end()
res.sort()
return res
func _get_root_folder() -> String:
var path: String = get_script().get_path().get_base_dir()
var folders = path.right(6) # Remove the res://
var tokens = folders.split('/')
return "res://" + tokens[0] + "/" + tokens[1]
func _on_load_preset(preset_name) -> void:
emit_signal("load_preset", preset_name)
visible = false
func _on_delete_preset(preset_name, ui) -> void:
_selected = preset_name
_selected_ui = ui
_confirmation_dialog.popup_centered()
func _on_delete_preset_confirmed():
DirAccess.remove_absolute(_get_root_folder() + "/presets/" + _selected + ".tscn")
_selected_ui.queue_free()
func _on_cancel_pressed():
visible = false

View File

@@ -0,0 +1,101 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/presets/load_preset.gd" id="1"]
[node name="LoadPresetPopup" type="WindowDialog"]
visible = true
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -147.5
margin_top = -156.5
margin_right = 147.5
margin_bottom = 156.5
size_flags_horizontal = 5
size_flags_vertical = 5
window_title = "Load Presets"
resizable = true
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="MarginContainer" type="MarginContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
custom_constants/margin_right = 8
custom_constants/margin_top = 8
custom_constants/margin_left = 8
custom_constants/margin_bottom = 8
__meta__ = {
"_edit_use_anchors_": false
}
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
margin_left = 8.0
margin_top = 8.0
margin_right = 287.0
margin_bottom = 305.0
custom_constants/separation = 6
[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer"]
margin_right = 279.0
margin_bottom = 271.0
size_flags_vertical = 3
scroll_horizontal_enabled = false
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer"]
margin_right = 279.0
margin_bottom = 271.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="NoPresets" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/VBoxContainer"]
visible = false
margin_right = 247.0
margin_bottom = 118.0
size_flags_horizontal = 3
size_flags_vertical = 3
text = "No presets found.
Create new presets by pressing the \"Save Preset\" button first."
valign = 1
autowrap = true
[node name="PresetsRoot" type="VBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer/VBoxContainer"]
margin_right = 279.0
margin_bottom = 271.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
margin_left = 112.0
margin_top = 277.0
margin_right = 166.0
margin_bottom = 297.0
size_flags_horizontal = 4
[node name="CancelButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer"]
margin_right = 54.0
margin_bottom = 20.0
text = "Cancel"
[node name="ConfirmationDialog" type="ConfirmationDialog" parent="."]
visible = true
margin_left = -320.0
margin_top = 37.0
margin_right = -120.0
margin_bottom = 112.0
dialog_text = "Delete preset?
(This action can't be undone)"
__meta__ = {
"_edit_use_anchors_": false
}
[connection signal="pressed" from="MarginContainer/VBoxContainer/ScrollContainer/VBoxContainer/PresetsRoot/Preset" to="MarginContainer/VBoxContainer/ScrollContainer/VBoxContainer/PresetsRoot/Preset" method="_on_pressed"]
[connection signal="pressed" from="MarginContainer/VBoxContainer/ScrollContainer/VBoxContainer/PresetsRoot/Preset2" to="MarginContainer/VBoxContainer/ScrollContainer/VBoxContainer/PresetsRoot/Preset2" method="_on_pressed"]
[connection signal="pressed" from="MarginContainer/VBoxContainer/ScrollContainer/VBoxContainer/PresetsRoot/Preset3" to="MarginContainer/VBoxContainer/ScrollContainer/VBoxContainer/PresetsRoot/Preset3" method="_on_pressed"]
[connection signal="pressed" from="MarginContainer/VBoxContainer/ScrollContainer/VBoxContainer/PresetsRoot/Preset4" to="MarginContainer/VBoxContainer/ScrollContainer/VBoxContainer/PresetsRoot/Preset4" method="_on_pressed"]
[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer/CancelButton" to="." method="_on_cancel_pressed"]
[connection signal="confirmed" from="ConfirmationDialog" to="." method="_on_delete_preset_confirmed"]

View File

@@ -0,0 +1,21 @@
@tool
extends Button
signal load_preset
signal delete_preset
@onready var _label: Label = $MarginContainer/HBoxContainer/Label
func set_preset_name(text) -> void:
_label.text = text
func _on_pressed() -> void:
load_preset.emit()
func _on_delete() -> void:
delete_preset.emit()

View File

@@ -0,0 +1,54 @@
[gd_scene load_steps=3 format=3]
[ext_resource type="Texture" uid="uid://dahwdjl2er75o" path="res://addons/proton_scatter/icons/close.svg" id="1"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/presets/preset.gd" id="2"]
[node name="Preset" type="Button"]
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
margin_top = -10.0
margin_bottom = 29.0
rect_min_size = Vector2( 0, 40 )
focus_mode = 0
script = ExtResource( 2 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="MarginContainer" type="MarginContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
mouse_filter = 2
custom_constants/margin_right = 6
custom_constants/margin_top = 6
custom_constants/margin_left = 6
custom_constants/margin_bottom = 6
__meta__ = {
"_edit_use_anchors_": false
}
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer"]
margin_left = 6.0
margin_top = 6.0
margin_right = 1274.0
margin_bottom = 34.0
[node name="Label" type="Label" parent="MarginContainer/HBoxContainer"]
margin_top = 7.0
margin_right = 1236.0
margin_bottom = 21.0
size_flags_horizontal = 3
text = "Preset name"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Delete" type="Button" parent="MarginContainer/HBoxContainer"]
margin_left = 1240.0
margin_right = 1268.0
margin_bottom = 28.0
icon = ExtResource( 1 )
[connection signal="pressed" from="." to="." method="_on_pressed"]
[connection signal="pressed" from="MarginContainer/HBoxContainer/Delete" to="." method="_on_delete"]

View File

@@ -0,0 +1,78 @@
@tool
extends Window
signal save_preset
@onready var _line_edit: LineEdit = $MarginContainer/VBoxContainer/LineEdit
@onready var _cancel: Button = $MarginContainer/VBoxContainer/HBoxContainer/Cancel
@onready var _save: Button = $MarginContainer/VBoxContainer/HBoxContainer/Save
@onready var _warning: Label = $MarginContainer/VBoxContainer/Warning
@onready var _confirm_overwrite = $ConfirmationDialog
func _ready():
_cancel.pressed.connect(_on_cancel_pressed)
_save.pressed.connect(_on_save_pressed)
_warning.text = ""
_confirm_overwrite.confirmed.connect(_save_preset)
func _on_cancel_pressed() -> void:
visible = false
_line_edit.text = ""
func _on_save_pressed() -> void:
var preset_name: String = _line_edit.text
if preset_name.is_empty():
_warning.text = "Preset name can't be empty"
return
if not preset_name.is_valid_filename():
_warning.text = """Preset name must be a valid file name.
It cannot contain the following characters:
: / \\ ? * " | % < >"""
return
_warning.text = ""
if _exists(preset_name):
_confirm_overwrite.dialog_text = "Preset \"" + preset_name + "\" already exists. Overwrite?"
_confirm_overwrite.popup_centered()
else:
_save_preset()
func _save_preset() -> void:
emit_signal("save_preset", _line_edit.text)
visible = false
_line_edit.text = ""
func _exists(preset: String) -> bool:
var dir = DirAccess.open(_get_root_folder() + "/presets/")
if not dir:
return false
dir.list_dir_begin()
while true:
var file = dir.get_next()
if file == "":
break
if file == preset + ".tscn":
dir.list_dir_end()
return true
dir.list_dir_end()
return false
func _get_root_folder() -> String:
var script: Script = get_script()
var path: String = script.get_path().get_base_dir()
var folders = path.right(6) # Remove the res://
var tokens = folders.split('/')
return "res://" + tokens[0] + "/" + tokens[1]

View File

@@ -0,0 +1,96 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/presets/save_preset.gd" id="1"]
[node name="SavePresetPopup" type="WindowDialog"]
visible = true
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -180.0
margin_top = -81.0
margin_right = 180.0
margin_bottom = 81.0
size_flags_horizontal = 5
size_flags_vertical = 5
window_title = "Save Preset"
resizable = true
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="MarginContainer" type="MarginContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
custom_constants/margin_right = 24
custom_constants/margin_top = 4
custom_constants/margin_left = 24
custom_constants/margin_bottom = 24
__meta__ = {
"_edit_use_anchors_": false
}
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
margin_left = 24.0
margin_top = 4.0
margin_right = 336.0
margin_bottom = 120.0
size_flags_vertical = 0
custom_constants/separation = 12
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Warning" type="Label" parent="MarginContainer/VBoxContainer"]
modulate = Color( 1, 0.513726, 0.278431, 1 )
margin_right = 312.0
margin_bottom = 48.0
text = "Preset name must be a valid file name.
It cannot contain the following characters:
: / \\\\ ? * \" | % < >"
valign = 2
autowrap = true
[node name="LineEdit" type="LineEdit" parent="MarginContainer/VBoxContainer"]
margin_top = 60.0
margin_right = 312.0
margin_bottom = 84.0
placeholder_text = "Preset Name"
caret_blink = true
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
margin_top = 96.0
margin_right = 312.0
margin_bottom = 116.0
custom_constants/separation = 24
alignment = 1
[node name="Cancel" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer"]
margin_left = 96.0
margin_right = 150.0
margin_bottom = 20.0
text = "Cancel"
[node name="Save" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer"]
margin_left = 174.0
margin_right = 215.0
margin_bottom = 20.0
text = "Save"
[node name="ConfirmationDialog" type="ConfirmationDialog" parent="."]
visible = true
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -135.0
margin_top = -44.0
margin_right = 135.0
margin_bottom = 44.0
window_title = "Overwrite existing preset?"
dialog_autowrap = true
__meta__ = {
"_edit_use_anchors_": false
}

View File

@@ -0,0 +1,159 @@
@tool
extends Control
const ModifierPanel := preload("./modifier/modifier_panel.tscn")
@onready var _modifiers_container: Control = $%ModifiersContainer
@onready var _modifiers_popup: PopupPanel = $%ModifiersPopup
var _scatter
var _modifier_stack
var _undo_redo
var _is_ready := false
func _ready():
_modifiers_popup.add_modifier.connect(_on_modifier_added)
_modifiers_container.child_moved.connect(_on_modifier_moved)
%Rebuild.pressed.connect(_on_rebuild_pressed)
%DocumentationButton.pressed.connect(_on_documentation_requested.bind("ProtonScatter"))
%LoadPreset.pressed.connect(_on_load_preset_pressed)
%SavePreset.pressed.connect(_on_save_preset_pressed)
_is_ready = true
rebuild_ui()
func set_node(node) -> void:
if not node:
return
_scatter = node
_undo_redo = _scatter.undo_redo
%Documentation.set_editor_plugin(_scatter.editor_plugin)
%Presets.set_editor_plugin(_scatter.editor_plugin)
rebuild_ui()
func rebuild_ui() -> void:
if not _is_ready:
return
_validate_stack_connections()
_clear()
for m in _modifier_stack.stack:
var ui = ModifierPanel.instantiate()
_modifiers_container.add_child(ui)
ui.set_root(_scatter)
ui.create_ui_for(m)
ui.removed.connect(_on_modifier_removed.bind(m))
ui.value_changed.connect(_on_value_changed)
ui.documentation_requested.connect(_on_documentation_requested.bind(m.display_name))
ui.duplication_requested.connect(_on_modifier_duplicated.bind(m))
func _clear() -> void:
for c in _modifiers_container.get_children():
_modifiers_container.remove_child(c)
c.queue_free()
func _validate_stack_connections() -> void:
if not _scatter:
return
if _modifier_stack:
_modifier_stack.stack_changed.disconnect(_on_stack_changed)
_modifier_stack = _scatter.modifier_stack
_modifier_stack.stack_changed.connect(_on_stack_changed)
if _modifier_stack.just_created:
%Presets.load_default(_scatter)
_modifier_stack.just_created = false
rebuild_ui()
func _set_children_owner(new_owner: Node, node: Node):
for child in node.get_children():
child.set_owner(new_owner)
if child.get_children().size() > 0:
_set_children_owner(new_owner, child)
func _get_root_folder() -> String:
var path: String = get_script().get_path().get_base_dir()
var folders = path.right(6) # Remove the res://
var tokens = folders.split('/')
return "res://" + tokens[0] + "/" + tokens[1]
func _on_modifier_added(modifier) -> void:
if _undo_redo:
_undo_redo.create_action("Create modifier " + modifier.display_name)
_undo_redo.add_undo_method(_modifier_stack, "remove", modifier)
_undo_redo.add_do_method(_modifier_stack, "add", modifier)
_undo_redo.commit_action()
else:
_modifier_stack.add(modifier)
func _on_modifier_moved(old_index: int, new_index: int) -> void:
if _undo_redo:
_undo_redo.create_action("Move modifier")
_undo_redo.add_undo_method(_modifier_stack, "move", new_index, old_index)
_undo_redo.add_do_method(_modifier_stack, "move", old_index, new_index)
_undo_redo.commit_action()
else:
_modifier_stack.move(old_index, new_index)
func _on_modifier_removed(modifier) -> void:
if _undo_redo:
_undo_redo.create_action("Remove modifier " + modifier.display_name)
_undo_redo.add_undo_method(_modifier_stack, "add", modifier)
_undo_redo.add_do_method(_modifier_stack, "remove", modifier)
_undo_redo.commit_action()
else:
_modifier_stack.remove(modifier)
func _on_modifier_duplicated(modifier) -> void:
var index = _modifier_stack.get_index(modifier)
if index == -1:
return
if _undo_redo:
_undo_redo.create_action("Duplicate modifier " + modifier.display_name)
_undo_redo.add_undo_method(_modifier_stack, "remove_at", index + 1)
_undo_redo.add_do_method(_modifier_stack, "duplicate_modifier", modifier)
_undo_redo.commit_action()
else:
_modifier_stack.duplicate_modifier(modifier)
func _on_stack_changed() -> void:
rebuild_ui()
func _on_value_changed() -> void:
_modifier_stack.value_changed.emit()
func _on_rebuild_pressed() -> void:
if _scatter:
_scatter.full_rebuild()
func _on_save_preset_pressed() -> void:
%Presets.save_preset(_scatter)
func _on_load_preset_pressed() -> void:
%Presets.load_preset(_scatter)
func _on_documentation_requested(page_name) -> void:
%Documentation.show_page(page_name)

View File

@@ -0,0 +1,111 @@
[gd_scene load_steps=12 format=3 uid="uid://dllpt2dven8vw"]
[ext_resource type="Texture2D" uid="uid://cun73k8jdmr4e" path="res://addons/proton_scatter/icons/add.svg" id="1_4vwtj"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/stack_panel.gd" id="1_ga4or"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier/drag_container.gd" id="1_hg5ys"]
[ext_resource type="Texture2D" uid="uid://yqlpvcmb7mfi" path="res://addons/proton_scatter/icons/rebuild.svg" id="2_svid4"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/add_modifier_button.gd" id="3_01ldn"]
[ext_resource type="PackedScene" uid="uid://belutr5odecw2" path="res://addons/proton_scatter/src/stack/inspector_plugin/ui/modifier_list_popup/popup.tscn" id="3_pkswu"]
[ext_resource type="Texture2D" uid="uid://ddjrq1h4mkn6a" path="res://addons/proton_scatter/icons/load.svg" id="3_w72lv"]
[ext_resource type="Texture2D" uid="uid://b2omj2e03x72e" path="res://addons/proton_scatter/icons/save.svg" id="4_5l2cx"]
[ext_resource type="Texture2D" uid="uid://do8d3urxirjoa" path="res://addons/proton_scatter/icons/doc.svg" id="8_fgqhd"]
[ext_resource type="PackedScene" uid="uid://cfg8iqtuion8b" path="res://addons/proton_scatter/src/documentation/documentation.tscn" id="9_y57kc"]
[ext_resource type="PackedScene" uid="uid://bcsosdvstytoq" path="res://addons/proton_scatter/src/presets/presets.tscn" id="11_2ut8s"]
[node name="StackPanel" type="MarginContainer"]
clip_children = 1
clip_contents = true
offset_right = 456.0
offset_bottom = 144.0
theme_override_constants/margin_left = 4
theme_override_constants/margin_top = 4
theme_override_constants/margin_right = 4
theme_override_constants/margin_bottom = 4
script = ExtResource("1_ga4or")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 2
theme_override_constants/separation = 16
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="Add" type="Button" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 2
focus_mode = 0
toggle_mode = true
text = " Add modifier"
icon = ExtResource("1_4vwtj")
script = ExtResource("3_01ldn")
[node name="ModifiersPopup" parent="VBoxContainer/HBoxContainer/Add" instance=ExtResource("3_pkswu")]
unique_name_in_owner = true
size = Vector2i(755, 322)
visible = false
[node name="Rebuild" type="Button" parent="VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 4
tooltip_text = "Force rebuild.
If the placed items does not look as expected, you can press this button to force it to regenerate the result.
Usually, you shouldn't have to use it so please report it on Github if you found a case where it's necessary to click this. "
focus_mode = 0
icon = ExtResource("2_svid4")
icon_alignment = 1
[node name="VSeparator" type="VSeparator" parent="VBoxContainer/HBoxContainer"]
modulate = Color(1, 1, 1, 0.54902)
layout_mode = 2
[node name="LoadPreset" type="Button" parent="VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Load a preset."
focus_mode = 0
text = "
"
icon = ExtResource("3_w72lv")
icon_alignment = 1
[node name="SavePreset" type="Button" parent="VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Save a preset."
focus_mode = 0
text = "
"
icon = ExtResource("4_5l2cx")
icon_alignment = 1
[node name="VSeparator2" type="VSeparator" parent="VBoxContainer/HBoxContainer"]
modulate = Color(1, 1, 1, 0.54902)
layout_mode = 2
[node name="DocumentationButton" type="Button" parent="VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Open documentation"
focus_mode = 0
text = "
"
icon = ExtResource("8_fgqhd")
icon_alignment = 1
[node name="ModifiersContainer" type="Container" parent="VBoxContainer"]
unique_name_in_owner = true
clip_children = 1
custom_minimum_size = Vector2(0, -4)
layout_mode = 2
size_flags_vertical = 3
mouse_filter = 0
script = ExtResource("1_hg5ys")
[node name="Documentation" parent="." instance=ExtResource("9_y57kc")]
unique_name_in_owner = true
[node name="Presets" parent="." instance=ExtResource("11_2ut8s")]
unique_name_in_owner = true

View File

@@ -0,0 +1,96 @@
@tool
extends Resource
signal stack_changed
signal value_changed
signal transforms_ready
const ProtonScatter := preload("../scatter.gd")
const TransformList := preload("../common/transform_list.gd")
@export var stack: Array[ScatterBaseModifier] = []
var just_created := false
func start_update(scatter_node: ProtonScatter, domain):
var transforms = TransformList.new()
for modifier in stack:
await modifier.process_transforms(transforms, domain, scatter_node.global_seed)
transforms_ready.emit(transforms)
return transforms
func stop_update() -> void:
for modifier in stack:
modifier.interrupt()
func add(modifier: ScatterBaseModifier) -> void:
stack.push_back(modifier)
modifier.modifier_changed.connect(_on_modifier_changed)
stack_changed.emit()
func move(old_index: int, new_index: int) -> void:
var modifier = stack.pop_at(old_index)
stack.insert(new_index, modifier)
stack_changed.emit()
func remove(modifier: ScatterBaseModifier) -> void:
if stack.has(modifier):
stack.erase(modifier)
stack_changed.emit()
func remove_at(index: int) -> void:
if stack.size() > index:
stack.remove_at(index)
stack_changed.emit()
func duplicate_modifier(modifier: ScatterBaseModifier) -> void:
var index: int = stack.find(modifier)
if index != -1:
var copy = modifier.get_copy()
add(copy)
move(stack.size() - 1, index + 1)
func get_copy():
var copy = get_script().new()
for modifier in stack:
copy.add(modifier.duplicate())
return copy
func get_index(modifier: ScatterBaseModifier) -> int:
return stack.find(modifier)
func is_using_edge_data() -> bool:
for modifier in stack:
if modifier.use_edge_data:
return true
return false
# Returns true if at least one modifier does not require shapes in order to work.
# (This is the case for the "Add single item" modifier for example)
func does_not_require_shapes() -> bool:
for modifier in stack:
if modifier.warning_ignore_no_shape:
return true
return false
func _on_modifier_changed() -> void:
stack_changed.emit()