add code step 2
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Normalize EOL for all files that Git considers text files.
|
||||||
|
* text=auto eol=lf
|
34
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Debug GDScript Scene",
|
||||||
|
"type": "godot",
|
||||||
|
"request": "launch",
|
||||||
|
"project": "${workspaceFolder}",
|
||||||
|
// "project": "/Users/paul/Documents/dev/godot/MountainsAndSeas",
|
||||||
|
"port": 6007,
|
||||||
|
"address": "127.0.0.1",
|
||||||
|
"scene": "current",
|
||||||
|
"fixed_fps": 60,
|
||||||
|
"disable_vsync": false,
|
||||||
|
"preLaunchTask": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Debug GDScript Project",
|
||||||
|
"type": "godot",
|
||||||
|
"request": "launch",
|
||||||
|
"project": "${workspaceFolder}",
|
||||||
|
// "project": "/Users/paul/Documents/dev/godot/MountainsAndSeas",
|
||||||
|
"port": 6007,
|
||||||
|
"address": "127.0.0.1",
|
||||||
|
"scene": "main",
|
||||||
|
"fixed_fps": 60,
|
||||||
|
"disable_vsync": false,
|
||||||
|
"preLaunchTask": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
33
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"workbench.colorTheme":"Bluloco Dark Godot",
|
||||||
|
"editor.fontFamily": "'Consolas'",
|
||||||
|
"editor.fontSize": 13,
|
||||||
|
"debug.console.fontSize": 13,
|
||||||
|
"terminal.integrated.fontSize": 13,
|
||||||
|
"chat.editor.fontSize": 13,
|
||||||
|
"workbench.sideBar.location": "left",
|
||||||
|
"project":"/Users/paul/Documents/dev/godot/MountainsAndSeas",
|
||||||
|
"cmake.configureOnOpen": true,
|
||||||
|
"godotTools.editorPath.godot4": "/Applications/Godot.app/Contents/MacOS/Godot",
|
||||||
|
// "godotTools.editorPath.godot4": "/Users/paul/Documents/dev/godot/godot/bin/godot.macos.editor.arm64",
|
||||||
|
// "godotTools.editorPath.godot4": "/opt/homebrew/bin/godot",
|
||||||
|
"godotTools.lsp.serverHost": "localhost",
|
||||||
|
"godotTools.lsp.serverPort": 6005,
|
||||||
|
"godotTools.lsp.serverProtocol": "tcp",
|
||||||
|
"editor.indentSize": "tabSize",
|
||||||
|
"godotFiles.godotCachePath": {
|
||||||
|
"win32": [
|
||||||
|
"%LOCALAPPDATA%/Godot/",
|
||||||
|
"~/AppData/Local/Godot/",
|
||||||
|
"%TEMP%/Godot/"
|
||||||
|
],
|
||||||
|
"darwin": [
|
||||||
|
"~/Library/Caches/Godot/"
|
||||||
|
],
|
||||||
|
"linux": [
|
||||||
|
"~/.cache/godot/",
|
||||||
|
"~/.var/app/org.godotengine.GodotSharp/cache/godot/",
|
||||||
|
"~/.var/app/org.godotengine.Godot/cache/godot/"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
21
addons/debug_menu/LICENSE.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# MIT License
|
||||||
|
|
||||||
|
Copyright © 2023-present Hugo Locurcio and contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
479
addons/debug_menu/debug_menu.gd
Normal file
@ -0,0 +1,479 @@
|
|||||||
|
extends CanvasLayer
|
||||||
|
|
||||||
|
@export var fps: Label
|
||||||
|
@export var frame_time: Label
|
||||||
|
@export var frame_number: Label
|
||||||
|
@export var frame_history_total_avg: Label
|
||||||
|
@export var frame_history_total_min: Label
|
||||||
|
@export var frame_history_total_max: Label
|
||||||
|
@export var frame_history_total_last: Label
|
||||||
|
@export var frame_history_cpu_avg: Label
|
||||||
|
@export var frame_history_cpu_min: Label
|
||||||
|
@export var frame_history_cpu_max: Label
|
||||||
|
@export var frame_history_cpu_last: Label
|
||||||
|
@export var frame_history_gpu_avg: Label
|
||||||
|
@export var frame_history_gpu_min: Label
|
||||||
|
@export var frame_history_gpu_max: Label
|
||||||
|
@export var frame_history_gpu_last: Label
|
||||||
|
@export var fps_graph: Panel
|
||||||
|
@export var total_graph: Panel
|
||||||
|
@export var cpu_graph: Panel
|
||||||
|
@export var gpu_graph: Panel
|
||||||
|
@export var information: Label
|
||||||
|
@export var settings: Label
|
||||||
|
|
||||||
|
## The number of frames to keep in history for graph drawing and best/worst calculations.
|
||||||
|
## Currently, this also affects how FPS is measured.
|
||||||
|
const HISTORY_NUM_FRAMES = 150
|
||||||
|
|
||||||
|
const GRAPH_SIZE = Vector2(150, 25)
|
||||||
|
const GRAPH_MIN_FPS = 10
|
||||||
|
const GRAPH_MAX_FPS = 160
|
||||||
|
const GRAPH_MIN_FRAMETIME = 1.0 / GRAPH_MIN_FPS
|
||||||
|
const GRAPH_MAX_FRAMETIME = 1.0 / GRAPH_MAX_FPS
|
||||||
|
|
||||||
|
## Debug menu display style.
|
||||||
|
enum Style {
|
||||||
|
HIDDEN, ## Debug menu is hidden.
|
||||||
|
VISIBLE_COMPACT, ## Debug menu is visible, with only the FPS, FPS cap (if any) and time taken to render the last frame.
|
||||||
|
VISIBLE_DETAILED, ## Debug menu is visible with full information, including graphs.
|
||||||
|
MAX, ## Represents the size of the Style enum.
|
||||||
|
}
|
||||||
|
|
||||||
|
## The style to use when drawing the debug menu.
|
||||||
|
var style := Style.HIDDEN:
|
||||||
|
set(value):
|
||||||
|
style = value
|
||||||
|
match style:
|
||||||
|
Style.HIDDEN:
|
||||||
|
visible = false
|
||||||
|
Style.VISIBLE_COMPACT, Style.VISIBLE_DETAILED:
|
||||||
|
visible = true
|
||||||
|
frame_number.visible = style == Style.VISIBLE_DETAILED
|
||||||
|
$DebugMenu/VBoxContainer/FrameTimeHistory.visible = style == Style.VISIBLE_DETAILED
|
||||||
|
$DebugMenu/VBoxContainer/FPSGraph.visible = style == Style.VISIBLE_DETAILED
|
||||||
|
$DebugMenu/VBoxContainer/TotalGraph.visible = style == Style.VISIBLE_DETAILED
|
||||||
|
$DebugMenu/VBoxContainer/CPUGraph.visible = style == Style.VISIBLE_DETAILED
|
||||||
|
$DebugMenu/VBoxContainer/GPUGraph.visible = style == Style.VISIBLE_DETAILED
|
||||||
|
information.visible = style == Style.VISIBLE_DETAILED
|
||||||
|
settings.visible = style == Style.VISIBLE_DETAILED
|
||||||
|
|
||||||
|
# Value of `Time.get_ticks_usec()` on the previous frame.
|
||||||
|
var last_tick := 0
|
||||||
|
|
||||||
|
var thread := Thread.new()
|
||||||
|
|
||||||
|
## Returns the sum of all values of an array (use as a parameter to `Array.reduce()`).
|
||||||
|
var sum_func := func avg(accum: float, number: float) -> float: return accum + number
|
||||||
|
|
||||||
|
# History of the last `HISTORY_NUM_FRAMES` rendered frames.
|
||||||
|
var frame_history_total: Array[float] = []
|
||||||
|
var frame_history_cpu: Array[float] = []
|
||||||
|
var frame_history_gpu: Array[float] = []
|
||||||
|
var fps_history: Array[float] = [] # Only used for graphs.
|
||||||
|
|
||||||
|
var frametime_avg := GRAPH_MIN_FRAMETIME
|
||||||
|
var frametime_cpu_avg := GRAPH_MAX_FRAMETIME
|
||||||
|
var frametime_gpu_avg := GRAPH_MIN_FRAMETIME
|
||||||
|
var frames_per_second := float(GRAPH_MIN_FPS)
|
||||||
|
var frame_time_gradient := Gradient.new()
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
# This must be done here instead of `_ready()` to avoid having `visibility_changed` be emitted immediately.
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
if not InputMap.has_action("cycle_debug_menu"):
|
||||||
|
# Create default input action if no user-defined override exists.
|
||||||
|
# We can't do it in the editor plugin's activation code as it doesn't seem to work there.
|
||||||
|
InputMap.add_action("cycle_debug_menu")
|
||||||
|
var event := InputEventKey.new()
|
||||||
|
event.keycode = KEY_F3
|
||||||
|
InputMap.action_add_event("cycle_debug_menu", event)
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
fps_graph.draw.connect(_fps_graph_draw)
|
||||||
|
total_graph.draw.connect(_total_graph_draw)
|
||||||
|
cpu_graph.draw.connect(_cpu_graph_draw)
|
||||||
|
gpu_graph.draw.connect(_gpu_graph_draw)
|
||||||
|
|
||||||
|
fps_history.resize(HISTORY_NUM_FRAMES)
|
||||||
|
frame_history_total.resize(HISTORY_NUM_FRAMES)
|
||||||
|
frame_history_cpu.resize(HISTORY_NUM_FRAMES)
|
||||||
|
frame_history_gpu.resize(HISTORY_NUM_FRAMES)
|
||||||
|
|
||||||
|
# NOTE: Both FPS and frametimes are colored following FPS logic
|
||||||
|
# (red = 10 FPS, yellow = 60 FPS, green = 110 FPS, cyan = 160 FPS).
|
||||||
|
# This makes the color gradient non-linear.
|
||||||
|
# Colors are taken from <https://tailwindcolor.com/>.
|
||||||
|
frame_time_gradient.set_color(0, Color8(239, 68, 68)) # red-500
|
||||||
|
frame_time_gradient.set_color(1, Color8(56, 189, 248)) # light-blue-400
|
||||||
|
frame_time_gradient.add_point(0.3333, Color8(250, 204, 21)) # yellow-400
|
||||||
|
frame_time_gradient.add_point(0.6667, Color8(128, 226, 95)) # 50-50 mix of lime-400 and green-400
|
||||||
|
|
||||||
|
get_viewport().size_changed.connect(update_settings_label)
|
||||||
|
|
||||||
|
# Display loading text while information is being queried,
|
||||||
|
# in case the user toggles the full debug menu just after starting the project.
|
||||||
|
information.text = "Loading hardware information...\n\n "
|
||||||
|
settings.text = "Loading project information..."
|
||||||
|
thread.start(
|
||||||
|
func():
|
||||||
|
# Disable thread safety checks as they interfere with this add-on.
|
||||||
|
# This only affects this particular thread, not other thread instances in the project.
|
||||||
|
# See <https://github.com/godotengine/godot/pull/78000> for details.
|
||||||
|
# Use a Callable so that this can be ignored on Godot 4.0 without causing a script error
|
||||||
|
# (thread safety checks were added in Godot 4.1).
|
||||||
|
if Engine.get_version_info()["hex"] >= 0x040100:
|
||||||
|
Callable(Thread, "set_thread_safety_checks_enabled").call(false)
|
||||||
|
|
||||||
|
# Enable required time measurements to display CPU/GPU frame time information.
|
||||||
|
# These lines are time-consuming operations, so run them in a separate thread.
|
||||||
|
RenderingServer.viewport_set_measure_render_time(get_viewport().get_viewport_rid(), true)
|
||||||
|
update_information_label()
|
||||||
|
update_settings_label()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
if event.is_action_pressed("cycle_debug_menu"):
|
||||||
|
style = wrapi(style + 1, 0, Style.MAX) as Style
|
||||||
|
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
thread.wait_to_finish()
|
||||||
|
|
||||||
|
|
||||||
|
## Update hardware information label (this can change at runtime based on window
|
||||||
|
## size and graphics settings). This is only called when the window is resized.
|
||||||
|
## To update when graphics settings are changed, the function must be called manually
|
||||||
|
## using `DebugMenu.update_settings_label()`.
|
||||||
|
func update_settings_label() -> void:
|
||||||
|
settings.text = ""
|
||||||
|
if ProjectSettings.has_setting("application/config/version"):
|
||||||
|
settings.text += "Project Version: %s\n" % ProjectSettings.get_setting("application/config/version")
|
||||||
|
|
||||||
|
var rendering_method := str(ProjectSettings.get_setting_with_override("rendering/renderer/rendering_method"))
|
||||||
|
var rendering_method_string := rendering_method
|
||||||
|
match rendering_method:
|
||||||
|
"forward_plus":
|
||||||
|
rendering_method_string = "Forward+"
|
||||||
|
"mobile":
|
||||||
|
rendering_method_string = "Forward Mobile"
|
||||||
|
"gl_compatibility":
|
||||||
|
rendering_method_string = "Compatibility"
|
||||||
|
settings.text += "Rendering Method: %s\n" % rendering_method_string
|
||||||
|
|
||||||
|
var viewport := get_viewport()
|
||||||
|
|
||||||
|
# The size of the viewport rendering, which determines which resolution 3D is rendered at.
|
||||||
|
var viewport_render_size := Vector2i()
|
||||||
|
|
||||||
|
if viewport.content_scale_mode == Window.CONTENT_SCALE_MODE_VIEWPORT:
|
||||||
|
viewport_render_size = viewport.get_visible_rect().size
|
||||||
|
settings.text += "Viewport: %d×%d, Window: %d×%d\n" % [viewport.get_visible_rect().size.x, viewport.get_visible_rect().size.y, viewport.size.x, viewport.size.y]
|
||||||
|
else:
|
||||||
|
# Window size matches viewport size.
|
||||||
|
viewport_render_size = viewport.size
|
||||||
|
settings.text += "Viewport: %d×%d\n" % [viewport.size.x, viewport.size.y]
|
||||||
|
|
||||||
|
# Display 3D settings only if relevant.
|
||||||
|
if viewport.get_camera_3d():
|
||||||
|
var scaling_3d_mode_string := "(unknown)"
|
||||||
|
match viewport.scaling_3d_mode:
|
||||||
|
Viewport.SCALING_3D_MODE_BILINEAR:
|
||||||
|
scaling_3d_mode_string = "Bilinear"
|
||||||
|
Viewport.SCALING_3D_MODE_FSR:
|
||||||
|
scaling_3d_mode_string = "FSR 1.0"
|
||||||
|
Viewport.SCALING_3D_MODE_FSR2:
|
||||||
|
scaling_3d_mode_string = "FSR 2.2"
|
||||||
|
|
||||||
|
var antialiasing_3d_string := ""
|
||||||
|
if viewport.scaling_3d_mode == Viewport.SCALING_3D_MODE_FSR2:
|
||||||
|
# The FSR2 scaling mode includes its own temporal antialiasing implementation.
|
||||||
|
antialiasing_3d_string += (" + " if not antialiasing_3d_string.is_empty() else "") + "FSR 2.2"
|
||||||
|
if viewport.scaling_3d_mode != Viewport.SCALING_3D_MODE_FSR2 and viewport.use_taa:
|
||||||
|
# Godot's own TAA is ignored when using FSR2 scaling mode, as FSR2 provides its own TAA implementation.
|
||||||
|
antialiasing_3d_string += (" + " if not antialiasing_3d_string.is_empty() else "") + "TAA"
|
||||||
|
if viewport.msaa_3d >= Viewport.MSAA_2X:
|
||||||
|
antialiasing_3d_string += (" + " if not antialiasing_3d_string.is_empty() else "") + "%d× MSAA" % pow(2, viewport.msaa_3d)
|
||||||
|
if viewport.screen_space_aa == Viewport.SCREEN_SPACE_AA_FXAA:
|
||||||
|
antialiasing_3d_string += (" + " if not antialiasing_3d_string.is_empty() else "") + "FXAA"
|
||||||
|
|
||||||
|
settings.text += "3D scale (%s): %d%% = %d×%d" % [
|
||||||
|
scaling_3d_mode_string,
|
||||||
|
viewport.scaling_3d_scale * 100,
|
||||||
|
viewport_render_size.x * viewport.scaling_3d_scale,
|
||||||
|
viewport_render_size.y * viewport.scaling_3d_scale,
|
||||||
|
]
|
||||||
|
|
||||||
|
if not antialiasing_3d_string.is_empty():
|
||||||
|
settings.text += "\n3D Antialiasing: %s" % antialiasing_3d_string
|
||||||
|
|
||||||
|
var environment := viewport.get_camera_3d().get_world_3d().environment
|
||||||
|
if environment:
|
||||||
|
if environment.ssr_enabled:
|
||||||
|
settings.text += "\nSSR: %d Steps" % environment.ssr_max_steps
|
||||||
|
|
||||||
|
if environment.ssao_enabled:
|
||||||
|
settings.text += "\nSSAO: On"
|
||||||
|
if environment.ssil_enabled:
|
||||||
|
settings.text += "\nSSIL: On"
|
||||||
|
|
||||||
|
if environment.sdfgi_enabled:
|
||||||
|
settings.text += "\nSDFGI: %d Cascades" % environment.sdfgi_cascades
|
||||||
|
|
||||||
|
if environment.glow_enabled:
|
||||||
|
settings.text += "\nGlow: On"
|
||||||
|
|
||||||
|
if environment.volumetric_fog_enabled:
|
||||||
|
settings.text += "\nVolumetric Fog: On"
|
||||||
|
var antialiasing_2d_string := ""
|
||||||
|
if viewport.msaa_2d >= Viewport.MSAA_2X:
|
||||||
|
antialiasing_2d_string = "%d× MSAA" % pow(2, viewport.msaa_2d)
|
||||||
|
|
||||||
|
if not antialiasing_2d_string.is_empty():
|
||||||
|
settings.text += "\n2D Antialiasing: %s" % antialiasing_2d_string
|
||||||
|
|
||||||
|
|
||||||
|
## Update hardware/software information label (this never changes at runtime).
|
||||||
|
func update_information_label() -> void:
|
||||||
|
var adapter_string := ""
|
||||||
|
# Make "NVIDIA Corporation" and "NVIDIA" be considered identical (required when using OpenGL to avoid redundancy).
|
||||||
|
if RenderingServer.get_video_adapter_vendor().trim_suffix(" Corporation") in RenderingServer.get_video_adapter_name():
|
||||||
|
# Avoid repeating vendor name before adapter name.
|
||||||
|
# Trim redundant suffix sometimes reported by NVIDIA graphics cards when using OpenGL.
|
||||||
|
adapter_string = RenderingServer.get_video_adapter_name().trim_suffix("/PCIe/SSE2")
|
||||||
|
else:
|
||||||
|
adapter_string = RenderingServer.get_video_adapter_vendor() + " - " + RenderingServer.get_video_adapter_name().trim_suffix("/PCIe/SSE2")
|
||||||
|
|
||||||
|
# Graphics driver version information isn't always availble.
|
||||||
|
var driver_info := OS.get_video_adapter_driver_info()
|
||||||
|
var driver_info_string := ""
|
||||||
|
if driver_info.size() >= 2:
|
||||||
|
driver_info_string = driver_info[1]
|
||||||
|
else:
|
||||||
|
driver_info_string = "(unknown)"
|
||||||
|
|
||||||
|
var release_string := ""
|
||||||
|
if OS.has_feature("editor"):
|
||||||
|
# Editor build (implies `debug`).
|
||||||
|
release_string = "editor"
|
||||||
|
elif OS.has_feature("debug"):
|
||||||
|
# Debug export template build.
|
||||||
|
release_string = "debug"
|
||||||
|
else:
|
||||||
|
# Release export template build.
|
||||||
|
release_string = "release"
|
||||||
|
|
||||||
|
var rendering_method := str(ProjectSettings.get_setting_with_override("rendering/renderer/rendering_method"))
|
||||||
|
var rendering_driver := str(ProjectSettings.get_setting_with_override("rendering/rendering_device/driver"))
|
||||||
|
var graphics_api_string := rendering_driver
|
||||||
|
if rendering_method != "gl_compatibility":
|
||||||
|
if rendering_driver == "d3d12":
|
||||||
|
graphics_api_string = "Direct3D 12"
|
||||||
|
elif rendering_driver == "metal":
|
||||||
|
graphics_api_string = "Metal"
|
||||||
|
elif rendering_driver == "vulkan":
|
||||||
|
if OS.has_feature("macos") or OS.has_feature("ios"):
|
||||||
|
graphics_api_string = "Vulkan via MoltenVK"
|
||||||
|
else:
|
||||||
|
graphics_api_string = "Vulkan"
|
||||||
|
else:
|
||||||
|
if rendering_driver == "opengl3_angle":
|
||||||
|
graphics_api_string = "OpenGL via ANGLE"
|
||||||
|
elif OS.has_feature("mobile") or rendering_driver == "opengl3_es":
|
||||||
|
graphics_api_string = "OpenGL ES"
|
||||||
|
elif OS.has_feature("web"):
|
||||||
|
graphics_api_string = "WebGL"
|
||||||
|
elif rendering_driver == "opengl3":
|
||||||
|
graphics_api_string = "OpenGL"
|
||||||
|
|
||||||
|
information.text = (
|
||||||
|
"%s, %d threads\n" % [OS.get_processor_name().replace("(R)", "").replace("(TM)", ""), OS.get_processor_count()]
|
||||||
|
+ "%s %s (%s %s), %s %s\n" % [OS.get_name(), "64-bit" if OS.has_feature("64") else "32-bit", release_string, "double" if OS.has_feature("double") else "single", graphics_api_string, RenderingServer.get_video_adapter_api_version()]
|
||||||
|
+ "%s, %s" % [adapter_string, driver_info_string]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _fps_graph_draw() -> void:
|
||||||
|
var fps_polyline := PackedVector2Array()
|
||||||
|
fps_polyline.resize(HISTORY_NUM_FRAMES)
|
||||||
|
for fps_index in fps_history.size():
|
||||||
|
fps_polyline[fps_index] = Vector2(
|
||||||
|
remap(fps_index, 0, fps_history.size(), 0, GRAPH_SIZE.x),
|
||||||
|
remap(clampf(fps_history[fps_index], GRAPH_MIN_FPS, GRAPH_MAX_FPS), GRAPH_MIN_FPS, GRAPH_MAX_FPS, GRAPH_SIZE.y, 0.0)
|
||||||
|
)
|
||||||
|
# Don't use antialiasing to speed up line drawing, but use a width that scales with
|
||||||
|
# viewport scale to keep the line easily readable on hiDPI displays.
|
||||||
|
fps_graph.draw_polyline(fps_polyline, frame_time_gradient.sample(remap(frames_per_second, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)), 1.0)
|
||||||
|
|
||||||
|
|
||||||
|
func _total_graph_draw() -> void:
|
||||||
|
var total_polyline := PackedVector2Array()
|
||||||
|
total_polyline.resize(HISTORY_NUM_FRAMES)
|
||||||
|
for total_index in frame_history_total.size():
|
||||||
|
total_polyline[total_index] = Vector2(
|
||||||
|
remap(total_index, 0, frame_history_total.size(), 0, GRAPH_SIZE.x),
|
||||||
|
remap(clampf(frame_history_total[total_index], GRAPH_MIN_FPS, GRAPH_MAX_FPS), GRAPH_MIN_FPS, GRAPH_MAX_FPS, GRAPH_SIZE.y, 0.0)
|
||||||
|
)
|
||||||
|
# Don't use antialiasing to speed up line drawing, but use a width that scales with
|
||||||
|
# viewport scale to keep the line easily readable on hiDPI displays.
|
||||||
|
total_graph.draw_polyline(total_polyline, frame_time_gradient.sample(remap(1000.0 / frametime_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)), 1.0)
|
||||||
|
|
||||||
|
|
||||||
|
func _cpu_graph_draw() -> void:
|
||||||
|
var cpu_polyline := PackedVector2Array()
|
||||||
|
cpu_polyline.resize(HISTORY_NUM_FRAMES)
|
||||||
|
for cpu_index in frame_history_cpu.size():
|
||||||
|
cpu_polyline[cpu_index] = Vector2(
|
||||||
|
remap(cpu_index, 0, frame_history_cpu.size(), 0, GRAPH_SIZE.x),
|
||||||
|
remap(clampf(frame_history_cpu[cpu_index], GRAPH_MIN_FPS, GRAPH_MAX_FPS), GRAPH_MIN_FPS, GRAPH_MAX_FPS, GRAPH_SIZE.y, 0.0)
|
||||||
|
)
|
||||||
|
# Don't use antialiasing to speed up line drawing, but use a width that scales with
|
||||||
|
# viewport scale to keep the line easily readable on hiDPI displays.
|
||||||
|
cpu_graph.draw_polyline(cpu_polyline, frame_time_gradient.sample(remap(1000.0 / frametime_cpu_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)), 1.0)
|
||||||
|
|
||||||
|
|
||||||
|
func _gpu_graph_draw() -> void:
|
||||||
|
var gpu_polyline := PackedVector2Array()
|
||||||
|
gpu_polyline.resize(HISTORY_NUM_FRAMES)
|
||||||
|
for gpu_index in frame_history_gpu.size():
|
||||||
|
gpu_polyline[gpu_index] = Vector2(
|
||||||
|
remap(gpu_index, 0, frame_history_gpu.size(), 0, GRAPH_SIZE.x),
|
||||||
|
remap(clampf(frame_history_gpu[gpu_index], GRAPH_MIN_FPS, GRAPH_MAX_FPS), GRAPH_MIN_FPS, GRAPH_MAX_FPS, GRAPH_SIZE.y, 0.0)
|
||||||
|
)
|
||||||
|
# Don't use antialiasing to speed up line drawing, but use a width that scales with
|
||||||
|
# viewport scale to keep the line easily readable on hiDPI displays.
|
||||||
|
gpu_graph.draw_polyline(gpu_polyline, frame_time_gradient.sample(remap(1000.0 / frametime_gpu_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)), 1.0)
|
||||||
|
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
if visible:
|
||||||
|
fps_graph.queue_redraw()
|
||||||
|
total_graph.queue_redraw()
|
||||||
|
cpu_graph.queue_redraw()
|
||||||
|
gpu_graph.queue_redraw()
|
||||||
|
|
||||||
|
# Difference between the last two rendered frames in milliseconds.
|
||||||
|
var frametime := (Time.get_ticks_usec() - last_tick) * 0.001
|
||||||
|
|
||||||
|
frame_history_total.push_back(frametime)
|
||||||
|
if frame_history_total.size() > HISTORY_NUM_FRAMES:
|
||||||
|
frame_history_total.pop_front()
|
||||||
|
|
||||||
|
# Frametimes are colored following FPS logic (red = 10 FPS, yellow = 60 FPS, green = 110 FPS, cyan = 160 FPS).
|
||||||
|
# This makes the color gradient non-linear.
|
||||||
|
frametime_avg = frame_history_total.reduce(sum_func) / frame_history_total.size()
|
||||||
|
frame_history_total_avg.text = str(frametime_avg).pad_decimals(2)
|
||||||
|
frame_history_total_avg.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
|
||||||
|
|
||||||
|
var frametime_min: float = frame_history_total.min()
|
||||||
|
frame_history_total_min.text = str(frametime_min).pad_decimals(2)
|
||||||
|
frame_history_total_min.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_min, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
|
||||||
|
|
||||||
|
var frametime_max: float = frame_history_total.max()
|
||||||
|
frame_history_total_max.text = str(frametime_max).pad_decimals(2)
|
||||||
|
frame_history_total_max.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_max, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
|
||||||
|
|
||||||
|
frame_history_total_last.text = str(frametime).pad_decimals(2)
|
||||||
|
frame_history_total_last.modulate = frame_time_gradient.sample(remap(1000.0 / frametime, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
|
||||||
|
|
||||||
|
var viewport_rid := get_viewport().get_viewport_rid()
|
||||||
|
var frametime_cpu := RenderingServer.viewport_get_measured_render_time_cpu(viewport_rid) + RenderingServer.get_frame_setup_time_cpu()
|
||||||
|
frame_history_cpu.push_back(frametime_cpu)
|
||||||
|
if frame_history_cpu.size() > HISTORY_NUM_FRAMES:
|
||||||
|
frame_history_cpu.pop_front()
|
||||||
|
|
||||||
|
frametime_cpu_avg = frame_history_cpu.reduce(sum_func) / frame_history_cpu.size()
|
||||||
|
frame_history_cpu_avg.text = str(frametime_cpu_avg).pad_decimals(2)
|
||||||
|
frame_history_cpu_avg.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_cpu_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
|
||||||
|
|
||||||
|
var frametime_cpu_min: float = frame_history_cpu.min()
|
||||||
|
frame_history_cpu_min.text = str(frametime_cpu_min).pad_decimals(2)
|
||||||
|
frame_history_cpu_min.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_cpu_min, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
|
||||||
|
|
||||||
|
var frametime_cpu_max: float = frame_history_cpu.max()
|
||||||
|
frame_history_cpu_max.text = str(frametime_cpu_max).pad_decimals(2)
|
||||||
|
frame_history_cpu_max.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_cpu_max, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
|
||||||
|
|
||||||
|
frame_history_cpu_last.text = str(frametime_cpu).pad_decimals(2)
|
||||||
|
frame_history_cpu_last.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_cpu, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
|
||||||
|
|
||||||
|
var frametime_gpu := RenderingServer.viewport_get_measured_render_time_gpu(viewport_rid)
|
||||||
|
frame_history_gpu.push_back(frametime_gpu)
|
||||||
|
if frame_history_gpu.size() > HISTORY_NUM_FRAMES:
|
||||||
|
frame_history_gpu.pop_front()
|
||||||
|
|
||||||
|
frametime_gpu_avg = frame_history_gpu.reduce(sum_func) / frame_history_gpu.size()
|
||||||
|
frame_history_gpu_avg.text = str(frametime_gpu_avg).pad_decimals(2)
|
||||||
|
frame_history_gpu_avg.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_gpu_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
|
||||||
|
|
||||||
|
var frametime_gpu_min: float = frame_history_gpu.min()
|
||||||
|
frame_history_gpu_min.text = str(frametime_gpu_min).pad_decimals(2)
|
||||||
|
frame_history_gpu_min.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_gpu_min, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
|
||||||
|
|
||||||
|
var frametime_gpu_max: float = frame_history_gpu.max()
|
||||||
|
frame_history_gpu_max.text = str(frametime_gpu_max).pad_decimals(2)
|
||||||
|
frame_history_gpu_max.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_gpu_max, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
|
||||||
|
|
||||||
|
frame_history_gpu_last.text = str(frametime_gpu).pad_decimals(2)
|
||||||
|
frame_history_gpu_last.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_gpu, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
|
||||||
|
|
||||||
|
frames_per_second = 1000.0 / frametime_avg
|
||||||
|
fps_history.push_back(frames_per_second)
|
||||||
|
if fps_history.size() > HISTORY_NUM_FRAMES:
|
||||||
|
fps_history.pop_front()
|
||||||
|
|
||||||
|
fps.text = str(floor(frames_per_second)) + " FPS"
|
||||||
|
var frame_time_color := frame_time_gradient.sample(remap(frames_per_second, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0))
|
||||||
|
fps.modulate = frame_time_color
|
||||||
|
|
||||||
|
frame_time.text = str(frametime).pad_decimals(2) + " mspf"
|
||||||
|
frame_time.modulate = frame_time_color
|
||||||
|
|
||||||
|
var vsync_string := ""
|
||||||
|
match DisplayServer.window_get_vsync_mode():
|
||||||
|
DisplayServer.VSYNC_ENABLED:
|
||||||
|
vsync_string = "V-Sync"
|
||||||
|
DisplayServer.VSYNC_ADAPTIVE:
|
||||||
|
vsync_string = "Adaptive V-Sync"
|
||||||
|
DisplayServer.VSYNC_MAILBOX:
|
||||||
|
vsync_string = "Mailbox V-Sync"
|
||||||
|
|
||||||
|
if Engine.max_fps > 0 or OS.low_processor_usage_mode:
|
||||||
|
# Display FPS cap determined by `Engine.max_fps` or low-processor usage mode sleep duration
|
||||||
|
# (the lowest FPS cap is used).
|
||||||
|
var low_processor_max_fps := roundi(1000000.0 / OS.low_processor_usage_mode_sleep_usec)
|
||||||
|
var fps_cap := low_processor_max_fps
|
||||||
|
if Engine.max_fps > 0:
|
||||||
|
fps_cap = mini(Engine.max_fps, low_processor_max_fps)
|
||||||
|
frame_time.text += " (cap: " + str(fps_cap) + " FPS"
|
||||||
|
|
||||||
|
if not vsync_string.is_empty():
|
||||||
|
frame_time.text += " + " + vsync_string
|
||||||
|
|
||||||
|
frame_time.text += ")"
|
||||||
|
else:
|
||||||
|
if not vsync_string.is_empty():
|
||||||
|
frame_time.text += " (" + vsync_string + ")"
|
||||||
|
|
||||||
|
frame_number.text = "Frame: " + str(Engine.get_frames_drawn())
|
||||||
|
|
||||||
|
last_tick = Time.get_ticks_usec()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_visibility_changed() -> void:
|
||||||
|
if visible:
|
||||||
|
# Reset graphs to prevent them from looking strange before `HISTORY_NUM_FRAMES` frames
|
||||||
|
# have been drawn.
|
||||||
|
var frametime_last := (Time.get_ticks_usec() - last_tick) * 0.001
|
||||||
|
fps_history.resize(HISTORY_NUM_FRAMES)
|
||||||
|
fps_history.fill(1000.0 / frametime_last)
|
||||||
|
frame_history_total.resize(HISTORY_NUM_FRAMES)
|
||||||
|
frame_history_total.fill(frametime_last)
|
||||||
|
frame_history_cpu.resize(HISTORY_NUM_FRAMES)
|
||||||
|
var viewport_rid := get_viewport().get_viewport_rid()
|
||||||
|
frame_history_cpu.fill(RenderingServer.viewport_get_measured_render_time_cpu(viewport_rid) + RenderingServer.get_frame_setup_time_cpu())
|
||||||
|
frame_history_gpu.resize(HISTORY_NUM_FRAMES)
|
||||||
|
frame_history_gpu.fill(RenderingServer.viewport_get_measured_render_time_gpu(viewport_rid))
|
401
addons/debug_menu/debug_menu.tscn
Normal file
@ -0,0 +1,401 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://cggqb75a8w8r"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/debug_menu/debug_menu.gd" id="1_p440y"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ki0n8"]
|
||||||
|
bg_color = Color(0, 0, 0, 0.25098)
|
||||||
|
|
||||||
|
[node name="CanvasLayer" type="CanvasLayer" node_paths=PackedStringArray("fps", "frame_time", "frame_number", "frame_history_total_avg", "frame_history_total_min", "frame_history_total_max", "frame_history_total_last", "frame_history_cpu_avg", "frame_history_cpu_min", "frame_history_cpu_max", "frame_history_cpu_last", "frame_history_gpu_avg", "frame_history_gpu_min", "frame_history_gpu_max", "frame_history_gpu_last", "fps_graph", "total_graph", "cpu_graph", "gpu_graph", "information", "settings")]
|
||||||
|
layer = 128
|
||||||
|
script = ExtResource("1_p440y")
|
||||||
|
fps = NodePath("DebugMenu/VBoxContainer/FPS")
|
||||||
|
frame_time = NodePath("DebugMenu/VBoxContainer/FrameTime")
|
||||||
|
frame_number = NodePath("DebugMenu/VBoxContainer/FrameNumber")
|
||||||
|
frame_history_total_avg = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/TotalAvg")
|
||||||
|
frame_history_total_min = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/TotalMin")
|
||||||
|
frame_history_total_max = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/TotalMax")
|
||||||
|
frame_history_total_last = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/TotalLast")
|
||||||
|
frame_history_cpu_avg = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/CPUAvg")
|
||||||
|
frame_history_cpu_min = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/CPUMin")
|
||||||
|
frame_history_cpu_max = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/CPUMax")
|
||||||
|
frame_history_cpu_last = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/CPULast")
|
||||||
|
frame_history_gpu_avg = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/GPUAvg")
|
||||||
|
frame_history_gpu_min = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/GPUMin")
|
||||||
|
frame_history_gpu_max = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/GPUMax")
|
||||||
|
frame_history_gpu_last = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/GPULast")
|
||||||
|
fps_graph = NodePath("DebugMenu/VBoxContainer/FPSGraph/Graph")
|
||||||
|
total_graph = NodePath("DebugMenu/VBoxContainer/TotalGraph/Graph")
|
||||||
|
cpu_graph = NodePath("DebugMenu/VBoxContainer/CPUGraph/Graph")
|
||||||
|
gpu_graph = NodePath("DebugMenu/VBoxContainer/GPUGraph/Graph")
|
||||||
|
information = NodePath("DebugMenu/VBoxContainer/Information")
|
||||||
|
settings = NodePath("DebugMenu/VBoxContainer/Settings")
|
||||||
|
|
||||||
|
[node name="DebugMenu" type="Control" parent="."]
|
||||||
|
custom_minimum_size = Vector2(400, 400)
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 1
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
offset_left = -416.0
|
||||||
|
offset_top = 8.0
|
||||||
|
offset_right = -16.0
|
||||||
|
offset_bottom = 408.0
|
||||||
|
grow_horizontal = 0
|
||||||
|
size_flags_horizontal = 8
|
||||||
|
size_flags_vertical = 4
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="DebugMenu"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 1
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
offset_left = -300.0
|
||||||
|
offset_bottom = 374.0
|
||||||
|
grow_horizontal = 0
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_constants/separation = 0
|
||||||
|
|
||||||
|
[node name="FPS" type="Label" parent="DebugMenu/VBoxContainer"]
|
||||||
|
modulate = Color(0, 1, 0, 1)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/line_spacing = 0
|
||||||
|
theme_override_constants/outline_size = 5
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "60 FPS"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="FrameTime" type="Label" parent="DebugMenu/VBoxContainer"]
|
||||||
|
modulate = Color(0, 1, 0, 1)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "16.67 mspf (cap: 123 FPS + Adaptive V-Sync)"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="FrameNumber" type="Label" parent="DebugMenu/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "Frame: 1234"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="FrameTimeHistory" type="GridContainer" parent="DebugMenu/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 8
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_constants/h_separation = 0
|
||||||
|
theme_override_constants/v_separation = 0
|
||||||
|
columns = 5
|
||||||
|
|
||||||
|
[node name="Spacer" type="Control" parent="DebugMenu/VBoxContainer/FrameTimeHistory"]
|
||||||
|
custom_minimum_size = Vector2(60, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="AvgHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"]
|
||||||
|
custom_minimum_size = Vector2(50, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "Average"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="MinHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"]
|
||||||
|
custom_minimum_size = Vector2(50, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "Best"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="MaxHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"]
|
||||||
|
custom_minimum_size = Vector2(50, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "Worst"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="LastHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"]
|
||||||
|
custom_minimum_size = Vector2(50, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "Last"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="TotalHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"]
|
||||||
|
custom_minimum_size = Vector2(50, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "Total:"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="TotalAvg" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"]
|
||||||
|
modulate = Color(0, 1, 0, 1)
|
||||||
|
custom_minimum_size = Vector2(50, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "123.45"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="TotalMin" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"]
|
||||||
|
modulate = Color(0, 1, 0, 1)
|
||||||
|
custom_minimum_size = Vector2(50, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "123.45"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="TotalMax" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"]
|
||||||
|
modulate = Color(0, 1, 0, 1)
|
||||||
|
custom_minimum_size = Vector2(50, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "123.45"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="TotalLast" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"]
|
||||||
|
modulate = Color(0, 1, 0, 1)
|
||||||
|
custom_minimum_size = Vector2(50, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "123.45"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="CPUHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"]
|
||||||
|
custom_minimum_size = Vector2(50, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "CPU:"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="CPUAvg" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"]
|
||||||
|
modulate = Color(0, 1, 0, 1)
|
||||||
|
custom_minimum_size = Vector2(50, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "123.45"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="CPUMin" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"]
|
||||||
|
modulate = Color(0, 1, 0, 1)
|
||||||
|
custom_minimum_size = Vector2(50, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "12.34"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="CPUMax" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"]
|
||||||
|
modulate = Color(0, 1, 0, 1)
|
||||||
|
custom_minimum_size = Vector2(50, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "123.45"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="CPULast" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"]
|
||||||
|
modulate = Color(0, 1, 0, 1)
|
||||||
|
custom_minimum_size = Vector2(50, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "123.45"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="GPUHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"]
|
||||||
|
custom_minimum_size = Vector2(50, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "GPU:"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="GPUAvg" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"]
|
||||||
|
modulate = Color(0, 1, 0, 1)
|
||||||
|
custom_minimum_size = Vector2(50, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "123.45"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="GPUMin" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"]
|
||||||
|
modulate = Color(0, 1, 0, 1)
|
||||||
|
custom_minimum_size = Vector2(50, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "1.23"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="GPUMax" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"]
|
||||||
|
modulate = Color(0, 1, 0, 1)
|
||||||
|
custom_minimum_size = Vector2(50, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "123.45"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="GPULast" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"]
|
||||||
|
modulate = Color(0, 1, 0, 1)
|
||||||
|
custom_minimum_size = Vector2(50, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "123.45"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="FPSGraph" type="HBoxContainer" parent="DebugMenu/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
alignment = 2
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="DebugMenu/VBoxContainer/FPSGraph"]
|
||||||
|
custom_minimum_size = Vector2(0, 27)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 8
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "FPS: ↑"
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="Graph" type="Panel" parent="DebugMenu/VBoxContainer/FPSGraph"]
|
||||||
|
custom_minimum_size = Vector2(150, 25)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_ki0n8")
|
||||||
|
|
||||||
|
[node name="TotalGraph" type="HBoxContainer" parent="DebugMenu/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
alignment = 2
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="DebugMenu/VBoxContainer/TotalGraph"]
|
||||||
|
custom_minimum_size = Vector2(0, 27)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 8
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "Total: ↓"
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="Graph" type="Panel" parent="DebugMenu/VBoxContainer/TotalGraph"]
|
||||||
|
custom_minimum_size = Vector2(150, 25)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_ki0n8")
|
||||||
|
|
||||||
|
[node name="CPUGraph" type="HBoxContainer" parent="DebugMenu/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
alignment = 2
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="DebugMenu/VBoxContainer/CPUGraph"]
|
||||||
|
custom_minimum_size = Vector2(0, 27)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 8
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "CPU: ↓"
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="Graph" type="Panel" parent="DebugMenu/VBoxContainer/CPUGraph"]
|
||||||
|
custom_minimum_size = Vector2(150, 25)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_ki0n8")
|
||||||
|
|
||||||
|
[node name="GPUGraph" type="HBoxContainer" parent="DebugMenu/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
alignment = 2
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="DebugMenu/VBoxContainer/GPUGraph"]
|
||||||
|
custom_minimum_size = Vector2(0, 27)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 8
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "GPU: ↓"
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="Graph" type="Panel" parent="DebugMenu/VBoxContainer/GPUGraph"]
|
||||||
|
custom_minimum_size = Vector2(150, 25)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_ki0n8")
|
||||||
|
|
||||||
|
[node name="Information" type="Label" parent="DebugMenu/VBoxContainer"]
|
||||||
|
modulate = Color(1, 1, 1, 0.752941)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "12th Gen Intel(R) Core(TM) i0-1234K
|
||||||
|
Windows 12 64-bit (double precision), Vulkan 1.2.34
|
||||||
|
NVIDIA GeForce RTX 1234, 123.45.67"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="Settings" type="Label" parent="DebugMenu/VBoxContainer"]
|
||||||
|
modulate = Color(0.8, 0.84, 1, 0.752941)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "Project Version: 1.2.3
|
||||||
|
Rendering Method: Forward+
|
||||||
|
Window: 1234×567, Viewport: 1234×567
|
||||||
|
3D Scale (FSR 1.0): 100% = 1234×567
|
||||||
|
3D Antialiasing: TAA + 2× MSAA + FXAA
|
||||||
|
SSR: 123 Steps
|
||||||
|
SSAO: On
|
||||||
|
SSIL: On
|
||||||
|
SDFGI: 1 Cascades
|
||||||
|
Glow: On
|
||||||
|
Volumetric Fog: On
|
||||||
|
2D Antialiasing: 2× MSAA"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[connection signal="visibility_changed" from="." to="." method="_on_visibility_changed"]
|
7
addons/debug_menu/plugin.cfg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[plugin]
|
||||||
|
|
||||||
|
name="Debug Menu"
|
||||||
|
description="In-game debug menu displaying performance metrics and hardware information"
|
||||||
|
author="Calinou"
|
||||||
|
version="1.2.0"
|
||||||
|
script="plugin.gd"
|
29
addons/debug_menu/plugin.gd
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
@tool
|
||||||
|
extends EditorPlugin
|
||||||
|
|
||||||
|
func _enter_tree() -> void:
|
||||||
|
add_autoload_singleton("DebugMenu", "res://addons/debug_menu/debug_menu.tscn")
|
||||||
|
|
||||||
|
# FIXME: This appears to do nothing.
|
||||||
|
# if not ProjectSettings.has_setting("application/config/version"):
|
||||||
|
# ProjectSettings.set_setting("application/config/version", "1.0.0")
|
||||||
|
#
|
||||||
|
# ProjectSettings.set_initial_value("application/config/version", "1.0.0")
|
||||||
|
# ProjectSettings.add_property_info({
|
||||||
|
# name = "application/config/version",
|
||||||
|
# type = TYPE_STRING,
|
||||||
|
# })
|
||||||
|
#
|
||||||
|
# if not InputMap.has_action("cycle_debug_menu"):
|
||||||
|
# InputMap.add_action("cycle_debug_menu")
|
||||||
|
# var event := InputEventKey.new()
|
||||||
|
# event.keycode = KEY_F3
|
||||||
|
# InputMap.action_add_event("cycle_debug_menu", event)
|
||||||
|
#
|
||||||
|
# ProjectSettings.save()
|
||||||
|
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
remove_autoload_singleton("DebugMenu")
|
||||||
|
# Don't remove the project setting's value and input map action,
|
||||||
|
# as the plugin may be re-enabled in the future.
|
BIN
addons/project-statistics/icon.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
48
addons/project-statistics/icons/circle.svg
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
viewBox="0 0 64 64"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||||
|
sodipodi:docname="circle.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#111111"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:pageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="2"
|
||||||
|
inkscape:cx="-81"
|
||||||
|
inkscape:cy="16.25"
|
||||||
|
inkscape:window-width="1366"
|
||||||
|
inkscape:window-height="716"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:#ffffff;stroke-width:3;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
id="path846"
|
||||||
|
cx="32"
|
||||||
|
cy="32"
|
||||||
|
r="32" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
58
addons/project-statistics/icons/json.svg
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
viewBox="0 0 64 64"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||||
|
sodipodi:docname="json.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#111111"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:pageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="21.124815"
|
||||||
|
inkscape:cx="4.165717"
|
||||||
|
inkscape:cy="13.798937"
|
||||||
|
inkscape:window-width="1366"
|
||||||
|
inkscape:window-height="716"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<g
|
||||||
|
aria-label="{}"
|
||||||
|
id="text3762"
|
||||||
|
style="font-weight:600;font-size:13.3333px;line-height:1.25;font-family:'Open Sans';-inkscape-font-specification:'Open Sans Semi-Bold';text-align:center;letter-spacing:2px;text-anchor:middle;fill:#ffff00;stroke-width:0.249993"
|
||||||
|
transform="matrix(4.0001062,0,0,4.0001062,-8.4879998e-4,-8.5180006e-4)">
|
||||||
|
<path
|
||||||
|
d="m 3.9759191,9.8359333 q 0,-1.2109344 -1.7317665,-1.2109344 V 7.3815124 q 0.8789041,0 1.3020801,-0.292968 Q 3.9759191,6.789066 3.9759191,6.1835988 V 4.1588643 q 0,-1.0156224 0.7031232,-1.4908816 Q 5.3886759,2.1862131 6.8014328,2.1862131 V 3.3711059 Q 6.0592472,3.4036579 5.7207064,3.6770947 5.3886759,3.9440211 5.3886759,4.4778739 v 1.9335889 q 0,1.2955697 -1.4908816,1.5494753 v 0.078125 q 1.4908816,0.2343744 1.4908816,1.5429648 v 1.9466091 q 0,0.533853 0.3320305,0.80729 0.3320304,0.273437 1.0807264,0.286458 v 1.191403 Q 5.2975303,13.800767 4.6334695,13.299466 3.9759191,12.804676 3.9759191,11.691397 Z"
|
||||||
|
id="path13307"
|
||||||
|
style="stroke-width:0.249993" />
|
||||||
|
<path
|
||||||
|
d="m 12.115226,11.834626 q 0,1.015623 -0.65104,1.490882 -0.644529,0.475259 -2.070307,0.48828 v -1.191403 q 0.618488,-0.0065 0.963539,-0.253906 0.345051,-0.240885 0.345051,-0.839842 V 9.8229125 q 0,-0.7877584 0.345052,-1.2174448 0.345051,-0.4296864 1.14583,-0.5664048 V 7.9609381 Q 10.702469,7.7070325 10.702469,6.4114628 V 4.4778739 q 0,-0.5338528 -0.299478,-0.8007792 Q 10.110023,3.4036579 9.393879,3.3711059 V 2.1862131 q 1.451819,0 2.083328,0.5013008 0.638019,0.4947904 0.638019,1.6276 v 1.8684849 q 0,0.65104 0.410156,0.9244768 0.416665,0.2734368 1.230465,0.2734368 v 1.2434865 q -0.800779,0 -1.223955,0.2799472 -0.416666,0.2734368 -0.416666,0.9309872 z"
|
||||||
|
id="path13309"
|
||||||
|
style="stroke-width:0.249993" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<style
|
||||||
|
id="style1218">.st0{fill:#999}</style>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
48
addons/project-statistics/icons/markdown.svg
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
viewBox="0 0 64 64"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||||
|
sodipodi:docname="markdown.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#111111"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:pageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="3.734375"
|
||||||
|
inkscape:cx="16.870293"
|
||||||
|
inkscape:cy="24.769875"
|
||||||
|
inkscape:window-width="1366"
|
||||||
|
inkscape:window-height="716"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
id="path1224"
|
||||||
|
style="fill:#ffffff;stroke-width:4.49999"
|
||||||
|
d="M 5.3360002,10.222707 V 53.777293 H 58.664 V 10.222707 Z m 6.6640468,12.093722 h 5.687486 l 5.687487,7.109358 5.695299,-7.109358 h 5.687486 V 41.691381 H 29.070319 V 30.582034 l -5.695299,7.109357 -5.687487,-7.109357 v 11.109347 h -5.687486 z m 31.28899,0 h 5.687486 v 9.953101 h 5.687487 l -8.531232,9.421851 -8.53123,-9.421851 h 5.687489 z" />
|
||||||
|
</g>
|
||||||
|
<style
|
||||||
|
id="style1218">.st0{fill:#999}</style>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
66
addons/project-statistics/icons/yaml.svg
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
viewBox="0 0 64 64"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||||
|
sodipodi:docname="yaml.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#111111"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:pageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="5.281204"
|
||||||
|
inkscape:cx="35.597943"
|
||||||
|
inkscape:cy="31.526902"
|
||||||
|
inkscape:window-width="1366"
|
||||||
|
inkscape:window-height="716"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<g
|
||||||
|
aria-label="YA ML"
|
||||||
|
id="text3762"
|
||||||
|
style="font-weight:800;font-size:8px;line-height:1;font-family:'Open Sans';-inkscape-font-specification:'Open Sans Ultra-Bold';letter-spacing:0px;fill:#ff5555;stroke-width:0.249995"
|
||||||
|
transform="matrix(4.0000825,0,0,4.0000825,-0.59442325,-6.6100002e-4)">
|
||||||
|
<path
|
||||||
|
d="M 4.7578125,3.2734375 5.7421875,1.15625 h 1.671875 L 5.5351562,4.6367187 V 6.8671875 H 3.9804688 V 4.6835937 L 2.1015625,1.15625 H 3.78125 Z"
|
||||||
|
id="path24552"
|
||||||
|
style="stroke-width:0.249995" />
|
||||||
|
<path
|
||||||
|
d="M 11.054688,6.8671875 10.773438,5.796875 H 8.9179688 L 8.6289063,6.8671875 H 6.9335937 L 8.796875,1.1328125 h 2.058594 l 1.886719,5.734375 z M 10.453125,4.53125 10.207031,3.59375 Q 10.121094,3.28125 9.9960938,2.7851563 9.875,2.2890625 9.8359375,2.0742188 9.8007813,2.2734375 9.6953125,2.7304688 9.59375,3.1875 9.2382813,4.53125 Z"
|
||||||
|
id="path24554"
|
||||||
|
style="stroke-width:0.249995" />
|
||||||
|
<path
|
||||||
|
d="M 5.2070312,14.867188 4.0390625,10.753906 h -0.035156 q 0.082031,1.050782 0.082031,1.632813 v 2.480469 H 2.71875 V 9.15625 h 2.0546875 l 1.1914062,4.054688 h 0.03125 L 7.1640625,9.15625 h 2.0585938 v 5.710938 H 7.8046875 v -2.503907 q 0,-0.195312 0.00391,-0.433593 0.00781,-0.238282 0.054687,-1.167969 H 7.828125 l -1.1523438,4.105469 z"
|
||||||
|
id="path24556"
|
||||||
|
style="stroke-width:0.249995" />
|
||||||
|
<path
|
||||||
|
d="M 10.453125,14.867188 V 9.15625 h 1.542969 v 4.464844 h 2.199219 v 1.246094 z"
|
||||||
|
id="path24558"
|
||||||
|
style="stroke-width:0.249995" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<style
|
||||||
|
id="style1218">.st0{fill:#999}</style>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
65
addons/project-statistics/loaders/FileStatistics.gd
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
var path: String
|
||||||
|
var size: int
|
||||||
|
var total_lines: int
|
||||||
|
var source_code_lines: int
|
||||||
|
var comment_lines: int
|
||||||
|
var blank_lines: int
|
||||||
|
|
||||||
|
func _load_dir(at_path: String) -> int:
|
||||||
|
return _load_file_info(at_path)
|
||||||
|
|
||||||
|
func _load_file_info(at_path: String, skip_line_count: bool = false) -> int:
|
||||||
|
var file := FileAccess.open(at_path, FileAccess.READ)
|
||||||
|
if !file:
|
||||||
|
return FileAccess.get_open_error()
|
||||||
|
|
||||||
|
path = at_path
|
||||||
|
size = file.get_length()
|
||||||
|
|
||||||
|
total_lines = 0
|
||||||
|
source_code_lines = 0
|
||||||
|
comment_lines = 0
|
||||||
|
blank_lines = 0
|
||||||
|
|
||||||
|
if skip_line_count:
|
||||||
|
return OK
|
||||||
|
|
||||||
|
while not file.eof_reached():
|
||||||
|
var line: String = file.get_line()
|
||||||
|
if _is_comment(line):
|
||||||
|
comment_lines += 1
|
||||||
|
elif is_blank(line):
|
||||||
|
blank_lines += 1
|
||||||
|
else:
|
||||||
|
source_code_lines += 1
|
||||||
|
total_lines +=1
|
||||||
|
|
||||||
|
file.close()
|
||||||
|
return OK
|
||||||
|
|
||||||
|
func get_name() -> String:
|
||||||
|
return path.get_file()
|
||||||
|
|
||||||
|
func _get_extension() -> String:
|
||||||
|
return path.get_extension()
|
||||||
|
|
||||||
|
func _get_icon() -> String:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
func _get_color() -> Color:
|
||||||
|
return Color.TRANSPARENT
|
||||||
|
|
||||||
|
func _is_comment(line: String) -> bool:
|
||||||
|
return false
|
||||||
|
|
||||||
|
func is_blank(line: String) -> bool:
|
||||||
|
return line.strip_edges().is_empty()
|
||||||
|
|
||||||
|
func _is_script() -> bool:
|
||||||
|
return false
|
||||||
|
|
||||||
|
func _is_scene() -> bool:
|
||||||
|
return false
|
||||||
|
|
||||||
|
func _is_resource() -> bool:
|
||||||
|
return false
|
158
addons/project-statistics/loaders/ProjectStatistics.gd
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
const FileStatistics: Script = preload("./FileStatistics.gd")
|
||||||
|
const ConfigFileStatistics: Script = preload("./extensions/ConfigFileStatistics.gd")
|
||||||
|
const CSharpStatistics: Script = preload("./extensions/CSharpStatistics.gd")
|
||||||
|
const GDScriptStatistics: Script = preload("./extensions/GDScriptStatistics.gd")
|
||||||
|
const JSONStatistics: Script = preload("./extensions/JSONStatistics.gd")
|
||||||
|
const MarkdownStatistics: Script = preload("./extensions/MarkdownStatistics.gd")
|
||||||
|
const ResourceStatistics: Script = preload("./extensions/ResourceStatistics.gd")
|
||||||
|
const YAMLStatistics: Script = preload("./extensions/YAMLStatistics.gd")
|
||||||
|
|
||||||
|
var directories: PackedStringArray
|
||||||
|
|
||||||
|
var scenes: Array
|
||||||
|
var resources: Array
|
||||||
|
var scripts: Array
|
||||||
|
var misc: Array
|
||||||
|
|
||||||
|
func _load_dir(root := "res://") -> int:
|
||||||
|
if !path_force_included(root) && path_ignored(root):
|
||||||
|
return OK
|
||||||
|
var directory := DirAccess.open(root)
|
||||||
|
if !directory:
|
||||||
|
return DirAccess.get_open_error()
|
||||||
|
for dir in directory.get_directories():
|
||||||
|
_load_dir(root + dir + "/")
|
||||||
|
for file_name in directory.get_files():
|
||||||
|
var current_path: String = root + file_name
|
||||||
|
if !path_force_included(current_path) && path_ignored(current_path):
|
||||||
|
continue
|
||||||
|
var file_stats: FileStatistics = get_file_loader(current_path)
|
||||||
|
if file_stats._load_dir(current_path) == OK:
|
||||||
|
if file_stats._is_scene():
|
||||||
|
scenes.append(file_stats)
|
||||||
|
elif file_stats._is_script():
|
||||||
|
scripts.append(file_stats)
|
||||||
|
elif file_stats._is_resource():
|
||||||
|
resources.append(file_stats)
|
||||||
|
else:
|
||||||
|
misc.append(file_stats)
|
||||||
|
return OK
|
||||||
|
|
||||||
|
func get_used_langauges() -> PackedStringArray:
|
||||||
|
var languages: PackedStringArray = []
|
||||||
|
for file_stats in scripts:
|
||||||
|
var extension: String = file_stats._get_extension()
|
||||||
|
if not file_stats._get_extension() in languages:
|
||||||
|
languages.append(extension)
|
||||||
|
return languages
|
||||||
|
|
||||||
|
func get_total_lines(of_scripts: bool = true) -> int:
|
||||||
|
var source: Array = scripts if of_scripts else misc
|
||||||
|
var total: int
|
||||||
|
for file_stats in source:
|
||||||
|
total += file_stats.total_lines
|
||||||
|
return total
|
||||||
|
|
||||||
|
func get_total_code_lines(of_scripts: bool = true) -> int:
|
||||||
|
var source: Array = scripts if of_scripts else misc
|
||||||
|
var total: int
|
||||||
|
for file_stats in source:
|
||||||
|
total += file_stats.source_code_lines
|
||||||
|
return total
|
||||||
|
|
||||||
|
func get_total_comment_lines(of_scripts: bool = true) -> int:
|
||||||
|
var source: Array = scripts if of_scripts else misc
|
||||||
|
var total: int
|
||||||
|
for file_stats in source:
|
||||||
|
total += file_stats.comment_lines
|
||||||
|
return total
|
||||||
|
|
||||||
|
func get_total_blank_lines(of_scripts: bool = true) -> int:
|
||||||
|
var source: Array = scripts if of_scripts else misc
|
||||||
|
var total: int
|
||||||
|
for file_stats in source:
|
||||||
|
total += file_stats.blank_lines
|
||||||
|
return total
|
||||||
|
|
||||||
|
func get_total_nodes() -> int:
|
||||||
|
var total: int
|
||||||
|
for file_stats in scenes:
|
||||||
|
total += file_stats.node_count
|
||||||
|
return total
|
||||||
|
|
||||||
|
func get_total_connections() -> int:
|
||||||
|
var total: int
|
||||||
|
for file_stats in scenes:
|
||||||
|
total += file_stats.connection_count
|
||||||
|
return total
|
||||||
|
|
||||||
|
func get_total_scenes_size() -> int:
|
||||||
|
var total: int
|
||||||
|
for file_stats in scenes:
|
||||||
|
total += file_stats.size
|
||||||
|
return total
|
||||||
|
|
||||||
|
func get_total_scripts_size() -> int:
|
||||||
|
var total: int
|
||||||
|
for file_stats in scripts:
|
||||||
|
total += file_stats.size
|
||||||
|
return total
|
||||||
|
|
||||||
|
func get_other_files_size() -> int:
|
||||||
|
var total: int
|
||||||
|
for file_stats in misc:
|
||||||
|
total += file_stats.size
|
||||||
|
return total
|
||||||
|
|
||||||
|
func get_file_loader(file_path: String) -> FileStatistics:
|
||||||
|
match file_path.get_extension().to_lower():
|
||||||
|
"cs":
|
||||||
|
return CSharpStatistics.new()
|
||||||
|
"ini", "cfg":
|
||||||
|
return ConfigFileStatistics.new()
|
||||||
|
"gd":
|
||||||
|
return GDScriptStatistics.new()
|
||||||
|
"md":
|
||||||
|
return MarkdownStatistics.new()
|
||||||
|
"json":
|
||||||
|
return JSONStatistics.new()
|
||||||
|
"yml", "yaml":
|
||||||
|
return YAMLStatistics.new()
|
||||||
|
|
||||||
|
return ResourceStatistics.new()
|
||||||
|
|
||||||
|
func duplicate():
|
||||||
|
var clone = get_script().new()
|
||||||
|
clone.directories = directories
|
||||||
|
clone.scenes = scenes.duplicate()
|
||||||
|
clone.resources = resources.duplicate()
|
||||||
|
clone.scripts = scripts.duplicate()
|
||||||
|
clone.misc = misc.duplicate()
|
||||||
|
return clone
|
||||||
|
|
||||||
|
static func is_path_ignored(path: String) -> bool:
|
||||||
|
if not Engine.is_editor_hint():
|
||||||
|
return false
|
||||||
|
var ignores: PackedStringArray = ProjectSettings.get_setting("statistics/ignore")
|
||||||
|
for expression in ignores:
|
||||||
|
if path.matchn(expression):
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
static func path_ignored(path: String) -> bool:
|
||||||
|
#if not Engine.is_editor_hint():
|
||||||
|
#return false
|
||||||
|
var expressions: PackedStringArray = ProjectSettings.get_setting("statistics/ignore")
|
||||||
|
for expression in expressions:
|
||||||
|
if path.matchn(expression):
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
static func path_force_included(path: String) -> bool:
|
||||||
|
#if not Engine.is_editor_hint():
|
||||||
|
#return false
|
||||||
|
var expressions: PackedStringArray = ProjectSettings.get_setting("statistics/force_include")
|
||||||
|
for expression in expressions:
|
||||||
|
if path.matchn(expression):
|
||||||
|
return true
|
||||||
|
return false
|
@ -0,0 +1,20 @@
|
|||||||
|
extends "../FileStatistics.gd"
|
||||||
|
|
||||||
|
func _is_comment(line: String) -> bool:
|
||||||
|
line = line.strip_edges()
|
||||||
|
return (line.begins_with("//")
|
||||||
|
or line.begins_with("/*")
|
||||||
|
or line.begins_with("*")
|
||||||
|
or line.ends_with("*/"))
|
||||||
|
|
||||||
|
func _get_extension() -> String:
|
||||||
|
return "C#"
|
||||||
|
|
||||||
|
func _get_icon() -> String:
|
||||||
|
return "CSharpScript"
|
||||||
|
|
||||||
|
func _get_color() -> Color:
|
||||||
|
return Color.LIME_GREEN
|
||||||
|
|
||||||
|
func _is_script() -> bool:
|
||||||
|
return true
|
@ -0,0 +1,13 @@
|
|||||||
|
extends "../FileStatistics.gd"
|
||||||
|
|
||||||
|
func _get_extension() -> String:
|
||||||
|
return "Config File"
|
||||||
|
|
||||||
|
func _get_color() -> Color:
|
||||||
|
return Color.TEAL
|
||||||
|
|
||||||
|
func _get_icon() -> String:
|
||||||
|
return "File"
|
||||||
|
|
||||||
|
func _is_comment(line: String) -> bool:
|
||||||
|
return line.strip_edges().begins_with(";")
|
@ -0,0 +1,17 @@
|
|||||||
|
extends "../FileStatistics.gd"
|
||||||
|
|
||||||
|
func _is_comment(line: String) -> bool:
|
||||||
|
# TODO: Detect multi-line comments
|
||||||
|
return line.strip_edges().begins_with("#")
|
||||||
|
|
||||||
|
func _get_extension() -> String:
|
||||||
|
return "GDScript"
|
||||||
|
|
||||||
|
func _get_icon() -> String:
|
||||||
|
return "GDScript"
|
||||||
|
|
||||||
|
func _get_color() -> Color:
|
||||||
|
return Color.STEEL_BLUE
|
||||||
|
|
||||||
|
func _is_script() -> bool:
|
||||||
|
return true
|
@ -0,0 +1,12 @@
|
|||||||
|
extends "../FileStatistics.gd"
|
||||||
|
|
||||||
|
const ICON: Texture = preload("../../icons/json.svg")
|
||||||
|
|
||||||
|
func _get_extension() -> String:
|
||||||
|
return "JSON"
|
||||||
|
|
||||||
|
func _get_color() -> Color:
|
||||||
|
return Color.GOLD
|
||||||
|
|
||||||
|
func _get_icon() -> String:
|
||||||
|
return ICON.resource_path
|
@ -0,0 +1,12 @@
|
|||||||
|
extends "../FileStatistics.gd"
|
||||||
|
|
||||||
|
const ICON: Texture = preload("../../icons/markdown.svg")
|
||||||
|
|
||||||
|
func _get_extension() -> String:
|
||||||
|
return "Markdown"
|
||||||
|
|
||||||
|
func _get_color() -> Color:
|
||||||
|
return Color.GHOST_WHITE
|
||||||
|
|
||||||
|
func _get_icon() -> String:
|
||||||
|
return ICON.resource_path
|
@ -0,0 +1,48 @@
|
|||||||
|
extends "../FileStatistics.gd"
|
||||||
|
|
||||||
|
var local_to_scene: bool
|
||||||
|
var type: String
|
||||||
|
var base_node_type: String
|
||||||
|
var node_count: int
|
||||||
|
var connection_count: int
|
||||||
|
|
||||||
|
func _load_dir(at_path: String) -> int:
|
||||||
|
if ResourceLoader.exists(at_path):
|
||||||
|
var resource := ResourceLoader.load(at_path)
|
||||||
|
if resource:
|
||||||
|
_load_file_info(at_path, true)
|
||||||
|
_load_resource(resource)
|
||||||
|
return OK
|
||||||
|
return ERR_CANT_ACQUIRE_RESOURCE
|
||||||
|
|
||||||
|
func _load_resource(resource: Resource) -> void:
|
||||||
|
local_to_scene = resource.resource_local_to_scene
|
||||||
|
type = resource.get_class()
|
||||||
|
if resource is PackedScene:
|
||||||
|
var scene: PackedScene = resource as PackedScene
|
||||||
|
var state: SceneState = scene.get_state()
|
||||||
|
base_node_type = state.get_node_type(0)
|
||||||
|
node_count = state.get_node_count()
|
||||||
|
connection_count = state.get_connection_count()
|
||||||
|
|
||||||
|
func _get_extension() -> String:
|
||||||
|
return type
|
||||||
|
|
||||||
|
func _get_icon() -> String:
|
||||||
|
return type
|
||||||
|
|
||||||
|
func _get_color() -> Color:
|
||||||
|
if type == "VisualScript":
|
||||||
|
return Color.AZURE
|
||||||
|
var color = Color(hash(type)).inverted()
|
||||||
|
color.a = 1.0
|
||||||
|
return color
|
||||||
|
|
||||||
|
func _is_scene() -> bool:
|
||||||
|
return ClassDB.is_parent_class(type, "PackedScene")
|
||||||
|
|
||||||
|
func _is_script() -> bool:
|
||||||
|
return ClassDB.is_parent_class(type, "Script")
|
||||||
|
|
||||||
|
func _is_resource() -> bool:
|
||||||
|
return true
|
@ -0,0 +1,15 @@
|
|||||||
|
extends "../FileStatistics.gd"
|
||||||
|
|
||||||
|
const ICON: Texture = preload("../../icons/yaml.svg")
|
||||||
|
|
||||||
|
func _get_extension() -> String:
|
||||||
|
return "YAMl"
|
||||||
|
|
||||||
|
func _get_color() -> Color:
|
||||||
|
return Color.MEDIUM_PURPLE
|
||||||
|
|
||||||
|
func _get_icon() -> String:
|
||||||
|
return ICON.resource_path
|
||||||
|
|
||||||
|
func _is_comment(line: String) -> bool:
|
||||||
|
return line.strip_edges().begins_with("#")
|
136
addons/project-statistics/nodes/MiscView.gd
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
@tool
|
||||||
|
extends "./TreeView.gd"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
NAME_COLUMN,
|
||||||
|
EXTENSION_COLUMN,
|
||||||
|
TOTAL_LINES_COLUMN,
|
||||||
|
SOURCE_LINES_COLUMN,
|
||||||
|
COMMENT_LINES_COLUMN,
|
||||||
|
BLANK_LINES_COLUMN,
|
||||||
|
SIZE_COLUMN
|
||||||
|
}
|
||||||
|
|
||||||
|
var total_files: TreeItem
|
||||||
|
var total_lines: TreeItem
|
||||||
|
var total_code_lines: TreeItem
|
||||||
|
var total_comments_lines: TreeItem
|
||||||
|
var total_blank_lines: TreeItem
|
||||||
|
var total_size: TreeItem
|
||||||
|
|
||||||
|
@onready var graph: Control = $VSplitContainer/HSplitContainer/MarginContainer/PieGraph
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
tree = $VSplitContainer/Tree
|
||||||
|
summary_tree = $VSplitContainer/HSplitContainer/SummaryTree
|
||||||
|
|
||||||
|
tree.set_column_title(NAME_COLUMN, "File name")
|
||||||
|
tree.set_column_title(EXTENSION_COLUMN, "Extension")
|
||||||
|
tree.set_column_title(TOTAL_LINES_COLUMN, "Total lines")
|
||||||
|
tree.set_column_title(SOURCE_LINES_COLUMN, "Source lines")
|
||||||
|
tree.set_column_title(COMMENT_LINES_COLUMN, "Comment lines")
|
||||||
|
tree.set_column_title(BLANK_LINES_COLUMN, "Blank lines")
|
||||||
|
tree.set_column_title(SIZE_COLUMN, "Size")
|
||||||
|
tree.set_column_titles_visible(true)
|
||||||
|
tree.hide_root = true
|
||||||
|
|
||||||
|
tree.connect("item_activated", _on_item_activated)
|
||||||
|
tree.connect("column_title_clicked", _on_column_title_clicked)
|
||||||
|
|
||||||
|
var root: TreeItem = summary_tree.create_item()
|
||||||
|
total_files = summary_tree.create_item(root)
|
||||||
|
total_lines = summary_tree.create_item(root)
|
||||||
|
total_code_lines = summary_tree.create_item(root)
|
||||||
|
total_comments_lines = summary_tree.create_item(root)
|
||||||
|
total_blank_lines = summary_tree.create_item(root)
|
||||||
|
total_size = summary_tree.create_item(root)
|
||||||
|
|
||||||
|
total_files.set_text(0, "Total files")
|
||||||
|
total_lines.set_text(0, "Total lines")
|
||||||
|
total_code_lines.set_text(0, "Total code lines")
|
||||||
|
total_comments_lines.set_text(0, "Total comments lines")
|
||||||
|
total_blank_lines.set_text(0, "Total blank lines")
|
||||||
|
total_size.set_text(0, "Total size")
|
||||||
|
summary_tree.hide_root = true
|
||||||
|
|
||||||
|
func display(stats: ProjectStatistics) -> void:
|
||||||
|
super.display(stats)
|
||||||
|
total_files.set_text(1, str(stats.misc.size()))
|
||||||
|
total_lines.set_text(1, str(stats.get_total_lines(false)))
|
||||||
|
total_code_lines.set_text(1, str(stats.get_total_code_lines(false)))
|
||||||
|
total_comments_lines.set_text(1, str(stats.get_total_comment_lines(false)))
|
||||||
|
total_blank_lines.set_text(1, str(stats.get_total_blank_lines(false)))
|
||||||
|
total_size.set_text(1, String.humanize_size(stats.get_other_files_size()))
|
||||||
|
|
||||||
|
var series: Dictionary = {}
|
||||||
|
for file_stats in stats.misc:
|
||||||
|
var name: String = file_stats.get_name()
|
||||||
|
var extension: String = file_stats._get_extension()
|
||||||
|
var color: Color = file_stats._get_color()
|
||||||
|
|
||||||
|
var chart_data: ChartData
|
||||||
|
if not series.has(extension):
|
||||||
|
chart_data = ChartData.new()
|
||||||
|
chart_data.name = extension
|
||||||
|
chart_data.color = color
|
||||||
|
series[extension] = chart_data
|
||||||
|
else:
|
||||||
|
chart_data = series[extension]
|
||||||
|
chart_data.value += file_stats.size
|
||||||
|
graph.set_series(series)
|
||||||
|
|
||||||
|
|
||||||
|
func update_tree(stats: ProjectStatistics) -> void:
|
||||||
|
tree.clear()
|
||||||
|
var root: TreeItem = tree.create_item()
|
||||||
|
for file_stats in stats.misc:
|
||||||
|
var item: TreeItem = tree.create_item(root)
|
||||||
|
|
||||||
|
item.set_text(NAME_COLUMN, file_stats.get_name())
|
||||||
|
item.set_tooltip_text(NAME_COLUMN, file_stats.path)
|
||||||
|
item.set_metadata(NAME_COLUMN, file_stats.path)
|
||||||
|
|
||||||
|
item.set_text(EXTENSION_COLUMN, file_stats._get_extension())
|
||||||
|
item.set_metadata(EXTENSION_COLUMN, file_stats._get_icon())
|
||||||
|
|
||||||
|
item.set_text(TOTAL_LINES_COLUMN, str(file_stats.total_lines))
|
||||||
|
item.set_text(SOURCE_LINES_COLUMN, str(file_stats.source_code_lines))
|
||||||
|
item.set_text(COMMENT_LINES_COLUMN, str(file_stats.comment_lines))
|
||||||
|
item.set_text(BLANK_LINES_COLUMN, str(file_stats.blank_lines))
|
||||||
|
|
||||||
|
_format_size(item, SIZE_COLUMN, file_stats.size)
|
||||||
|
|
||||||
|
func update_icons() -> void:
|
||||||
|
var root: TreeItem = tree.get_root()
|
||||||
|
if not root:
|
||||||
|
return
|
||||||
|
var children:= root.get_children()
|
||||||
|
for next in children:
|
||||||
|
var icon_path: String = next.get_metadata(EXTENSION_COLUMN)
|
||||||
|
var icon: Texture
|
||||||
|
if has_theme_icon(icon_path, "EditorIcons"):
|
||||||
|
icon = get_theme_icon(icon_path, "EditorIcons")
|
||||||
|
elif ResourceLoader.exists(icon_path):
|
||||||
|
icon = ResourceLoader.load(icon_path)
|
||||||
|
next.set_icon(NAME_COLUMN, icon)
|
||||||
|
next.set_icon_max_width(NAME_COLUMN, 16)
|
||||||
|
next.set_icon(TOTAL_LINES_COLUMN, get_theme_icon("AnimationTrackList", "EditorIcons"))
|
||||||
|
next.set_icon(SOURCE_LINES_COLUMN, get_theme_icon("CodeFoldedRightArrow", "EditorIcons"))
|
||||||
|
next.set_icon(COMMENT_LINES_COLUMN, get_theme_icon("VisualShaderNodeComment", "EditorIcons"))
|
||||||
|
|
||||||
|
func _sort_by_column(column: int) -> void:
|
||||||
|
match column:
|
||||||
|
NAME_COLUMN:
|
||||||
|
stats.misc.sort_custom(sort_name)
|
||||||
|
EXTENSION_COLUMN:
|
||||||
|
stats.misc.sort_custom(sort_extension)
|
||||||
|
TOTAL_LINES_COLUMN:
|
||||||
|
stats.misc.sort_custom(sort_total_lines)
|
||||||
|
SOURCE_LINES_COLUMN:
|
||||||
|
stats.misc.sort_custom(sort_source_code_lines)
|
||||||
|
COMMENT_LINES_COLUMN:
|
||||||
|
stats.misc.sort_custom(sort_comment_lines)
|
||||||
|
BLANK_LINES_COLUMN:
|
||||||
|
stats.misc.sort_custom(sort_blank_lines)
|
||||||
|
SIZE_COLUMN:
|
||||||
|
stats.misc.sort_custom(sort_size)
|
27
addons/project-statistics/nodes/MiscView.tscn
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://ddli62n5pmlvn"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/project-statistics/nodes/MiscView.gd" id="1"]
|
||||||
|
[ext_resource type="PackedScene" path="res://addons/project-statistics/nodes/ScriptsView.tscn" id="2"]
|
||||||
|
|
||||||
|
[node name="MiscView" instance=ExtResource("2")]
|
||||||
|
anchors_preset = 15
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="VSplitContainer" parent="." index="0"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="HSplitContainer" parent="VSplitContainer" index="0"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="SummaryTree" parent="VSplitContainer/HSplitContainer" index="0"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="MarginContainer" parent="VSplitContainer/HSplitContainer" index="1"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="PieGraph" parent="VSplitContainer/HSplitContainer/MarginContainer" index="0"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Tree" parent="VSplitContainer" index="1"]
|
||||||
|
layout_mode = 2
|
||||||
|
column_titles_visible = true
|
62
addons/project-statistics/nodes/Overview.gd
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
@tool
|
||||||
|
extends "./StatisticsView.gd"
|
||||||
|
|
||||||
|
const BURNT_SIENNA: Color = Color("#EC6B56")
|
||||||
|
const CRAYOLA_MAIZE: Color = Color("#FFC154")
|
||||||
|
const KEPPEL: Color = Color("#47B39C")
|
||||||
|
|
||||||
|
var total_scenes: TreeItem
|
||||||
|
var total_resources: TreeItem
|
||||||
|
var total_scripts: TreeItem
|
||||||
|
var other_files: TreeItem
|
||||||
|
|
||||||
|
@onready var summary: Tree = $HSplitContainer/SummaryTree
|
||||||
|
@onready var graph: Control = $HSplitContainer/VBoxContainer/PieGraph
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
var root: TreeItem = summary.create_item()
|
||||||
|
total_scenes = summary.create_item(root)
|
||||||
|
total_resources = summary.create_item(root)
|
||||||
|
total_scripts = summary.create_item(root)
|
||||||
|
other_files = summary.create_item(root)
|
||||||
|
|
||||||
|
total_scenes.set_text(0, "Total scenes")
|
||||||
|
total_resources.set_text(0, "Total resources")
|
||||||
|
total_scripts.set_text(0, "Total scripts")
|
||||||
|
other_files.set_text(0, "Other files")
|
||||||
|
|
||||||
|
summary.hide_root = true
|
||||||
|
|
||||||
|
func display(stats: ProjectStatistics) -> void:
|
||||||
|
super.display(stats) #.display(stats)
|
||||||
|
total_scenes.set_text(1, str(stats.scenes.size()))
|
||||||
|
total_resources.set_text(1, str(stats.resources.size()))
|
||||||
|
total_scripts.set_text(1, str(stats.scripts.size()))
|
||||||
|
other_files.set_text(1, str(stats.misc.size()))
|
||||||
|
|
||||||
|
var series: Dictionary = {}
|
||||||
|
series["Scenes"] = _create_chart_data("Scenes", BURNT_SIENNA)
|
||||||
|
series["Resources"] = _create_chart_data("Resources", CRAYOLA_MAIZE)
|
||||||
|
series["Scripts"] = _create_chart_data("Scripts", KEPPEL)
|
||||||
|
series["Other"] = _create_chart_data("Other", Color.LIGHT_GRAY)
|
||||||
|
for file_stats in stats.scenes:
|
||||||
|
series["Scenes"].value += file_stats.size
|
||||||
|
for file_stats in stats.resources:
|
||||||
|
series["Resources"].value += file_stats.size
|
||||||
|
for file_stats in stats.scripts:
|
||||||
|
series["Scripts"].value += file_stats.size
|
||||||
|
for file_stats in stats.misc:
|
||||||
|
series["Other"].value += file_stats.size
|
||||||
|
graph.set_series(series)
|
||||||
|
|
||||||
|
func update_icons() -> void:
|
||||||
|
total_scenes.set_icon(0, get_theme_icon("PackedScene", "EditorIcons"))
|
||||||
|
total_resources.set_icon(0, get_theme_icon("Object", "EditorIcons"))
|
||||||
|
total_scripts.set_icon(0, get_theme_icon("Script", "EditorIcons"))
|
||||||
|
other_files.set_icon(0, get_theme_icon("File", "EditorIcons"))
|
||||||
|
|
||||||
|
func _create_chart_data(name: String, color: Color) -> ChartData:
|
||||||
|
var data = ChartData.new()
|
||||||
|
data.name = name
|
||||||
|
data.color = color
|
||||||
|
return data
|
28
addons/project-statistics/nodes/Overview.tscn
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://03754kkjn2ue"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://by7ltrt0iq35i" path="res://addons/project-statistics/nodes/charts/PieGraph.tscn" id="1"]
|
||||||
|
[ext_resource type="Script" path="res://addons/project-statistics/nodes/Overview.gd" id="3"]
|
||||||
|
|
||||||
|
[node name="Overview" type="MarginContainer"]
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
script = ExtResource("3")
|
||||||
|
|
||||||
|
[node name="HSplitContainer" type="HSplitContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="SummaryTree" type="Tree" parent="HSplitContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
columns = 2
|
||||||
|
hide_root = true
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="HSplitContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="PieGraph" parent="HSplitContainer/VBoxContainer" instance=ExtResource("1")]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 1
|
5
addons/project-statistics/nodes/PanelContainer.gd
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
@tool
|
||||||
|
extends PanelContainer
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
set("custom_styles/panel", get_theme_stylebox("Content", "EditorStyles"))
|
96
addons/project-statistics/nodes/ResourcesView.gd
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
@tool
|
||||||
|
extends "./TreeView.gd"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
NAME_COLUMN,
|
||||||
|
RESOURCE_TYPE_COLUMN,
|
||||||
|
LOCAL_TO_SCENE_COLUMN,
|
||||||
|
SIZE_COLUMN
|
||||||
|
}
|
||||||
|
|
||||||
|
var total_resources: TreeItem
|
||||||
|
var total_size: TreeItem
|
||||||
|
|
||||||
|
@onready var graph: Control = $VSplitContainer/HSplitContainer/MarginContainer/PieGraph
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
tree = $VSplitContainer/Tree
|
||||||
|
summary_tree = $VSplitContainer/HSplitContainer/SummaryTree
|
||||||
|
|
||||||
|
tree.set_column_title(NAME_COLUMN, "File name")
|
||||||
|
tree.set_column_title(RESOURCE_TYPE_COLUMN, "Type")
|
||||||
|
tree.set_column_title(LOCAL_TO_SCENE_COLUMN, "Local to scene")
|
||||||
|
tree.set_column_title(SIZE_COLUMN, "Size")
|
||||||
|
tree.set_column_titles_visible(true)
|
||||||
|
tree.hide_root = true
|
||||||
|
|
||||||
|
tree.connect("item_activated", _on_item_activated)
|
||||||
|
tree.connect("column_title_clicked", _on_column_title_clicked)
|
||||||
|
|
||||||
|
var root: TreeItem = summary_tree.create_item()
|
||||||
|
total_resources = summary_tree.create_item(root)
|
||||||
|
total_size = summary_tree.create_item(root)
|
||||||
|
|
||||||
|
total_resources.set_text(0, "Total resources")
|
||||||
|
total_size.set_text(0, "Total size")
|
||||||
|
summary_tree.hide_root = true
|
||||||
|
|
||||||
|
func display(stats: ProjectStatistics) -> void:
|
||||||
|
#.display(stats)
|
||||||
|
super.display(stats)
|
||||||
|
total_resources.set_text(1, str(stats.resources.size()))
|
||||||
|
total_size.set_text(1, String.humanize_size(stats.get_total_scripts_size()))
|
||||||
|
|
||||||
|
var series: Dictionary = {}
|
||||||
|
for file_stats in stats.resources:
|
||||||
|
var name: String = file_stats.get_name()
|
||||||
|
var extension: String = file_stats._get_extension()
|
||||||
|
var color: Color = file_stats._get_color()
|
||||||
|
|
||||||
|
var chart_data: ChartData
|
||||||
|
if not series.has(extension):
|
||||||
|
chart_data = ChartData.new()
|
||||||
|
chart_data.name = extension
|
||||||
|
chart_data.color = color
|
||||||
|
series[extension] = chart_data
|
||||||
|
else:
|
||||||
|
chart_data = series[extension]
|
||||||
|
chart_data.value += file_stats.size
|
||||||
|
graph.set_series(series)
|
||||||
|
|
||||||
|
func update_tree(stats: ProjectStatistics) -> void:
|
||||||
|
tree.clear()
|
||||||
|
var root: TreeItem = tree.create_item()
|
||||||
|
for file_stats in stats.resources:
|
||||||
|
var item: TreeItem = tree.create_item(root)
|
||||||
|
item.set_cell_mode(LOCAL_TO_SCENE_COLUMN, TreeItem.CELL_MODE_CHECK)
|
||||||
|
|
||||||
|
item.set_text(NAME_COLUMN, file_stats.get_name())
|
||||||
|
item.set_tooltip_text(NAME_COLUMN, file_stats.path)
|
||||||
|
item.set_metadata(NAME_COLUMN, file_stats.path)
|
||||||
|
|
||||||
|
item.set_text(RESOURCE_TYPE_COLUMN, file_stats.type)
|
||||||
|
item.set_checked(LOCAL_TO_SCENE_COLUMN, file_stats.local_to_scene)
|
||||||
|
|
||||||
|
_format_size(item, SIZE_COLUMN, file_stats.size)
|
||||||
|
|
||||||
|
func update_icons() -> void:
|
||||||
|
var root: TreeItem = tree.get_root()
|
||||||
|
if not root:
|
||||||
|
return
|
||||||
|
var children:= root.get_children()
|
||||||
|
for next in children:
|
||||||
|
next.set_icon(NAME_COLUMN, get_theme_icon(next.get_text(RESOURCE_TYPE_COLUMN), "EditorIcons"))
|
||||||
|
next.set_icon(RESOURCE_TYPE_COLUMN, get_theme_icon("ClassList", "EditorIcons"))
|
||||||
|
|
||||||
|
|
||||||
|
func _sort_by_column(column: int) -> void:
|
||||||
|
match column:
|
||||||
|
NAME_COLUMN:
|
||||||
|
stats.resources.sort_custom(sort_name)
|
||||||
|
RESOURCE_TYPE_COLUMN:
|
||||||
|
stats.resources.sort_custom(sort_resource_type)
|
||||||
|
LOCAL_TO_SCENE_COLUMN:
|
||||||
|
stats.resources.sort_custom(sort_local_to_scene)
|
||||||
|
SIZE_COLUMN:
|
||||||
|
stats.resources.sort_custom(sort_size)
|
28
addons/project-statistics/nodes/ResourcesView.tscn
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://vcehrmbfpknm"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/project-statistics/nodes/ResourcesView.gd" id="1"]
|
||||||
|
[ext_resource type="PackedScene" path="res://addons/project-statistics/nodes/ScriptsView.tscn" id="2"]
|
||||||
|
|
||||||
|
[node name="ResourcesView" instance=ExtResource("2")]
|
||||||
|
anchors_preset = 15
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="VSplitContainer" parent="." index="0"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="HSplitContainer" parent="VSplitContainer" index="0"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="SummaryTree" parent="VSplitContainer/HSplitContainer" index="0"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="MarginContainer" parent="VSplitContainer/HSplitContainer" index="1"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="PieGraph" parent="VSplitContainer/HSplitContainer/MarginContainer" index="0"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Tree" parent="VSplitContainer" index="1"]
|
||||||
|
layout_mode = 2
|
||||||
|
columns = 4
|
||||||
|
column_titles_visible = true
|
97
addons/project-statistics/nodes/ScenesView.gd
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
@tool
|
||||||
|
extends "./TreeView.gd"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
NAME_COLUMN,
|
||||||
|
BASE_NODE_TYPE_COLUMN,
|
||||||
|
NODE_COUNT_COLUMN,
|
||||||
|
NODE_CONNECTIONS_COLUMN,
|
||||||
|
LOCAL_TO_SCENE_COLUMN,
|
||||||
|
SIZE_COLUMN
|
||||||
|
}
|
||||||
|
|
||||||
|
var total_scenes: TreeItem
|
||||||
|
var total_nodes: TreeItem
|
||||||
|
var total_connections: TreeItem
|
||||||
|
var total_size: TreeItem
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
tree = $VSplitContainer/Tree as Tree
|
||||||
|
summary_tree = $VSplitContainer/SummaryTree as Tree
|
||||||
|
|
||||||
|
tree.set_column_title(NAME_COLUMN, "Scene")
|
||||||
|
tree.set_column_title(BASE_NODE_TYPE_COLUMN, "Base node")
|
||||||
|
tree.set_column_title(NODE_COUNT_COLUMN, "Node count")
|
||||||
|
tree.set_column_title(NODE_CONNECTIONS_COLUMN, "Node connection count")
|
||||||
|
tree.set_column_title(LOCAL_TO_SCENE_COLUMN, "Local to scene")
|
||||||
|
tree.set_column_title(SIZE_COLUMN, "Size")
|
||||||
|
tree.set_column_titles_visible(true)
|
||||||
|
tree.hide_root = true
|
||||||
|
|
||||||
|
tree.item_activated.connect(_on_item_activated)
|
||||||
|
tree.column_title_clicked.connect(_on_column_title_clicked)
|
||||||
|
|
||||||
|
var root: TreeItem = summary_tree.create_item()
|
||||||
|
total_scenes = summary_tree.create_item(root)
|
||||||
|
total_nodes = summary_tree.create_item(root)
|
||||||
|
total_connections = summary_tree.create_item(root)
|
||||||
|
total_size = summary_tree.create_item(root)
|
||||||
|
|
||||||
|
total_scenes.set_text(0, "Total scenes")
|
||||||
|
total_nodes.set_text(0, "Total nodes")
|
||||||
|
total_connections.set_text(0, "Total connections")
|
||||||
|
total_size.set_text(0, "Total size")
|
||||||
|
summary_tree.hide_root = true
|
||||||
|
|
||||||
|
func display(stats: ProjectStatistics) -> void:
|
||||||
|
super.display(stats)
|
||||||
|
total_scenes.set_text(1, str(stats.scenes.size()))
|
||||||
|
total_nodes.set_text(1, str(stats.get_total_nodes()))
|
||||||
|
total_connections.set_text(1, str(stats.get_total_connections()))
|
||||||
|
total_size.set_text(1, String.humanize_size(stats.get_total_scenes_size()))
|
||||||
|
|
||||||
|
func update_tree(stats: ProjectStatistics) -> void:
|
||||||
|
tree.clear()
|
||||||
|
var root: TreeItem = tree.create_item()
|
||||||
|
for file_stats in stats.scenes:
|
||||||
|
var item: TreeItem = tree.create_item(root)
|
||||||
|
item.set_cell_mode(LOCAL_TO_SCENE_COLUMN, TreeItem.CELL_MODE_CHECK)
|
||||||
|
|
||||||
|
item.set_text(NAME_COLUMN, file_stats.get_name())
|
||||||
|
item.set_tooltip_text(NAME_COLUMN, file_stats.path)
|
||||||
|
item.set_metadata(NAME_COLUMN, file_stats.path)
|
||||||
|
|
||||||
|
item.set_text(BASE_NODE_TYPE_COLUMN, file_stats.base_node_type)
|
||||||
|
item.set_metadata(BASE_NODE_TYPE_COLUMN, file_stats.base_node_type)
|
||||||
|
|
||||||
|
item.set_text(NODE_COUNT_COLUMN, str(file_stats.node_count))
|
||||||
|
item.set_text(NODE_CONNECTIONS_COLUMN, str(file_stats.connection_count))
|
||||||
|
item.set_checked(LOCAL_TO_SCENE_COLUMN, file_stats.local_to_scene)
|
||||||
|
|
||||||
|
_format_size(item, SIZE_COLUMN, file_stats.size)
|
||||||
|
|
||||||
|
func update_icons() -> void:
|
||||||
|
var root: TreeItem = tree.get_root()
|
||||||
|
if not root:
|
||||||
|
return
|
||||||
|
var children:= root.get_children()
|
||||||
|
for next in children:
|
||||||
|
next.set_icon(NAME_COLUMN, get_theme_icon("PackedScene", "EditorIcons"))
|
||||||
|
next.set_icon(BASE_NODE_TYPE_COLUMN, get_theme_icon(next.get_metadata(BASE_NODE_TYPE_COLUMN), "EditorIcons"))
|
||||||
|
next.set_icon(NODE_COUNT_COLUMN, get_theme_icon("Node", "EditorIcons"))
|
||||||
|
next.set_icon(NODE_CONNECTIONS_COLUMN, get_theme_icon("Slot", "EditorIcons"))
|
||||||
|
|
||||||
|
func _sort_by_column(column: int) -> void:
|
||||||
|
match column:
|
||||||
|
NAME_COLUMN:
|
||||||
|
stats.scenes.sort_custom(sort_name)
|
||||||
|
BASE_NODE_TYPE_COLUMN:
|
||||||
|
stats.scenes.sort_custom(sort_node_type)
|
||||||
|
NODE_COUNT_COLUMN:
|
||||||
|
stats.scenes.sort_custom(sort_node_count)
|
||||||
|
NODE_CONNECTIONS_COLUMN:
|
||||||
|
stats.scenes.sort_custom(sort_connection_count)
|
||||||
|
LOCAL_TO_SCENE_COLUMN:
|
||||||
|
stats.scenes.sort_custom(sort_local_to_scene)
|
||||||
|
SIZE_COLUMN:
|
||||||
|
stats.scenes.sort_custom(sort_size)
|
28
addons/project-statistics/nodes/ScenesView.tscn
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://dfmtk6v2uv63o"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/project-statistics/nodes/ScenesView.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="ScenesView" type="MarginContainer"]
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="VSplitContainer" type="VSplitContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="SummaryTree" type="Tree" parent="VSplitContainer"]
|
||||||
|
custom_minimum_size = Vector2(2.08165e-12, 95)
|
||||||
|
layout_mode = 2
|
||||||
|
columns = 2
|
||||||
|
hide_root = true
|
||||||
|
|
||||||
|
[node name="Tree" type="Tree" parent="VSplitContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
columns = 6
|
||||||
|
column_titles_visible = true
|
||||||
|
hide_root = true
|
135
addons/project-statistics/nodes/ScriptsView.gd
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
@tool
|
||||||
|
extends "./TreeView.gd"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
NAME_COLUMN,
|
||||||
|
LANGUAGE_COLUMN,
|
||||||
|
TOTAL_LINES_COLUMN,
|
||||||
|
CODE_LINES_COLUMN,
|
||||||
|
COMMENT_LINES_COLUMN,
|
||||||
|
BLANK_LINES_COLUMN,
|
||||||
|
SIZE_COLUMN
|
||||||
|
}
|
||||||
|
|
||||||
|
var total_scripts: TreeItem
|
||||||
|
var used_languages: TreeItem
|
||||||
|
var total_lines: TreeItem
|
||||||
|
var total_code_lines: TreeItem
|
||||||
|
var total_comments_lines: TreeItem
|
||||||
|
var total_blank_lines: TreeItem
|
||||||
|
var total_size: TreeItem
|
||||||
|
|
||||||
|
@onready var graph: Control = $VSplitContainer/HSplitContainer/MarginContainer/PieGraph
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
tree = $VSplitContainer/Tree
|
||||||
|
summary_tree = $VSplitContainer/HSplitContainer/SummaryTree
|
||||||
|
|
||||||
|
tree.set_column_title(NAME_COLUMN, "File name")
|
||||||
|
tree.set_column_title(LANGUAGE_COLUMN, "Language")
|
||||||
|
tree.set_column_title(TOTAL_LINES_COLUMN, "Total lines")
|
||||||
|
tree.set_column_title(CODE_LINES_COLUMN, "Code lines")
|
||||||
|
tree.set_column_title(COMMENT_LINES_COLUMN, "Comment lines")
|
||||||
|
tree.set_column_title(BLANK_LINES_COLUMN, "Blank lines")
|
||||||
|
tree.set_column_title(SIZE_COLUMN, "Size")
|
||||||
|
tree.set_column_titles_visible(true)
|
||||||
|
tree.hide_root = true
|
||||||
|
|
||||||
|
tree.connect("item_activated", _on_item_activated)
|
||||||
|
tree.connect("column_title_clicked", _on_column_title_clicked)
|
||||||
|
|
||||||
|
var root: TreeItem = summary_tree.create_item()
|
||||||
|
total_scripts = summary_tree.create_item(root)
|
||||||
|
used_languages = summary_tree.create_item(root)
|
||||||
|
total_lines = summary_tree.create_item(root)
|
||||||
|
total_code_lines = summary_tree.create_item(root)
|
||||||
|
total_comments_lines = summary_tree.create_item(root)
|
||||||
|
total_blank_lines = summary_tree.create_item(root)
|
||||||
|
total_size = summary_tree.create_item(root)
|
||||||
|
|
||||||
|
total_scripts.set_text(0, "Total scripts")
|
||||||
|
used_languages.set_text(0, "Used languages")
|
||||||
|
total_lines.set_text(0, "Total lines")
|
||||||
|
total_code_lines.set_text(0, "Total code lines")
|
||||||
|
total_comments_lines.set_text(0, "Total comments lines")
|
||||||
|
total_blank_lines.set_text(0, "Total blank lines")
|
||||||
|
total_size.set_text(0, "Total size")
|
||||||
|
summary_tree.hide_root = true
|
||||||
|
|
||||||
|
|
||||||
|
func display(stats: ProjectStatistics) -> void:
|
||||||
|
super.display(stats)
|
||||||
|
|
||||||
|
total_scripts.set_text(1, str(stats.scripts.size()))
|
||||||
|
used_languages.set_text(1, ", ".join(stats.get_used_langauges()))
|
||||||
|
total_lines.set_text(1, str(stats.get_total_lines()))
|
||||||
|
total_code_lines.set_text(1, str(stats.get_total_code_lines()))
|
||||||
|
total_comments_lines.set_text(1, str(stats.get_total_comment_lines()))
|
||||||
|
total_blank_lines.set_text(1, str(stats.get_total_blank_lines()))
|
||||||
|
total_size.set_text(1, String.humanize_size(stats.get_total_scripts_size()))
|
||||||
|
|
||||||
|
var series: Dictionary = {}
|
||||||
|
for file_stats in stats.scripts:
|
||||||
|
var name: String = file_stats.get_name()
|
||||||
|
var extension: String = file_stats._get_extension()
|
||||||
|
var color: Color = file_stats._get_color()
|
||||||
|
|
||||||
|
var chart_data :ChartData
|
||||||
|
if not series.has(extension):
|
||||||
|
chart_data = ChartData.new()
|
||||||
|
chart_data.name = extension
|
||||||
|
chart_data.color = color
|
||||||
|
series[extension] = chart_data
|
||||||
|
else:
|
||||||
|
chart_data = series[extension]
|
||||||
|
chart_data.value += file_stats.size
|
||||||
|
graph.set_series(series)
|
||||||
|
|
||||||
|
func update_tree(stats: ProjectStatistics) -> void:
|
||||||
|
tree.clear()
|
||||||
|
var root: TreeItem = tree.create_item()
|
||||||
|
for file_stats:FileStatistics in stats.scripts:
|
||||||
|
var item: TreeItem = tree.create_item(root)
|
||||||
|
|
||||||
|
item.set_text(NAME_COLUMN, file_stats.get_name())
|
||||||
|
item.set_tooltip_text(NAME_COLUMN, file_stats.path)
|
||||||
|
item.set_metadata(NAME_COLUMN, file_stats.path)
|
||||||
|
|
||||||
|
item.set_text(LANGUAGE_COLUMN, file_stats._get_extension())
|
||||||
|
item.set_metadata(LANGUAGE_COLUMN, file_stats._get_icon())
|
||||||
|
|
||||||
|
item.set_text(TOTAL_LINES_COLUMN, str(file_stats.total_lines))
|
||||||
|
item.set_text(CODE_LINES_COLUMN, str(file_stats.source_code_lines))
|
||||||
|
item.set_text(COMMENT_LINES_COLUMN, str(file_stats.comment_lines))
|
||||||
|
item.set_text(BLANK_LINES_COLUMN, str(file_stats.blank_lines))
|
||||||
|
|
||||||
|
_format_size(item, SIZE_COLUMN, file_stats.size)
|
||||||
|
|
||||||
|
func update_icons() -> void:
|
||||||
|
var root: TreeItem = tree.get_root()
|
||||||
|
if not root:
|
||||||
|
return
|
||||||
|
var children:= root.get_children()
|
||||||
|
for next in children:
|
||||||
|
next.set_icon(NAME_COLUMN, get_theme_icon(next.get_metadata(LANGUAGE_COLUMN), "EditorIcons"))
|
||||||
|
next.set_icon(LANGUAGE_COLUMN, get_theme_icon("Script", "EditorIcons"))
|
||||||
|
next.set_icon(TOTAL_LINES_COLUMN, get_theme_icon("AnimationTrackList", "EditorIcons"))
|
||||||
|
next.set_icon(CODE_LINES_COLUMN, get_theme_icon("CodeFoldedRightArrow", "EditorIcons"))
|
||||||
|
next.set_icon(COMMENT_LINES_COLUMN, get_theme_icon("VisualShaderNodeComment", "EditorIcons"))
|
||||||
|
|
||||||
|
func _sort_by_column(column: int) -> void:
|
||||||
|
match column:
|
||||||
|
NAME_COLUMN:
|
||||||
|
stats.scripts.sort_custom(sort_name)
|
||||||
|
LANGUAGE_COLUMN:
|
||||||
|
stats.scripts.sort_custom(sort_extension)
|
||||||
|
TOTAL_LINES_COLUMN:
|
||||||
|
stats.scripts.sort_custom(sort_total_lines)
|
||||||
|
CODE_LINES_COLUMN:
|
||||||
|
stats.scripts.sort_custom(sort_source_code_lines)
|
||||||
|
COMMENT_LINES_COLUMN:
|
||||||
|
stats.scripts.sort_custom(sort_comment_lines)
|
||||||
|
BLANK_LINES_COLUMN:
|
||||||
|
stats.scripts.sort_custom(sort_blank_lines)
|
||||||
|
SIZE_COLUMN:
|
||||||
|
stats.scripts.sort_custom(sort_size)
|
53
addons/project-statistics/nodes/ScriptsView.tscn
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
[gd_scene load_steps=3 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://addons/project-statistics/nodes/charts/PieGraph.tscn" type="PackedScene" id=1]
|
||||||
|
[ext_resource path="res://addons/project-statistics/nodes/ScriptsView.gd" type="Script" id=2]
|
||||||
|
|
||||||
|
[node name="ScriptsView" type="MarginContainer"]
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
script = ExtResource( 2 )
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="VSplitContainer" type="VSplitContainer" parent="."]
|
||||||
|
margin_right = 1024.0
|
||||||
|
margin_bottom = 600.0
|
||||||
|
|
||||||
|
[node name="HSplitContainer" type="HSplitContainer" parent="VSplitContainer"]
|
||||||
|
margin_right = 1024.0
|
||||||
|
margin_bottom = 180.0
|
||||||
|
rect_min_size = Vector2( 0, 175 )
|
||||||
|
|
||||||
|
[node name="SummaryTree" type="Tree" parent="VSplitContainer/HSplitContainer"]
|
||||||
|
margin_right = 506.0
|
||||||
|
margin_bottom = 180.0
|
||||||
|
rect_min_size = Vector2( 0, 180 )
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
columns = 2
|
||||||
|
hide_root = true
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="VSplitContainer/HSplitContainer"]
|
||||||
|
margin_left = 518.0
|
||||||
|
margin_right = 1024.0
|
||||||
|
margin_bottom = 180.0
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="PieGraph" parent="VSplitContainer/HSplitContainer/MarginContainer" instance=ExtResource( 1 )]
|
||||||
|
margin_right = 506.0
|
||||||
|
margin_bottom = 180.0
|
||||||
|
|
||||||
|
[node name="Tree" type="Tree" parent="VSplitContainer"]
|
||||||
|
margin_top = 192.0
|
||||||
|
margin_right = 1024.0
|
||||||
|
margin_bottom = 600.0
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
columns = 7
|
||||||
|
hide_root = true
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
36
addons/project-statistics/nodes/StatisticsPreview.gd
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
@tool
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
class_name StatisticsPreview
|
||||||
|
|
||||||
|
const ProjectStatistics: Script = preload("../loaders/ProjectStatistics.gd")
|
||||||
|
|
||||||
|
var editor_interface: EditorInterface
|
||||||
|
|
||||||
|
@onready var overview: Control = $VSplitContainer/ScrollContainer/MarginContainer/TabContainer/Overview
|
||||||
|
@onready var scenes_view: Control = $VSplitContainer/ScrollContainer/MarginContainer/TabContainer/Scenes
|
||||||
|
@onready var resources_view: Control = $VSplitContainer/ScrollContainer/MarginContainer/TabContainer/Resources
|
||||||
|
@onready var scripts_view: Control = $VSplitContainer/ScrollContainer/MarginContainer/TabContainer/Scripts
|
||||||
|
@onready var misc_view: Control = $VSplitContainer/ScrollContainer/MarginContainer/TabContainer/Misc
|
||||||
|
|
||||||
|
func _on_refresh_pressed() -> void:
|
||||||
|
var stats: ProjectStatistics = ProjectStatistics.new()
|
||||||
|
stats._load_dir()
|
||||||
|
|
||||||
|
overview.display(stats)
|
||||||
|
scenes_view.display(stats)
|
||||||
|
resources_view.display(stats)
|
||||||
|
scripts_view.display(stats)
|
||||||
|
misc_view.display(stats)
|
||||||
|
|
||||||
|
func _on_file_selected(path: String) -> void:
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
editor_interface.select_file(path)
|
||||||
|
if not ResourceLoader.exists(path):
|
||||||
|
return
|
||||||
|
var resource: Resource = ResourceLoader.load(path)
|
||||||
|
if resource:
|
||||||
|
if resource is PackedScene:
|
||||||
|
editor_interface.open_scene_from_path(path)
|
||||||
|
else:
|
||||||
|
editor_interface.edit_resource(resource)
|
82
addons/project-statistics/nodes/StatisticsPreview.tscn
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
[gd_scene load_steps=7 format=3 uid="uid://bfqwgtk1grusq"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/project-statistics/nodes/StatisticsPreview.gd" id="1"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://03754kkjn2ue" path="res://addons/project-statistics/nodes/Overview.tscn" id="2"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dfmtk6v2uv63o" path="res://addons/project-statistics/nodes/ScenesView.tscn" id="3"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://vcehrmbfpknm" path="res://addons/project-statistics/nodes/ResourcesView.tscn" id="4"]
|
||||||
|
[ext_resource type="PackedScene" path="res://addons/project-statistics/nodes/ScriptsView.tscn" id="5"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://ddli62n5pmlvn" path="res://addons/project-statistics/nodes/MiscView.tscn" id="6"]
|
||||||
|
|
||||||
|
[node name="StatisticsPreview" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="VSplitContainer" type="VSplitContainer" parent="."]
|
||||||
|
layout_mode = 0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
dragger_visibility = 2
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="VSplitContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="HSplitContainer" type="HSplitContainer" parent="VSplitContainer/MarginContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
dragger_visibility = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="VSplitContainer/MarginContainer/HSplitContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Project statistics:"
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="VSplitContainer/MarginContainer/HSplitContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
alignment = 2
|
||||||
|
|
||||||
|
[node name="RefreshButton" type="Button" parent="VSplitContainer/MarginContainer/HSplitContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Refresh"
|
||||||
|
|
||||||
|
[node name="ScrollContainer" type="ScrollContainer" parent="VSplitContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="VSplitContainer/ScrollContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="Panel" type="Panel" parent="VSplitContainer/ScrollContainer/MarginContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="TabContainer" type="TabContainer" parent="VSplitContainer/ScrollContainer/MarginContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Overview" parent="VSplitContainer/ScrollContainer/MarginContainer/TabContainer" instance=ExtResource("2")]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Scenes" parent="VSplitContainer/ScrollContainer/MarginContainer/TabContainer" instance=ExtResource("3")]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Resources" parent="VSplitContainer/ScrollContainer/MarginContainer/TabContainer" instance=ExtResource("4")]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Scripts" parent="VSplitContainer/ScrollContainer/MarginContainer/TabContainer" instance=ExtResource("5")]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Misc" parent="VSplitContainer/ScrollContainer/MarginContainer/TabContainer" instance=ExtResource("6")]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[connection signal="pressed" from="VSplitContainer/MarginContainer/HSplitContainer/HBoxContainer/RefreshButton" to="." method="_on_refresh_pressed"]
|
||||||
|
[connection signal="file_selected" from="VSplitContainer/ScrollContainer/MarginContainer/TabContainer/Resources" to="." method="_on_file_selected"]
|
||||||
|
[connection signal="file_selected" from="VSplitContainer/ScrollContainer/MarginContainer/TabContainer/Scripts" to="." method="_on_file_selected"]
|
||||||
|
[connection signal="file_selected" from="VSplitContainer/ScrollContainer/MarginContainer/TabContainer/Misc" to="." method="_on_file_selected"]
|
33
addons/project-statistics/nodes/StatisticsView.gd
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
@tool
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
signal file_selected(path)
|
||||||
|
|
||||||
|
const ProjectStatistics: Script = preload("../loaders/ProjectStatistics.gd")
|
||||||
|
const FileStatistics: Script = preload("../loaders/FileStatistics.gd")
|
||||||
|
const PieChart: Script = preload("./charts/PieChart.gd")
|
||||||
|
const ChartData: Script = preload("./charts/ChartData.gd")
|
||||||
|
|
||||||
|
var stats: ProjectStatistics
|
||||||
|
|
||||||
|
func display(stats: ProjectStatistics) -> void:
|
||||||
|
self.stats = stats
|
||||||
|
await get_tree().process_frame
|
||||||
|
update_icons()
|
||||||
|
|
||||||
|
func update_icons() -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
func _notification(what: int) -> void:
|
||||||
|
match what:
|
||||||
|
NOTIFICATION_THEME_CHANGED:
|
||||||
|
call_deferred("update_icons")
|
||||||
|
|
||||||
|
static func _format_size(item: TreeItem, column: int, size: int) -> void:
|
||||||
|
var size_text: PackedStringArray = String.humanize_size(size).split(" ")
|
||||||
|
var size_value: String = size_text[0]
|
||||||
|
var size_unit: String = size_text[1]
|
||||||
|
item.set_text(column, size_value)
|
||||||
|
item.set_suffix(column, size_unit)
|
||||||
|
item.set_tooltip_text(column, str(size) + " bytes")
|
||||||
|
item.set_text_alignment(column, HORIZONTAL_ALIGNMENT_CENTER)
|
79
addons/project-statistics/nodes/TreeView.gd
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
@tool
|
||||||
|
extends "./StatisticsView.gd"
|
||||||
|
|
||||||
|
@onready var tree: Tree
|
||||||
|
@onready var summary_tree: Tree
|
||||||
|
|
||||||
|
func display(stats: ProjectStatistics) -> void:
|
||||||
|
#.display(stats)
|
||||||
|
super.display(stats)
|
||||||
|
update_tree(stats)
|
||||||
|
|
||||||
|
func update_tree(stats: ProjectStatistics) -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
func _on_item_activated() -> void:
|
||||||
|
if tree.get_selected_column() == 0:
|
||||||
|
var path: String = tree.get_selected().get_metadata(0)
|
||||||
|
emit_signal("file_selected", path)
|
||||||
|
|
||||||
|
func _on_column_title_clicked(column: int, _mouse_button: int) -> void:
|
||||||
|
if stats:
|
||||||
|
stats = stats.duplicate()
|
||||||
|
_sort_by_column(column)
|
||||||
|
update_tree(stats)
|
||||||
|
update_icons()
|
||||||
|
|
||||||
|
func _sort_by_column(column: int) -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
func sort_name(file1: FileStatistics, file2: FileStatistics) -> bool:
|
||||||
|
var result: int = file2.get_name().casecmp_to(file1.get_name())
|
||||||
|
return result == 1 if result != 0 else sort_path(file1, file2)
|
||||||
|
|
||||||
|
func sort_path(file1: FileStatistics, file2: FileStatistics) -> bool:
|
||||||
|
return file2.path.casecmp_to(file1.path) == 1
|
||||||
|
|
||||||
|
func sort_extension(file1: FileStatistics, file2: FileStatistics) -> bool:
|
||||||
|
var result: int = file2._get_extension().casecmp_to(file1._get_extension())
|
||||||
|
return result == 1 if result != 0 else sort_path(file1, file2)
|
||||||
|
|
||||||
|
func sort_resource_type(file1: FileStatistics, file2: FileStatistics) -> bool:
|
||||||
|
var result: int = file2.type.casecmp_to(file1.type)
|
||||||
|
return result == 1 if result != 0 else sort_path(file1, file2)
|
||||||
|
|
||||||
|
func sort_node_type(file1: FileStatistics, file2: FileStatistics) -> bool:
|
||||||
|
var result: int = file2.base_node_type.casecmp_to(file1.base_node_type)
|
||||||
|
return result == 1 if result != 0 else sort_path(file1, file2)
|
||||||
|
|
||||||
|
func sort_size(file1: FileStatistics, file2: FileStatistics) -> bool:
|
||||||
|
return file1.size > file2.size if file1.size != file2.size else sort_name(file1, file2)
|
||||||
|
|
||||||
|
func sort_total_lines(file1: FileStatistics, file2: FileStatistics) -> bool:
|
||||||
|
return file1.total_lines > file2.total_lines if file1.total_lines != file2.total_lines else sort_source_code_lines(file1, file2)
|
||||||
|
|
||||||
|
func sort_source_code_lines(file1: FileStatistics, file2: FileStatistics) -> bool:
|
||||||
|
return file1.source_code_lines > file2.source_code_lines if file1.source_code_lines != file2.source_code_lines else sort_comment_lines(file1, file2)
|
||||||
|
|
||||||
|
func sort_comment_lines(file1: FileStatistics, file2: FileStatistics) -> bool:
|
||||||
|
return file1.comment_lines > file2.comment_lines if file1.comment_lines != file2.comment_lines else sort_blank_lines(file1, file2)
|
||||||
|
|
||||||
|
func sort_blank_lines(file1: FileStatistics, file2: FileStatistics) -> bool:
|
||||||
|
return file1.blank_lines > file2.blank_lines if file1.blank_lines != file2.blank_lines else sort_size(file1, file2)
|
||||||
|
|
||||||
|
func sort_node_count(file1: FileStatistics, file2: FileStatistics) -> bool:
|
||||||
|
return file1.node_count > file2.node_count if file1.node_count != file2.node_count else sort_connection_count(file1, file2)
|
||||||
|
|
||||||
|
func sort_connection_count(file1: FileStatistics, file2: FileStatistics) -> bool:
|
||||||
|
return file1.connection_count > file2.connection_count if file1.connection_count != file2.connection_count else sort_name(file1, file2)
|
||||||
|
|
||||||
|
func sort_local_to_scene(file1: FileStatistics, file2: FileStatistics) -> bool:
|
||||||
|
return file1.local_to_scene if not file1.local_to_scene and file2.local_to_scene else sort_name(file1, file2)
|
||||||
|
|
||||||
|
func _notification(what: int) -> void:
|
||||||
|
match what:
|
||||||
|
NOTIFICATION_PREDELETE:
|
||||||
|
if is_instance_valid(tree):
|
||||||
|
tree.clear()
|
||||||
|
if is_instance_valid(tree):
|
||||||
|
summary_tree.clear()
|
3
addons/project-statistics/nodes/charts/ChartData.gd
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
var name: String
|
||||||
|
var value: float
|
||||||
|
var color: Color = Color.GRAY
|
73
addons/project-statistics/nodes/charts/PieChart.gd
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
@tool
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
const ChartData: Script = preload("./ChartData.gd")
|
||||||
|
|
||||||
|
@export var radius := 50.0
|
||||||
|
|
||||||
|
var series: Dictionary
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
#rect_clip_content = true
|
||||||
|
clip_contents = true
|
||||||
|
|
||||||
|
func clear() -> void:
|
||||||
|
series.clear()
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
func add_data(data: ChartData) -> void:
|
||||||
|
series[data.name] = data
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
func remove_data(name: String) -> void:
|
||||||
|
series.erase(name)
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
func set_radius(value: float) -> void:
|
||||||
|
radius = max(0, value)
|
||||||
|
self.rect_min_size = Vector2.ONE * radius * 2
|
||||||
|
|
||||||
|
func draw_filled_arc(center: Vector2, radius: float, start_angle: float, end_angle: float,
|
||||||
|
point_count: int, color: Color, antialiased: bool = false
|
||||||
|
) -> void:
|
||||||
|
var points: PackedVector2Array = []
|
||||||
|
points.push_back(center)
|
||||||
|
|
||||||
|
for i in range(point_count + 1):
|
||||||
|
var angle_point: float = start_angle + i * (end_angle - start_angle) / point_count
|
||||||
|
points.push_back(center + Vector2(cos(angle_point), sin(angle_point)) * radius)
|
||||||
|
#draw_polygon(points, [color], [], null, null, antialiased)
|
||||||
|
draw_polygon(points, [color])
|
||||||
|
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
var charts_data: Array = series.values()
|
||||||
|
var total: float = 0.0
|
||||||
|
for data in charts_data:
|
||||||
|
total += data.value
|
||||||
|
|
||||||
|
var center: Vector2 = get_rect().size / 2.0
|
||||||
|
var start_angle: float = 0.0
|
||||||
|
var i: int
|
||||||
|
for data in charts_data:
|
||||||
|
var percent: float = data.value / total
|
||||||
|
var end_angle: float = start_angle - percent * 2 * PI
|
||||||
|
draw_filled_arc(
|
||||||
|
center,
|
||||||
|
radius,
|
||||||
|
start_angle,
|
||||||
|
end_angle,
|
||||||
|
30 * percent + 2,
|
||||||
|
data.color,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
start_angle = end_angle
|
||||||
|
var outline_width: float = 1.2
|
||||||
|
|
||||||
|
if not get_theme_constant("dark_theme", "Editor"):
|
||||||
|
draw_arc(center, radius - outline_width / 2, 0.0, 2 * PI, 32, Color.LIGHT_SLATE_GRAY, 1.5, true)
|
||||||
|
|
||||||
|
func _notification(what: int) -> void:
|
||||||
|
match what:
|
||||||
|
NOTIFICATION_THEME_CHANGED:
|
||||||
|
queue_redraw()
|
49
addons/project-statistics/nodes/charts/PieGraph.gd
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
@tool
|
||||||
|
extends VSplitContainer
|
||||||
|
|
||||||
|
const ChartData: Script = preload("./ChartData.gd")
|
||||||
|
const PieChart: Script = preload("./PieChart.gd")
|
||||||
|
|
||||||
|
const CIRCLE_ICON: Texture = preload("../../icons/circle.svg")
|
||||||
|
|
||||||
|
@onready var chart: PieChart = $PieChart
|
||||||
|
@onready var key_tree: Tree = $KeyTree
|
||||||
|
|
||||||
|
|
||||||
|
func set_series(series: Dictionary) -> void:
|
||||||
|
chart.series = series
|
||||||
|
chart.queue_redraw()
|
||||||
|
key_tree.clear()
|
||||||
|
|
||||||
|
var root: TreeItem = key_tree.create_item()
|
||||||
|
|
||||||
|
var values: Array = series.values()
|
||||||
|
values.sort_custom(_sort_series)
|
||||||
|
|
||||||
|
var total: float = _get_total(values)
|
||||||
|
for data in values:
|
||||||
|
var item: TreeItem = key_tree.create_item(root)
|
||||||
|
item.set_text(0, data.name)
|
||||||
|
item.set_icon(0, CIRCLE_ICON)
|
||||||
|
item.set_icon_modulate(0, data.color)
|
||||||
|
item.set_icon_max_width(0, 8)
|
||||||
|
item.set_text(1, "%05.2f" % (data.value / total * 100))
|
||||||
|
item.set_suffix(1, "%")
|
||||||
|
|
||||||
|
chart.series = series
|
||||||
|
chart.queue_redraw()
|
||||||
|
|
||||||
|
func _get_total(series: Array) -> float:
|
||||||
|
var total: float
|
||||||
|
for data in series:
|
||||||
|
total += data.value
|
||||||
|
return total
|
||||||
|
|
||||||
|
func _sort_series(data1: ChartData, data2: ChartData) -> bool:
|
||||||
|
return data1.value > data2.value
|
||||||
|
|
||||||
|
func _notification(what: int) -> void:
|
||||||
|
match what:
|
||||||
|
NOTIFICATION_RESIZED, NOTIFICATION_SORT_CHILDREN:
|
||||||
|
if chart:
|
||||||
|
chart.radius = min(chart.get_rect().size.x, chart.get_rect().size.y) / 2 * 0.9
|
29
addons/project-statistics/nodes/charts/PieGraph.tscn
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://by7ltrt0iq35i"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/project-statistics/nodes/charts/PieChart.gd" id="1"]
|
||||||
|
[ext_resource type="Script" path="res://addons/project-statistics/nodes/charts/PieGraph.gd" id="2"]
|
||||||
|
|
||||||
|
[node name="PieGraph" type="VSplitContainer"]
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
script = ExtResource("2")
|
||||||
|
|
||||||
|
[node name="PieChart" type="Control" parent="."]
|
||||||
|
clip_contents = true
|
||||||
|
custom_minimum_size = Vector2(120, 120)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="KeyTree" type="Tree" parent="."]
|
||||||
|
custom_minimum_size = Vector2(2.08165e-12, 100)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 8
|
||||||
|
columns = 2
|
||||||
|
hide_root = true
|
7
addons/project-statistics/plugin.cfg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[plugin]
|
||||||
|
|
||||||
|
name="Statistics"
|
||||||
|
description="Shows your project statistics"
|
||||||
|
author="Abdera7mane"
|
||||||
|
version="0.1"
|
||||||
|
script="plugin.gd"
|
42
addons/project-statistics/plugin.gd
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
@tool
|
||||||
|
extends EditorPlugin
|
||||||
|
|
||||||
|
|
||||||
|
const IGNORE_PROPERTY: String = "statistics/ignore"
|
||||||
|
const FORCE_INCLUDE_PROPERTY: String = "statistics/force_include"
|
||||||
|
|
||||||
|
var DEFAULT_IGNORE = PackedStringArray([
|
||||||
|
"res://.import/*",
|
||||||
|
"res://.github/*",
|
||||||
|
"res://addons/*",
|
||||||
|
"*.import"
|
||||||
|
])
|
||||||
|
|
||||||
|
var statistics_preview: PackedScene = preload("./nodes/StatisticsPreview.tscn")
|
||||||
|
var preview: StatisticsPreview
|
||||||
|
|
||||||
|
func _enter_tree() -> void:
|
||||||
|
preview = statistics_preview.instantiate()
|
||||||
|
preview.editor_interface = get_editor_interface()
|
||||||
|
add_control_to_bottom_panel(preview, "Statistics")
|
||||||
|
_setup()
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
remove_control_from_bottom_panel(preview)
|
||||||
|
preview.editor_interface = null
|
||||||
|
|
||||||
|
func _setup() -> void:
|
||||||
|
if not ProjectSettings.has_setting(IGNORE_PROPERTY):
|
||||||
|
ProjectSettings.set_setting(IGNORE_PROPERTY, DEFAULT_IGNORE)
|
||||||
|
ProjectSettings.add_property_info({
|
||||||
|
name = IGNORE_PROPERTY,
|
||||||
|
type = TYPE_PACKED_STRING_ARRAY
|
||||||
|
})
|
||||||
|
ProjectSettings.set_initial_value(IGNORE_PROPERTY, DEFAULT_IGNORE)
|
||||||
|
|
||||||
|
if not ProjectSettings.has_setting(FORCE_INCLUDE_PROPERTY):
|
||||||
|
ProjectSettings.set_setting(FORCE_INCLUDE_PROPERTY, PackedStringArray())
|
||||||
|
ProjectSettings.add_property_info({
|
||||||
|
name = FORCE_INCLUDE_PROPERTY,
|
||||||
|
type = TYPE_PACKED_STRING_ARRAY
|
||||||
|
})
|
0
addons/project-statistics/screenshots/.gdignore
Normal file
BIN
addons/project-statistics/screenshots/configuration.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
addons/project-statistics/screenshots/overview_tab.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
addons/project-statistics/screenshots/resources_tab.png
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
addons/project-statistics/screenshots/scenes_tab.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
addons/project-statistics/screenshots/scripts_tab.png
Normal file
After Width: | Height: | Size: 103 KiB |
9
addons/resources_spreadsheet_view/LICENSE.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# MIT License
|
||||||
|
|
||||||
|
Copyright 2022 Gennady "Don Tnowe" Krupenyov
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
6
addons/resources_spreadsheet_view/editor_color_setter.gd
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
@tool
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
modulate = get_theme_color("accent_color", "Editor")
|
12
addons/resources_spreadsheet_view/editor_icon_button.gd
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
@tool
|
||||||
|
extends Button
|
||||||
|
|
||||||
|
@export var icon_name := "Node" :
|
||||||
|
set(v):
|
||||||
|
icon_name = v
|
||||||
|
if has_theme_icon(v, "EditorIcons"):
|
||||||
|
icon = get_theme_icon(v, "EditorIcons")
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
self.icon_name = (icon_name)
|
458
addons/resources_spreadsheet_view/editor_view.gd
Normal file
@ -0,0 +1,458 @@
|
|||||||
|
@tool
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
signal grid_updated()
|
||||||
|
|
||||||
|
const TablesPluginSettingsClass := preload("res://addons/resources_spreadsheet_view/settings_grid.gd")
|
||||||
|
|
||||||
|
@export @onready var node_folder_path : LineEdit = $"HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer/Path"
|
||||||
|
@export @onready var node_recent_paths : OptionButton = $"HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer2/RecentPaths"
|
||||||
|
@export @onready var node_table_root : GridContainer = $"HeaderContentSplit/MarginContainer/FooterContentSplit/Panel/Scroll/MarginContainer/TableGrid"
|
||||||
|
@export @onready var node_columns : HBoxContainer = $"HeaderContentSplit/VBoxContainer/Columns/Columns"
|
||||||
|
@export @onready var node_page_manager : Control = $"HeaderContentSplit/VBoxContainer/HBoxContainer3/Pages"
|
||||||
|
|
||||||
|
@onready var _on_cell_gui_input : Callable = $"InputHandler"._on_cell_gui_input
|
||||||
|
@onready var _selection := $"SelectionManager"
|
||||||
|
|
||||||
|
var editor_interface : Object
|
||||||
|
var editor_plugin : EditorPlugin
|
||||||
|
|
||||||
|
var current_path := ""
|
||||||
|
var save_data_path : String = get_script().resource_path.get_base_dir() + "/saved_state.json"
|
||||||
|
var sorting_by := &""
|
||||||
|
var sorting_reverse := false
|
||||||
|
|
||||||
|
var columns := []
|
||||||
|
var column_types := []
|
||||||
|
var column_hints := []
|
||||||
|
var column_hint_strings := []
|
||||||
|
var rows := []
|
||||||
|
var remembered_paths := {}
|
||||||
|
var remembered_paths_total_count := 0
|
||||||
|
var table_functions_dict := {}
|
||||||
|
|
||||||
|
var search_cond : RefCounted
|
||||||
|
var io : RefCounted
|
||||||
|
|
||||||
|
var first_row := 0
|
||||||
|
var last_row := 0
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
editor_interface.get_resource_filesystem().filesystem_changed.connect(_on_filesystem_changed)
|
||||||
|
if FileAccess.file_exists(save_data_path):
|
||||||
|
var file = FileAccess.open(save_data_path, FileAccess.READ)
|
||||||
|
var as_text = file.get_as_text()
|
||||||
|
var as_var = JSON.parse_string(as_text)
|
||||||
|
|
||||||
|
node_recent_paths.load_paths(as_var.get("recent_paths", []))
|
||||||
|
node_columns.hidden_columns = as_var.get("hidden_columns", {})
|
||||||
|
table_functions_dict = as_var.get("table_functions", {})
|
||||||
|
for x in $"HeaderContentSplit/VBoxContainer/Search/Search".get_children():
|
||||||
|
if x.has_method(&"load_saved_functions"):
|
||||||
|
x.load_saved_functions(table_functions_dict)
|
||||||
|
|
||||||
|
if node_recent_paths.recent_paths.size() >= 1:
|
||||||
|
display_folder(node_recent_paths.recent_paths[0], "resource_name", false, true)
|
||||||
|
|
||||||
|
|
||||||
|
func save_data():
|
||||||
|
var file = FileAccess.open(save_data_path, FileAccess.WRITE)
|
||||||
|
file.store_string(JSON.stringify(
|
||||||
|
{
|
||||||
|
"recent_paths" : node_recent_paths.recent_paths,
|
||||||
|
"hidden_columns" : node_columns.hidden_columns,
|
||||||
|
"table_functions" : table_functions_dict,
|
||||||
|
}
|
||||||
|
, " "))
|
||||||
|
|
||||||
|
|
||||||
|
func _on_filesystem_changed():
|
||||||
|
var file_total_count := _get_file_count_recursive(current_path)
|
||||||
|
if file_total_count != remembered_paths_total_count:
|
||||||
|
refresh()
|
||||||
|
|
||||||
|
else:
|
||||||
|
for k in remembered_paths:
|
||||||
|
if !is_instance_valid(remembered_paths[k]):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if remembered_paths[k].resource_path != k:
|
||||||
|
var res = remembered_paths[k]
|
||||||
|
remembered_paths.erase(k)
|
||||||
|
remembered_paths[res.resource_path] = res
|
||||||
|
refresh()
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
func _get_file_count_recursive(path : String) -> int:
|
||||||
|
var editor_fs : EditorFileSystem = editor_interface.get_resource_filesystem()
|
||||||
|
var path_dir := editor_fs.get_filesystem_path(path)
|
||||||
|
if !path_dir: return 0
|
||||||
|
|
||||||
|
var file_total_count := 0
|
||||||
|
var folder_stack : Array[EditorFileSystemDirectory] = [path_dir]
|
||||||
|
while folder_stack.size() > 0:
|
||||||
|
path_dir = folder_stack.pop_back()
|
||||||
|
file_total_count += path_dir.get_file_count()
|
||||||
|
for i in path_dir.get_subdir_count():
|
||||||
|
folder_stack.append(path_dir.get_subdir(i))
|
||||||
|
|
||||||
|
return file_total_count
|
||||||
|
|
||||||
|
|
||||||
|
func display_folder(folderpath : String, sort_by : StringName = "", sort_reverse : bool = false, force_rebuild : bool = false, is_echo : bool = false):
|
||||||
|
if folderpath == "":
|
||||||
|
# You wouldn't just wanna edit ALL resources in the project, that's a long time to load!
|
||||||
|
return
|
||||||
|
|
||||||
|
if sort_by == "":
|
||||||
|
sort_by = &"resource_path"
|
||||||
|
|
||||||
|
$"HeaderContentSplit/MarginContainer/FooterContentSplit/Panel/Label".visible = false
|
||||||
|
if folderpath.get_extension() == "":
|
||||||
|
folderpath = folderpath.trim_suffix("/") + "/"
|
||||||
|
|
||||||
|
if folderpath.ends_with(".tres") and !folderpath.ends_with(ResourceTablesImport.SUFFIX):
|
||||||
|
folderpath = folderpath.get_base_dir() + "/"
|
||||||
|
|
||||||
|
node_recent_paths.add_path_to_recent(folderpath)
|
||||||
|
first_row = node_page_manager.first_row
|
||||||
|
_load_resources_from_path(folderpath, sort_by, sort_reverse)
|
||||||
|
last_row = min(first_row + node_page_manager.rows_per_page, rows.size())
|
||||||
|
|
||||||
|
if columns.size() == 0: return
|
||||||
|
|
||||||
|
node_folder_path.text = folderpath
|
||||||
|
if (
|
||||||
|
force_rebuild
|
||||||
|
or current_path != folderpath
|
||||||
|
or columns.size() != node_columns.get_child_count()
|
||||||
|
):
|
||||||
|
node_table_root.columns = columns.size()
|
||||||
|
for x in node_table_root.get_children():
|
||||||
|
x.free()
|
||||||
|
|
||||||
|
node_columns.columns = columns
|
||||||
|
|
||||||
|
var cells_left_to_free : int = node_table_root.get_child_count() - (last_row - first_row) * columns.size()
|
||||||
|
while cells_left_to_free > 0:
|
||||||
|
node_table_root.get_child(0).free()
|
||||||
|
cells_left_to_free -= 1
|
||||||
|
|
||||||
|
var color_rows : bool = ProjectSettings.get_setting(TablesPluginSettingsClass.PREFIX + "color_rows")
|
||||||
|
for i in last_row - first_row:
|
||||||
|
_update_row(first_row + i, color_rows)
|
||||||
|
|
||||||
|
current_path = folderpath
|
||||||
|
remembered_paths_total_count = _get_file_count_recursive(folderpath)
|
||||||
|
node_columns.update()
|
||||||
|
grid_updated.emit()
|
||||||
|
|
||||||
|
|
||||||
|
func refresh(force_rebuild : bool = true):
|
||||||
|
display_folder(current_path, sorting_by, sorting_reverse, force_rebuild)
|
||||||
|
|
||||||
|
|
||||||
|
func _load_resources_from_path(path : String, sort_by : StringName, sort_reverse : bool):
|
||||||
|
if path.ends_with("/"):
|
||||||
|
io = ResourceTablesEditFormatTres.new()
|
||||||
|
|
||||||
|
else:
|
||||||
|
var loaded = load(path)
|
||||||
|
if loaded is ResourceTablesImport:
|
||||||
|
io = loaded.view_script.new()
|
||||||
|
|
||||||
|
else:
|
||||||
|
io = ResourceTablesEditFormatTres.new()
|
||||||
|
|
||||||
|
io.editor_view = self
|
||||||
|
remembered_paths.clear()
|
||||||
|
rows = io.import_from_path(path, insert_row_sorted, sort_by, sort_reverse)
|
||||||
|
|
||||||
|
|
||||||
|
func fill_property_data(res : Resource):
|
||||||
|
columns.clear()
|
||||||
|
column_types.clear()
|
||||||
|
column_hints.clear()
|
||||||
|
column_hint_strings.clear()
|
||||||
|
var column_values := []
|
||||||
|
var i := -1
|
||||||
|
for x in res.get_property_list():
|
||||||
|
if x[&"usage"] & PROPERTY_USAGE_EDITOR != 0 and x[&"name"] != "script":
|
||||||
|
i += 1
|
||||||
|
columns.append(x[&"name"])
|
||||||
|
column_types.append(x[&"type"])
|
||||||
|
column_hints.append(x[&"hint"])
|
||||||
|
column_hint_strings.append(x[&"hint_string"].split(","))
|
||||||
|
column_values.append(io.get_value(res, columns[i]))
|
||||||
|
|
||||||
|
_selection.initialize_editors(column_values, column_types, column_hints)
|
||||||
|
|
||||||
|
|
||||||
|
func fill_property_data_many(resources : Array):
|
||||||
|
columns.clear()
|
||||||
|
column_types.clear()
|
||||||
|
column_hints.clear()
|
||||||
|
column_hint_strings.clear()
|
||||||
|
var column_values := []
|
||||||
|
var i := -1
|
||||||
|
var found_props := {}
|
||||||
|
for x in resources:
|
||||||
|
if x == null: continue
|
||||||
|
for y in x.get_property_list():
|
||||||
|
found_props[y[&"name"]] = y
|
||||||
|
y[&"owner_object"] = x
|
||||||
|
|
||||||
|
for x in found_props.values():
|
||||||
|
if x[&"usage"] & PROPERTY_USAGE_EDITOR != 0 and x[&"name"] != "script":
|
||||||
|
i += 1
|
||||||
|
columns.append(x[&"name"])
|
||||||
|
column_types.append(x[&"type"])
|
||||||
|
column_hints.append(x[&"hint"])
|
||||||
|
column_hint_strings.append(x[&"hint_string"].split(","))
|
||||||
|
column_values.append(io.get_value(x[&"owner_object"], columns[i]))
|
||||||
|
|
||||||
|
_selection.initialize_editors(column_values, column_types, column_hints)
|
||||||
|
|
||||||
|
|
||||||
|
func insert_row_sorted(res : Resource, loaded_rows : Array, sort_by : StringName, sort_reverse : bool):
|
||||||
|
if search_cond != null and not search_cond.can_show(res, loaded_rows.size()):
|
||||||
|
return
|
||||||
|
|
||||||
|
if not sort_by in res:
|
||||||
|
return
|
||||||
|
|
||||||
|
var sort_value = io.get_value(res, sort_by)
|
||||||
|
for i in loaded_rows.size():
|
||||||
|
if sort_reverse == compare_values(sort_value, io.get_value(loaded_rows[i], sort_by)):
|
||||||
|
loaded_rows.insert(i, res)
|
||||||
|
return
|
||||||
|
|
||||||
|
remembered_paths[res.resource_path] = res
|
||||||
|
loaded_rows.append(res)
|
||||||
|
|
||||||
|
|
||||||
|
func compare_values(a, b) -> bool:
|
||||||
|
if a == null or b == null: return b == null
|
||||||
|
if a is Color:
|
||||||
|
return a.h > b.h if a.h != b.h else a.v > b.v
|
||||||
|
|
||||||
|
if a is Resource:
|
||||||
|
return a.resource_path > b.resource_path
|
||||||
|
|
||||||
|
if a is Array:
|
||||||
|
return a.size() > b.size()
|
||||||
|
|
||||||
|
return a > b
|
||||||
|
|
||||||
|
|
||||||
|
func _set_sorting(sort_by : StringName):
|
||||||
|
var sort_reverse : bool = !(sorting_by != sort_by or sorting_reverse)
|
||||||
|
sorting_reverse = sort_reverse
|
||||||
|
display_folder(current_path, sort_by, sort_reverse)
|
||||||
|
sorting_by = sort_by
|
||||||
|
|
||||||
|
|
||||||
|
func _update_row(row_index : int, color_rows : bool = true):
|
||||||
|
var current_node : Control
|
||||||
|
var next_color := Color.WHITE
|
||||||
|
var column_editors : Array = _selection.column_editors
|
||||||
|
var res_path : String = rows[row_index].resource_path.get_basename().substr(current_path.length())
|
||||||
|
for i in columns.size():
|
||||||
|
if node_table_root.get_child_count() <= (row_index - first_row) * columns.size() + i:
|
||||||
|
current_node = column_editors[i].create_cell(self)
|
||||||
|
current_node.gui_input.connect(_on_cell_gui_input.bind(current_node))
|
||||||
|
node_table_root.add_child(current_node)
|
||||||
|
|
||||||
|
else:
|
||||||
|
current_node = node_table_root.get_child((row_index - first_row) * columns.size() + i)
|
||||||
|
current_node.tooltip_text = (
|
||||||
|
columns[i].capitalize()
|
||||||
|
+ "\n---\n"
|
||||||
|
+ "Of " + res_path
|
||||||
|
)
|
||||||
|
|
||||||
|
if columns[i] in rows[row_index]:
|
||||||
|
current_node.mouse_filter = MOUSE_FILTER_STOP
|
||||||
|
current_node.modulate.a = 1.0
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Empty cell, can't click, property doesn't exist.
|
||||||
|
current_node.mouse_filter = MOUSE_FILTER_IGNORE
|
||||||
|
current_node.modulate.a = 0.0
|
||||||
|
continue
|
||||||
|
|
||||||
|
if columns[i] == &"resource_path":
|
||||||
|
column_editors[i].set_value(current_node, res_path)
|
||||||
|
|
||||||
|
else:
|
||||||
|
var cell_value = io.get_value(rows[row_index], columns[i])
|
||||||
|
if cell_value != null or column_types[i] == TYPE_OBJECT:
|
||||||
|
column_editors[i].set_value(current_node, cell_value)
|
||||||
|
if color_rows and column_types[i] == TYPE_COLOR:
|
||||||
|
next_color = cell_value
|
||||||
|
|
||||||
|
column_editors[i].set_color(current_node, next_color)
|
||||||
|
|
||||||
|
|
||||||
|
func get_selected_column() -> int:
|
||||||
|
return _selection.get_cell_column(_selection.edited_cells[0])
|
||||||
|
|
||||||
|
|
||||||
|
func select_column(column_index : int):
|
||||||
|
_selection.deselect_all_cells()
|
||||||
|
_selection.select_cell(node_table_root.get_child(column_index))
|
||||||
|
_selection.select_cells_to(node_table_root.get_child(
|
||||||
|
column_index + columns.size()
|
||||||
|
* (last_row - first_row - 1))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func set_edited_cells_values(new_cell_values : Array):
|
||||||
|
var edited_rows = _selection.get_edited_rows()
|
||||||
|
var column = _selection.get_cell_column(_selection.edited_cells[0])
|
||||||
|
var edited_cells_resources = _get_row_resources(edited_rows)
|
||||||
|
|
||||||
|
# Duplicated here since if using text editing, edited_cells_text needs to modified
|
||||||
|
# but here, it would be converted from a String breaking editing
|
||||||
|
new_cell_values = new_cell_values.duplicate()
|
||||||
|
|
||||||
|
editor_plugin.undo_redo.create_action("Set Cell Values")
|
||||||
|
editor_plugin.undo_redo.add_undo_method(
|
||||||
|
self,
|
||||||
|
&"_update_resources",
|
||||||
|
edited_cells_resources.duplicate(),
|
||||||
|
edited_rows.duplicate(),
|
||||||
|
column,
|
||||||
|
get_edited_cells_values()
|
||||||
|
)
|
||||||
|
editor_plugin.undo_redo.add_undo_method(
|
||||||
|
_selection,
|
||||||
|
&"_update_selected_cells_text"
|
||||||
|
)
|
||||||
|
editor_plugin.undo_redo.add_do_method(
|
||||||
|
self,
|
||||||
|
&"_update_resources",
|
||||||
|
edited_cells_resources.duplicate(),
|
||||||
|
edited_rows.duplicate(),
|
||||||
|
column,
|
||||||
|
new_cell_values.duplicate()
|
||||||
|
)
|
||||||
|
editor_plugin.undo_redo.commit_action(true)
|
||||||
|
# editor_interface.get_resource_filesystem().scan()
|
||||||
|
|
||||||
|
|
||||||
|
func rename_row(row, new_name):
|
||||||
|
if !has_row_names(): return
|
||||||
|
|
||||||
|
io.rename_row(row, new_name)
|
||||||
|
refresh()
|
||||||
|
|
||||||
|
|
||||||
|
func duplicate_selected_rows(new_name : String):
|
||||||
|
io.duplicate_rows(_get_row_resources(_selection.get_edited_rows()), new_name)
|
||||||
|
refresh()
|
||||||
|
|
||||||
|
|
||||||
|
func delete_selected_rows():
|
||||||
|
io.delete_rows(_get_row_resources(_selection.get_edited_rows()))
|
||||||
|
refresh()
|
||||||
|
refresh.call_deferred()
|
||||||
|
|
||||||
|
|
||||||
|
func has_row_names():
|
||||||
|
return io.has_row_names()
|
||||||
|
|
||||||
|
|
||||||
|
func get_last_selected_row():
|
||||||
|
return rows[_selection.get_cell_row(_selection.edited_cells[-1])]
|
||||||
|
|
||||||
|
|
||||||
|
func get_edited_cells_values() -> Array:
|
||||||
|
var cells : Array = _selection.edited_cells.duplicate()
|
||||||
|
var column_index : int = _selection.get_cell_column(_selection.edited_cells[0])
|
||||||
|
var cell_editor : Object = _selection.column_editors[column_index]
|
||||||
|
|
||||||
|
var result := []
|
||||||
|
result.resize(cells.size())
|
||||||
|
for i in cells.size():
|
||||||
|
result[i] = io.get_value(rows[_selection.get_cell_row(cells[i])], columns[column_index])
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
func _update_resources(update_rows : Array, update_row_indices : Array[int], update_column : int, values : Array):
|
||||||
|
var column_editor = _selection.column_editors[update_column]
|
||||||
|
for i in update_rows.size():
|
||||||
|
var row = update_row_indices[i]
|
||||||
|
var update_cell = node_table_root.get_child((row - first_row) * columns.size() + update_column)
|
||||||
|
|
||||||
|
column_editor.set_value(update_cell, values[i])
|
||||||
|
if values[i] is String:
|
||||||
|
values[i] = try_convert(values[i], column_types[update_column])
|
||||||
|
|
||||||
|
if values[i] == null:
|
||||||
|
continue
|
||||||
|
|
||||||
|
io.set_value(
|
||||||
|
update_rows[i],
|
||||||
|
columns[update_column],
|
||||||
|
values[i],
|
||||||
|
row
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
if column_types[update_column] == TYPE_COLOR:
|
||||||
|
for j in columns.size() - update_column:
|
||||||
|
if j != 0 and column_types[j + update_column] == TYPE_COLOR:
|
||||||
|
break
|
||||||
|
|
||||||
|
_selection.column_editors[j + update_column].set_color(
|
||||||
|
update_cell.get_parent().get_child(
|
||||||
|
row * columns.size() + update_column + j - first_row
|
||||||
|
),
|
||||||
|
values[i]
|
||||||
|
)
|
||||||
|
|
||||||
|
node_columns._update_column_sizes()
|
||||||
|
io.save_entries(rows, update_row_indices)
|
||||||
|
|
||||||
|
|
||||||
|
func try_convert(value, type):
|
||||||
|
if type == TYPE_BOOL:
|
||||||
|
# "off" displayed in lowercase, "ON" in uppercase.
|
||||||
|
return value[0] == "o"
|
||||||
|
|
||||||
|
# If it can't convert, throws exception and returns null.
|
||||||
|
return convert(value, type)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_path_text_submitted(new_text : String = ""):
|
||||||
|
if new_text != "":
|
||||||
|
current_path = new_text
|
||||||
|
display_folder(new_text, "", false, true)
|
||||||
|
|
||||||
|
else:
|
||||||
|
refresh()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_FileDialog_dir_selected(dir : String):
|
||||||
|
node_folder_path.text = dir
|
||||||
|
display_folder(dir)
|
||||||
|
|
||||||
|
|
||||||
|
func _get_row_resources(row_indices) -> Array:
|
||||||
|
var arr := []
|
||||||
|
arr.resize(row_indices.size())
|
||||||
|
for i in arr.size():
|
||||||
|
arr[i] = rows[row_indices[i]]
|
||||||
|
|
||||||
|
return arr
|
||||||
|
|
||||||
|
|
||||||
|
func _on_File_pressed():
|
||||||
|
node_folder_path.get_parent().get_parent().visible = !node_folder_path.get_parent().get_parent().visible
|
||||||
|
|
||||||
|
|
||||||
|
func _on_SearchProcess_pressed():
|
||||||
|
$"HeaderContentSplit/VBoxContainer/Search".visible = !$"HeaderContentSplit/VBoxContainer/Search".visible
|
573
addons/resources_spreadsheet_view/editor_view.tscn
Normal file
@ -0,0 +1,573 @@
|
|||||||
|
[gd_scene load_steps=30 format=3 uid="uid://tmleonv20aqk"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_view.gd" id="1_wfx75"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_color_setter.gd" id="2_t2s7k"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="3_7ja2l"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/main_screen/recent_paths.gd" id="4_umoob"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/main_screen/expression_textfield.gd" id="5_faq75"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/main_screen/table_pages.gd" id="5_ka2yn"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/main_screen/column_header_manager.gd" id="6_emnmd"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://d1s6oihqedvo5" path="res://addons/resources_spreadsheet_view/main_screen/table_header.tscn" id="7_3dx0v"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://ddqak780cwwfj" path="res://addons/resources_spreadsheet_view/typed_editors/dock_enum_array.tscn" id="8_234wn"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://c3a2cip8ffccv" path="res://addons/resources_spreadsheet_view/typed_editors/dock_array.tscn" id="9_nts08"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://b3a3bo6cfyh5t" path="res://addons/resources_spreadsheet_view/typed_editors/dock_color.tscn" id="10_nsma2"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://gtbf7b0wptv" path="res://addons/resources_spreadsheet_view/typed_editors/dock_number.tscn" id="11_q1ao4"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://rww3gpl052bn" path="res://addons/resources_spreadsheet_view/typed_editors/dock_texture.tscn" id="12_4kr6q"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dhunxgcae6h1" path="res://addons/resources_spreadsheet_view/settings_grid.tscn" id="13_as1sh"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://p6x03dbvhnqw" path="res://addons/resources_spreadsheet_view/typed_editors/dock_dict.tscn" id="13_il556"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/main_screen/input_handler.gd" id="14_2t57a"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://b413igx28kkvb" path="res://addons/resources_spreadsheet_view/import_export/import_export_dialog.tscn" id="14_3p12b"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/main_screen/selection_manager.gd" id="15_mx6qn"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_enum_array.gd" id="16_p7n52"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_array.gd" id="17_sofdw"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_color.gd" id="18_oeewr"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_bool.gd" id="19_7x44x"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_dict.gd" id="19_oeuko"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_enum.gd" id="20_swsbn"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_resource.gd" id="21_58wf8"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_string.gd" id="22_bni8r"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://b51hnttsie7k5" path="res://addons/resources_spreadsheet_view/main_screen/selection_actions.tscn" id="23_m53sx"]
|
||||||
|
|
||||||
|
[sub_resource type="Gradient" id="Gradient_8kp6w"]
|
||||||
|
offsets = PackedFloat32Array(0, 0.995413, 1)
|
||||||
|
colors = PackedColorArray(1, 1, 1, 0.490196, 1, 1, 1, 0.0458716, 1, 1, 1, 0)
|
||||||
|
|
||||||
|
[sub_resource type="GradientTexture2D" id="GradientTexture2D_18il8"]
|
||||||
|
gradient = SubResource("Gradient_8kp6w")
|
||||||
|
|
||||||
|
[node name="Control" type="MarginContainer" node_paths=PackedStringArray("node_folder_path", "node_recent_paths", "node_table_root", "node_columns", "node_page_manager")]
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
focus_neighbor_left = NodePath(".")
|
||||||
|
focus_neighbor_top = NodePath(".")
|
||||||
|
focus_neighbor_right = NodePath(".")
|
||||||
|
focus_neighbor_bottom = NodePath(".")
|
||||||
|
focus_next = NodePath(".")
|
||||||
|
focus_previous = NodePath(".")
|
||||||
|
focus_mode = 2
|
||||||
|
theme_override_constants/margin_left = 3
|
||||||
|
theme_override_constants/margin_right = 3
|
||||||
|
theme_override_constants/margin_bottom = 5
|
||||||
|
script = ExtResource("1_wfx75")
|
||||||
|
node_folder_path = NodePath("HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer/Path")
|
||||||
|
node_recent_paths = NodePath("HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer2/RecentPaths")
|
||||||
|
node_table_root = NodePath("HeaderContentSplit/MarginContainer/FooterContentSplit/Panel/Scroll/MarginContainer/TableGrid")
|
||||||
|
node_columns = NodePath("HeaderContentSplit/VBoxContainer/Columns/Columns")
|
||||||
|
node_page_manager = NodePath("HeaderContentSplit/VBoxContainer/HBoxContainer3/Pages")
|
||||||
|
metadata/_edit_lock_ = true
|
||||||
|
|
||||||
|
[node name="HeaderContentSplit" type="VBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="HeaderContentSplit"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="MenuStrip" type="HBoxContainer" parent="HeaderContentSplit/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="File" type="Button" parent="HeaderContentSplit/VBoxContainer/MenuStrip"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Settings"
|
||||||
|
focus_mode = 0
|
||||||
|
toggle_mode = true
|
||||||
|
button_pressed = true
|
||||||
|
text = "File"
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[node name="SearchProcess" type="Button" parent="HeaderContentSplit/VBoxContainer/MenuStrip"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Settings"
|
||||||
|
focus_mode = 0
|
||||||
|
toggle_mode = true
|
||||||
|
text = "Filter/Process"
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[node name="VisibleCols" type="MenuButton" parent="HeaderContentSplit/VBoxContainer/MenuStrip"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Shown Columns"
|
||||||
|
|
||||||
|
[node name="VSeparator" type="Control" parent="HeaderContentSplit/VBoxContainer/MenuStrip"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="Settings" type="Button" parent="HeaderContentSplit/VBoxContainer/MenuStrip"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Settings"
|
||||||
|
focus_mode = 0
|
||||||
|
text = "Settings"
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[node name="VSeparator2" type="VSeparator" parent="HeaderContentSplit/VBoxContainer/MenuStrip"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Info" type="Button" parent="HeaderContentSplit/VBoxContainer/MenuStrip"]
|
||||||
|
layout_mode = 2
|
||||||
|
focus_mode = 0
|
||||||
|
text = "About"
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HSplitContainer" parent="HeaderContentSplit/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
split_offset = -249
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="HeaderContentSplit/VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_constants/separation = 0
|
||||||
|
|
||||||
|
[node name="ColorRect4" type="ColorRect" parent="HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer"]
|
||||||
|
modulate = Color(0, 0, 0, 1)
|
||||||
|
custom_minimum_size = Vector2(6, 18)
|
||||||
|
layout_mode = 2
|
||||||
|
script = ExtResource("2_t2s7k")
|
||||||
|
|
||||||
|
[node name="TextureRect" type="TextureRect" parent="HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer/ColorRect4"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 11
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_right = 48.0
|
||||||
|
grow_horizontal = 0
|
||||||
|
grow_vertical = 2
|
||||||
|
texture = SubResource("GradientTexture2D_18il8")
|
||||||
|
expand_mode = 1
|
||||||
|
|
||||||
|
[node name="ColorRect3" type="Control" parent="HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(2, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Resource Folder:"
|
||||||
|
|
||||||
|
[node name="Path" type="LineEdit" parent="HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
caret_blink = true
|
||||||
|
|
||||||
|
[node name="SelectDir" type="Button" parent="HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Open Folder"
|
||||||
|
script = ExtResource("3_7ja2l")
|
||||||
|
icon_name = "Folder"
|
||||||
|
|
||||||
|
[node name="DeletePath" type="Button" parent="HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Remove Path from Recent"
|
||||||
|
script = ExtResource("3_7ja2l")
|
||||||
|
icon_name = "Remove"
|
||||||
|
|
||||||
|
[node name="HBoxContainer2" type="HBoxContainer" parent="HeaderContentSplit/VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label2" type="Label" parent="HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer2"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Open Recent:"
|
||||||
|
|
||||||
|
[node name="RecentPaths" type="OptionButton" parent="HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer2" node_paths=PackedStringArray("editor_view")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
clip_text = true
|
||||||
|
fit_to_longest_item = false
|
||||||
|
script = ExtResource("4_umoob")
|
||||||
|
editor_view = NodePath("../../../../..")
|
||||||
|
|
||||||
|
[node name="ImportExport" type="Button" parent="HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer2"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Import/Export CSV..."
|
||||||
|
script = ExtResource("3_7ja2l")
|
||||||
|
icon_name = "TextFile"
|
||||||
|
|
||||||
|
[node name="Search" type="VBoxContainer" parent="HeaderContentSplit/VBoxContainer"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 0
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="HeaderContentSplit/VBoxContainer/Search"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="ColorRect4" type="ColorRect" parent="HeaderContentSplit/VBoxContainer/Search/HBoxContainer"]
|
||||||
|
modulate = Color(0, 0, 0, 1)
|
||||||
|
custom_minimum_size = Vector2(6, 18)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 5
|
||||||
|
script = ExtResource("2_t2s7k")
|
||||||
|
|
||||||
|
[node name="TextureRect" type="TextureRect" parent="HeaderContentSplit/VBoxContainer/Search/HBoxContainer/ColorRect4"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 11
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_right = 48.0
|
||||||
|
grow_horizontal = 0
|
||||||
|
grow_vertical = 2
|
||||||
|
texture = SubResource("GradientTexture2D_18il8")
|
||||||
|
expand_mode = 1
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="HeaderContentSplit/VBoxContainer/Search/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "GDScript Filter and Process"
|
||||||
|
|
||||||
|
[node name="HSeparator" type="HSeparator" parent="HeaderContentSplit/VBoxContainer/Search/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="Search" type="HBoxContainer" parent="HeaderContentSplit/VBoxContainer/Search"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="ColorRect2" type="ColorRect" parent="HeaderContentSplit/VBoxContainer/Search/Search"]
|
||||||
|
modulate = Color(0, 0, 0, 1)
|
||||||
|
custom_minimum_size = Vector2(6, 18)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 5
|
||||||
|
script = ExtResource("2_t2s7k")
|
||||||
|
|
||||||
|
[node name="TextureRect" type="TextureRect" parent="HeaderContentSplit/VBoxContainer/Search/Search/ColorRect2"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 11
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_right = 48.0
|
||||||
|
grow_horizontal = 0
|
||||||
|
grow_vertical = 2
|
||||||
|
texture = SubResource("GradientTexture2D_18il8")
|
||||||
|
expand_mode = 1
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="HeaderContentSplit/VBoxContainer/Search/Search"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Condition:"
|
||||||
|
|
||||||
|
[node name="Label2" type="Label" parent="HeaderContentSplit/VBoxContainer/Search/Search"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Enter an expression. The table only show rows where the expression returns `true`.
|
||||||
|
|
||||||
|
You can use `res.<property_name>` to get a property, and `index` to get row number. Hit ENTER to run the search.
|
||||||
|
|
||||||
|
Try out these:
|
||||||
|
- (res.number_property > 0 and res.number_property < 100)
|
||||||
|
- (res.text_property != \\\"\\\")
|
||||||
|
- (\\\"a\\\" in res.text_property)
|
||||||
|
- (index < 5)"
|
||||||
|
mouse_filter = 0
|
||||||
|
mouse_default_cursor_shape = 16
|
||||||
|
text = "(?)"
|
||||||
|
|
||||||
|
[node name="Filter" type="HBoxContainer" parent="HeaderContentSplit/VBoxContainer/Search/Search"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
script = ExtResource("5_faq75")
|
||||||
|
editor_view_path = NodePath("../../../../..")
|
||||||
|
title = "func f(res : Resource, index : int):"
|
||||||
|
default_text = "true"
|
||||||
|
default_text_ml = "return true"
|
||||||
|
function_save_key = "filter"
|
||||||
|
|
||||||
|
[node name="VSeparator" type="VSeparator" parent="HeaderContentSplit/VBoxContainer/Search/Search"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label3" type="Label" parent="HeaderContentSplit/VBoxContainer/Search/Search"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Process:"
|
||||||
|
|
||||||
|
[node name="Label4" type="Label" parent="HeaderContentSplit/VBoxContainer/Search/Search"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Enter an expression. The values in selected cells will be replaced with calculated new values.
|
||||||
|
|
||||||
|
You can use `value` to get the cell's value, `res.<property_name>` to get a property, `row_index` to get row number
|
||||||
|
and `cell_index` to get the cell's selection order. Hit ENTER to run the search.
|
||||||
|
|
||||||
|
These are some valid expressions:
|
||||||
|
- (res.property1 + res.property2)
|
||||||
|
- (res.property1.replace(\\\"old_string\\\", \\\"new_string\\\"))
|
||||||
|
- (load(\\\"res://path/to_resource.tres\\\"))
|
||||||
|
|
||||||
|
Don't forget quotation marks on strings and str() on non-strings."
|
||||||
|
mouse_filter = 0
|
||||||
|
mouse_default_cursor_shape = 16
|
||||||
|
text = "(?)"
|
||||||
|
|
||||||
|
[node name="Process" type="HBoxContainer" parent="HeaderContentSplit/VBoxContainer/Search/Search"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
script = ExtResource("5_faq75")
|
||||||
|
editor_view_path = NodePath("../../../../..")
|
||||||
|
mode = 1
|
||||||
|
title = "func f(value : Var, res : Resource, all_res : Array[Resource], row_index : int):"
|
||||||
|
default_text = "value"
|
||||||
|
default_text_ml = "return value"
|
||||||
|
function_save_key = "process"
|
||||||
|
|
||||||
|
[node name="HBoxContainer3" type="HBoxContainer" parent="HeaderContentSplit/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="HeaderContentSplit/VBoxContainer/HBoxContainer3"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Grid"
|
||||||
|
|
||||||
|
[node name="Refresh" type="Button" parent="HeaderContentSplit/VBoxContainer/HBoxContainer3"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Refresh"
|
||||||
|
script = ExtResource("3_7ja2l")
|
||||||
|
icon_name = "Loop"
|
||||||
|
|
||||||
|
[node name="Pages" type="HBoxContainer" parent="HeaderContentSplit/VBoxContainer/HBoxContainer3" node_paths=PackedStringArray("node_editor_view_root")]
|
||||||
|
layout_mode = 2
|
||||||
|
script = ExtResource("5_ka2yn")
|
||||||
|
node_editor_view_root = NodePath("../../../..")
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="HeaderContentSplit/VBoxContainer/HBoxContainer3/Pages"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Page:"
|
||||||
|
|
||||||
|
[node name="Pagelist" type="HBoxContainer" parent="HeaderContentSplit/VBoxContainer/HBoxContainer3/Pages"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label2" type="Label" parent="HeaderContentSplit/VBoxContainer/HBoxContainer3/Pages"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Rows per page:"
|
||||||
|
|
||||||
|
[node name="LineEdit" type="SpinBox" parent="HeaderContentSplit/VBoxContainer/HBoxContainer3/Pages"]
|
||||||
|
layout_mode = 2
|
||||||
|
min_value = 2.0
|
||||||
|
max_value = 300.0
|
||||||
|
value = 50.0
|
||||||
|
|
||||||
|
[node name="HSeparator" type="HSeparator" parent="HeaderContentSplit/VBoxContainer/HBoxContainer3"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="Sep" type="Control" parent="HeaderContentSplit/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Columns" type="Control" parent="HeaderContentSplit/VBoxContainer"]
|
||||||
|
clip_contents = true
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Columns" type="HBoxContainer" parent="HeaderContentSplit/VBoxContainer/Columns" node_paths=PackedStringArray("editor_view", "hide_columns_button", "grid")]
|
||||||
|
layout_mode = 0
|
||||||
|
theme_override_constants/separation = 0
|
||||||
|
script = ExtResource("6_emnmd")
|
||||||
|
table_header_scene = ExtResource("7_3dx0v")
|
||||||
|
editor_view = NodePath("../../../..")
|
||||||
|
hide_columns_button = NodePath("../../MenuStrip/VisibleCols")
|
||||||
|
grid = NodePath("../../../MarginContainer/FooterContentSplit/Panel/Scroll/MarginContainer/TableGrid")
|
||||||
|
|
||||||
|
[node name="Sep2" type="Control" parent="HeaderContentSplit/VBoxContainer"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="HeaderContentSplit"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="FooterContentSplit" type="VBoxContainer" parent="HeaderContentSplit/MarginContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="Panel" type="MarginContainer" parent="HeaderContentSplit/MarginContainer/FooterContentSplit"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Panel" type="Panel" parent="HeaderContentSplit/MarginContainer/FooterContentSplit/Panel"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Scroll" type="ScrollContainer" parent="HeaderContentSplit/MarginContainer/FooterContentSplit/Panel"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="HeaderContentSplit/MarginContainer/FooterContentSplit/Panel/Scroll"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="TableGrid" type="GridContainer" parent="HeaderContentSplit/MarginContainer/FooterContentSplit/Panel/Scroll/MarginContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/h_separation = 0
|
||||||
|
theme_override_constants/v_separation = 0
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="HeaderContentSplit/MarginContainer/FooterContentSplit/Panel"]
|
||||||
|
self_modulate = Color(1, 1, 1, 0.498039)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
text = "No folder selected!
|
||||||
|
Please select a folder to edit using the text field or Open button above.
|
||||||
|
|
||||||
|
Then, Shift+Click or Ctrl+Click cells to edit them using the keyboard,
|
||||||
|
Inspector dock or this screen's bottom panels.
|
||||||
|
|
||||||
|
To find out keybindings available, open the \"About\" menu."
|
||||||
|
horizontal_alignment = 1
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="Footer" type="VBoxContainer" parent="HeaderContentSplit/MarginContainer/FooterContentSplit"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="PropertyEditors" type="VBoxContainer" parent="HeaderContentSplit/MarginContainer/FooterContentSplit/Footer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="EditEnumArray" parent="HeaderContentSplit/MarginContainer/FooterContentSplit/Footer/PropertyEditors" instance=ExtResource("8_234wn")]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="EditArray" parent="HeaderContentSplit/MarginContainer/FooterContentSplit/Footer/PropertyEditors" instance=ExtResource("9_nts08")]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="EditDict" parent="HeaderContentSplit/MarginContainer/FooterContentSplit/Footer/PropertyEditors" instance=ExtResource("13_il556")]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="EditColor" parent="HeaderContentSplit/MarginContainer/FooterContentSplit/Footer/PropertyEditors" instance=ExtResource("10_nsma2")]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="EditNumber" parent="HeaderContentSplit/MarginContainer/FooterContentSplit/Footer/PropertyEditors" instance=ExtResource("11_q1ao4")]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="EditTexture" parent="HeaderContentSplit/MarginContainer/FooterContentSplit/Footer/PropertyEditors" instance=ExtResource("12_4kr6q")]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Control" type="Control" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
metadata/_edit_lock_ = true
|
||||||
|
|
||||||
|
[node name="FileDialog" type="FileDialog" parent="Control"]
|
||||||
|
title = "Open"
|
||||||
|
size = Vector2i(800, 500)
|
||||||
|
min_size = Vector2i(800, 400)
|
||||||
|
ok_button_text = "Open"
|
||||||
|
mode_overrides_title = false
|
||||||
|
file_mode = 3
|
||||||
|
filters = PackedStringArray("*.tres")
|
||||||
|
|
||||||
|
[node name="FileDialogText" type="FileDialog" parent="Control"]
|
||||||
|
title = "Open"
|
||||||
|
size = Vector2i(800, 500)
|
||||||
|
min_size = Vector2i(800, 400)
|
||||||
|
ok_button_text = "Open"
|
||||||
|
mode_overrides_title = false
|
||||||
|
file_mode = 0
|
||||||
|
filters = PackedStringArray("*.csv")
|
||||||
|
|
||||||
|
[node name="Info" type="AcceptDialog" parent="Control"]
|
||||||
|
title = "About"
|
||||||
|
size = Vector2i(800, 500)
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="Control/Info"]
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = 8.0
|
||||||
|
offset_top = 8.0
|
||||||
|
offset_right = -8.0
|
||||||
|
offset_bottom = -49.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="Panel" type="Panel" parent="Control/Info/MarginContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="RichTextLabel" type="RichTextLabel" parent="Control/Info/MarginContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
bbcode_enabled = true
|
||||||
|
text = "Edit Resources as Table 2
|
||||||
|
|
||||||
|
\"Welp, it is what it sounds like!\"
|
||||||
|
|
||||||
|
Possible inputs:
|
||||||
|
- Ctrl + Click / Cmd + Click - Select multiple cells in one column
|
||||||
|
- Shift + Click - Select all cells between A and B in one column
|
||||||
|
- Up / Down / Shift+Tab / Tab - move cell selection up/down/left/right
|
||||||
|
|
||||||
|
- Left/Right - Move cursor along cell text
|
||||||
|
- Backspace/Delete - Erase text Left / Right from cursor
|
||||||
|
- Home/End - Move cursor to start/end of cell
|
||||||
|
- Ctrl + <move/erase> / Cmd + <move/erase> - Move through / Erase whole word
|
||||||
|
|
||||||
|
- Ctrl/Cmd + C/V - Copy cells / Paste text into cells
|
||||||
|
- Ctrl/Cmd + (Shift) + Z - The Savior
|
||||||
|
If clipboard contains as many lines as there are cells selected, each line is pasted into a separate cell.
|
||||||
|
|
||||||
|
Made by Don Tnowe. 2022.
|
||||||
|
https://twitter.com/don_tnowe
|
||||||
|
Issues and contribution:
|
||||||
|
https://github.com/don-tnowe/godot-resources-as-sheets-plugin"
|
||||||
|
|
||||||
|
[node name="Settings" type="AcceptDialog" parent="Control"]
|
||||||
|
title = "Settings"
|
||||||
|
size = Vector2i(500, 300)
|
||||||
|
min_size = Vector2i(500, 300)
|
||||||
|
|
||||||
|
[node name="Settings" parent="Control/Settings" instance=ExtResource("13_as1sh")]
|
||||||
|
|
||||||
|
[node name="ImportExport" type="Window" parent="Control"]
|
||||||
|
process_mode = 3
|
||||||
|
initial_position = 4
|
||||||
|
size = Vector2i(600, 400)
|
||||||
|
visible = false
|
||||||
|
transient = true
|
||||||
|
exclusive = true
|
||||||
|
min_size = Vector2i(600, 400)
|
||||||
|
|
||||||
|
[node name="ImportExport" parent="Control/ImportExport" instance=ExtResource("14_3p12b")]
|
||||||
|
|
||||||
|
[node name="SelectionActions" parent="Control" node_paths=PackedStringArray("editor_view", "selection") instance=ExtResource("23_m53sx")]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
offset_left = -506.0
|
||||||
|
offset_top = 65.0
|
||||||
|
offset_right = -426.0
|
||||||
|
offset_bottom = 117.0
|
||||||
|
editor_view = NodePath("../..")
|
||||||
|
selection = NodePath("../../SelectionManager")
|
||||||
|
|
||||||
|
[node name="InputHandler" type="Node" parent="."]
|
||||||
|
script = ExtResource("14_2t57a")
|
||||||
|
|
||||||
|
[node name="SelectionManager" type="Control" parent="." node_paths=PackedStringArray("node_property_editors", "scrollbar")]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
script = ExtResource("15_mx6qn")
|
||||||
|
cell_editor_classes = Array[Script]([ExtResource("16_p7n52"), ExtResource("19_oeuko"), ExtResource("17_sofdw"), ExtResource("18_oeewr"), ExtResource("19_7x44x"), ExtResource("20_swsbn"), ExtResource("21_58wf8"), ExtResource("22_bni8r")])
|
||||||
|
node_property_editors = NodePath("../HeaderContentSplit/MarginContainer/FooterContentSplit/Footer/PropertyEditors")
|
||||||
|
scrollbar = NodePath("../HeaderContentSplit/MarginContainer/FooterContentSplit/Panel/Scroll")
|
||||||
|
metadata/_edit_lock_ = true
|
||||||
|
|
||||||
|
[connection signal="grid_updated" from="." to="HeaderContentSplit/VBoxContainer/HBoxContainer3/Pages" method="_on_grid_updated"]
|
||||||
|
[connection signal="gui_input" from="." to="InputHandler" method="_gui_input"]
|
||||||
|
[connection signal="pressed" from="HeaderContentSplit/VBoxContainer/MenuStrip/File" to="." method="_on_File_pressed"]
|
||||||
|
[connection signal="pressed" from="HeaderContentSplit/VBoxContainer/MenuStrip/SearchProcess" to="." method="_on_SearchProcess_pressed"]
|
||||||
|
[connection signal="about_to_popup" from="HeaderContentSplit/VBoxContainer/MenuStrip/VisibleCols" to="HeaderContentSplit/VBoxContainer/Columns/Columns" method="_on_visible_cols_about_to_popup"]
|
||||||
|
[connection signal="pressed" from="HeaderContentSplit/VBoxContainer/MenuStrip/Settings" to="Control/Settings" method="popup_centered"]
|
||||||
|
[connection signal="pressed" from="HeaderContentSplit/VBoxContainer/MenuStrip/Info" to="Control/Info" method="popup_centered"]
|
||||||
|
[connection signal="text_submitted" from="HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer/Path" to="." method="_on_path_text_submitted"]
|
||||||
|
[connection signal="pressed" from="HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer/SelectDir" to="Control/FileDialog" method="popup_centered"]
|
||||||
|
[connection signal="pressed" from="HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer/DeletePath" to="HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer2/RecentPaths" method="remove_selected_path_from_recent"]
|
||||||
|
[connection signal="pressed" from="HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer2/ImportExport" to="Control/FileDialogText" method="popup_centered"]
|
||||||
|
[connection signal="pressed" from="HeaderContentSplit/VBoxContainer/HBoxContainer3/Refresh" to="." method="_on_path_text_submitted"]
|
||||||
|
[connection signal="value_changed" from="HeaderContentSplit/VBoxContainer/HBoxContainer3/Pages/LineEdit" to="HeaderContentSplit/VBoxContainer/HBoxContainer3/Pages" method="_on_LineEdit_value_changed"]
|
||||||
|
[connection signal="dir_selected" from="Control/FileDialog" to="." method="_on_FileDialog_dir_selected"]
|
||||||
|
[connection signal="file_selected" from="Control/FileDialog" to="." method="_on_FileDialog_dir_selected"]
|
||||||
|
[connection signal="dir_selected" from="Control/FileDialogText" to="Control/ImportExport/ImportExport" method="_on_file_selected"]
|
||||||
|
[connection signal="file_selected" from="Control/FileDialogText" to="Control/ImportExport/ImportExport" method="_on_file_selected"]
|
||||||
|
[connection signal="close_requested" from="Control/ImportExport" to="Control/ImportExport" method="hide"]
|
||||||
|
[connection signal="cells_rightclicked" from="SelectionManager" to="Control/SelectionActions" method="_on_grid_cells_rightclicked"]
|
||||||
|
[connection signal="cells_selected" from="SelectionManager" to="Control/SelectionActions" method="_on_grid_cells_selected"]
|
@ -0,0 +1,36 @@
|
|||||||
|
class_name ResourceTablesEditFormat
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
var editor_view : Control
|
||||||
|
|
||||||
|
## Override to define reading behaviour.
|
||||||
|
func get_value(entry, key : String):
|
||||||
|
pass
|
||||||
|
|
||||||
|
## Override to define writing behaviour. This is NOT supposed to save - use `save_entries`.
|
||||||
|
func set_value(entry, key : String, value, index : int):
|
||||||
|
pass
|
||||||
|
|
||||||
|
## Override to define how the data gets saved.
|
||||||
|
func save_entries(all_entries : Array, indices : Array):
|
||||||
|
pass
|
||||||
|
|
||||||
|
## Override to allow editing rows from the Inspector.
|
||||||
|
func create_resource(entry) -> Resource:
|
||||||
|
return Resource.new()
|
||||||
|
|
||||||
|
## Override to define duplication behaviour. `name_input` should be a suffix if multiple entries, and full name if one.
|
||||||
|
func duplicate_rows(rows : Array, name_input : String):
|
||||||
|
pass
|
||||||
|
|
||||||
|
## Override to define removal behaviour.
|
||||||
|
func delete_rows(rows : Array):
|
||||||
|
pass
|
||||||
|
|
||||||
|
## Override with `return true` if `resource_path` is defined and the Rename butoon should show.
|
||||||
|
func has_row_names():
|
||||||
|
return false
|
||||||
|
|
||||||
|
## Override to define import behaviour. Must return the `rows` value for the editor view.
|
||||||
|
func import_from_path(folderpath : String, insert_func : Callable, sort_by : String, sort_reverse : bool = false) -> Array:
|
||||||
|
return []
|
@ -0,0 +1,88 @@
|
|||||||
|
class_name ResourceTablesEditFormatCsv
|
||||||
|
extends ResourceTablesEditFormatTres
|
||||||
|
|
||||||
|
var import_data
|
||||||
|
var csv_rows = []
|
||||||
|
var resource_original_positions = {}
|
||||||
|
|
||||||
|
|
||||||
|
func get_value(entry, key : String):
|
||||||
|
return entry.get(key)
|
||||||
|
|
||||||
|
|
||||||
|
func set_value(entry, key : String, value, index : int):
|
||||||
|
entry.set(key, value)
|
||||||
|
csv_rows[resource_original_positions[entry]] = import_data.resource_to_strings(entry)
|
||||||
|
|
||||||
|
|
||||||
|
func save_entries(all_entries : Array, indices : Array, repeat : bool = true):
|
||||||
|
if timer == null or timer.time_left <= 0.0:
|
||||||
|
var space_after_delimeter = import_data.delimeter.ends_with(" ")
|
||||||
|
var file = FileAccess.open(import_data.edited_path, FileAccess.WRITE)
|
||||||
|
for x in csv_rows:
|
||||||
|
if space_after_delimeter:
|
||||||
|
for i in x.size():
|
||||||
|
if i == 0: continue
|
||||||
|
x[i] = " " + x[i]
|
||||||
|
|
||||||
|
file.store_csv_line(x, import_data.delimeter[0])
|
||||||
|
|
||||||
|
if repeat:
|
||||||
|
timer = editor_view.get_tree().create_timer(3.0)
|
||||||
|
timer.timeout.connect(save_entries.bind(all_entries, indices, false))
|
||||||
|
|
||||||
|
|
||||||
|
func create_resource(entry) -> Resource:
|
||||||
|
return entry
|
||||||
|
|
||||||
|
|
||||||
|
func duplicate_rows(rows : Array, name_input : String):
|
||||||
|
for x in rows:
|
||||||
|
var new_res = x.duplicate()
|
||||||
|
var index = resource_original_positions[x]
|
||||||
|
csv_rows.insert(index, import_data.resource_to_strings(new_res))
|
||||||
|
_bump_row_indices(index + 1, 1)
|
||||||
|
resource_original_positions[new_res] = index + 1
|
||||||
|
|
||||||
|
save_entries([], [])
|
||||||
|
|
||||||
|
|
||||||
|
func delete_rows(rows):
|
||||||
|
for x in rows:
|
||||||
|
var index = resource_original_positions[x]
|
||||||
|
csv_rows.remove(index)
|
||||||
|
_bump_row_indices(index, -1)
|
||||||
|
resource_original_positions.erase(x)
|
||||||
|
|
||||||
|
save_entries([], [])
|
||||||
|
|
||||||
|
|
||||||
|
func has_row_names():
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
func _bump_row_indices(from : int, increment : int = 1):
|
||||||
|
for k in resource_original_positions:
|
||||||
|
if resource_original_positions[k] >= from:
|
||||||
|
resource_original_positions[k] += increment
|
||||||
|
|
||||||
|
|
||||||
|
func import_from_path(path : String, insert_func : Callable, sort_by : String, sort_reverse : bool = false) -> Array:
|
||||||
|
import_data = load(path)
|
||||||
|
var file = FileAccess.open(import_data.edited_path, FileAccess.READ)
|
||||||
|
csv_rows = ResourceTablesImportFormatCsv.import_as_arrays(import_data)
|
||||||
|
|
||||||
|
var rows := []
|
||||||
|
var res : Resource
|
||||||
|
resource_original_positions.clear()
|
||||||
|
for i in csv_rows.size():
|
||||||
|
if import_data.remove_first_row and i == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
res = import_data.strings_to_resource(csv_rows[i])
|
||||||
|
res.resource_path = ""
|
||||||
|
insert_func.call(res, rows, sort_by, sort_reverse)
|
||||||
|
resource_original_positions[res] = i
|
||||||
|
|
||||||
|
editor_view.fill_property_data(rows[0])
|
||||||
|
return rows
|
@ -0,0 +1,87 @@
|
|||||||
|
class_name ResourceTablesEditFormatTres
|
||||||
|
extends ResourceTablesEditFormat
|
||||||
|
|
||||||
|
var timer : SceneTreeTimer
|
||||||
|
|
||||||
|
|
||||||
|
func get_value(entry, key : String):
|
||||||
|
return entry[key]
|
||||||
|
|
||||||
|
|
||||||
|
func set_value(entry, key : String, value, index : int):
|
||||||
|
entry[key] = value
|
||||||
|
|
||||||
|
|
||||||
|
func save_entries(all_entries : Array, indices : Array, repeat : bool = true):
|
||||||
|
# No need to save. Resources are saved with Ctrl+S
|
||||||
|
# (likely because plugin.edit_resource is called to show inspector)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
func create_resource(entry) -> Resource:
|
||||||
|
return entry
|
||||||
|
|
||||||
|
|
||||||
|
func duplicate_rows(rows : Array, name_input : String):
|
||||||
|
if rows.size() == 1:
|
||||||
|
var new_row = rows[0].duplicate()
|
||||||
|
new_row.resource_path = rows[0].resource_path.get_base_dir() + "/" + name_input + ".tres"
|
||||||
|
ResourceSaver.save(new_row)
|
||||||
|
return
|
||||||
|
|
||||||
|
var new_row
|
||||||
|
for x in rows:
|
||||||
|
new_row = x.duplicate()
|
||||||
|
new_row.resource_path = x.resource_path.get_basename() + name_input + ".tres"
|
||||||
|
ResourceSaver.save(new_row)
|
||||||
|
|
||||||
|
|
||||||
|
func rename_row(row, new_name : String):
|
||||||
|
var new_row = row
|
||||||
|
DirAccess.open("res://").remove(row.resource_path)
|
||||||
|
new_row.resource_path = row.resource_path.get_base_dir() + "/" + new_name + ".tres"
|
||||||
|
ResourceSaver.save(new_row)
|
||||||
|
|
||||||
|
|
||||||
|
func delete_rows(rows):
|
||||||
|
for x in rows:
|
||||||
|
DirAccess.open("res://").remove(x.resource_path)
|
||||||
|
|
||||||
|
|
||||||
|
func has_row_names():
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func import_from_path(folderpath : String, insert_func : Callable, sort_by : String, sort_reverse : bool = false) -> Array:
|
||||||
|
var rows := []
|
||||||
|
var dir := DirAccess.open(folderpath)
|
||||||
|
if dir == null: return []
|
||||||
|
|
||||||
|
var file_stack : Array[String] = []
|
||||||
|
var folder_stack : Array[String] = [folderpath]
|
||||||
|
|
||||||
|
while folder_stack.size() > 0:
|
||||||
|
folderpath = folder_stack.pop_back()
|
||||||
|
|
||||||
|
for x in DirAccess.get_files_at(folderpath):
|
||||||
|
file_stack.append(folderpath.path_join(x))
|
||||||
|
|
||||||
|
for x in DirAccess.get_directories_at(folderpath):
|
||||||
|
folder_stack.append(folderpath.path_join(x))
|
||||||
|
|
||||||
|
var loaded_res : Array[Resource] = []
|
||||||
|
var res : Resource = null
|
||||||
|
var cur_dir_types : Dictionary = {}
|
||||||
|
loaded_res.resize(file_stack.size())
|
||||||
|
for i in file_stack.size():
|
||||||
|
res = null
|
||||||
|
if file_stack[i].ends_with(".tres"):
|
||||||
|
res = load(file_stack[i])
|
||||||
|
loaded_res[i] = res
|
||||||
|
|
||||||
|
editor_view.fill_property_data_many(loaded_res)
|
||||||
|
for x in loaded_res:
|
||||||
|
if x == null: continue
|
||||||
|
insert_func.call(x, rows, sort_by, sort_reverse)
|
||||||
|
|
||||||
|
return rows
|
@ -0,0 +1,32 @@
|
|||||||
|
class_name ResourceTablesExportFormatCsv
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
|
||||||
|
static func can_edit_path(path : String):
|
||||||
|
return path.ends_with(".csv")
|
||||||
|
|
||||||
|
|
||||||
|
static func export_to_file(entries_array : Array, column_names : Array, into_path : String, import_data):
|
||||||
|
var file = FileAccess.open(into_path, FileAccess.WRITE)
|
||||||
|
|
||||||
|
var line = PackedStringArray()
|
||||||
|
var space_after_delimeter = import_data.delimeter.ends_with(" ")
|
||||||
|
import_data.prop_names = column_names
|
||||||
|
import_data.prop_types = import_data.get_resource_property_types(entries_array[0], column_names)
|
||||||
|
import_data.resource_path = ""
|
||||||
|
line.resize(column_names.size())
|
||||||
|
if import_data.remove_first_row:
|
||||||
|
for j in column_names.size():
|
||||||
|
line[j] = column_names[j]
|
||||||
|
if space_after_delimeter and j != 0:
|
||||||
|
line[j] = " " + line[j]
|
||||||
|
|
||||||
|
file.store_csv_line(line, import_data.delimeter[0])
|
||||||
|
|
||||||
|
for i in entries_array.size():
|
||||||
|
for j in column_names.size():
|
||||||
|
line[j] = import_data.property_to_string((entries_array[i].get(column_names[j])), j)
|
||||||
|
if space_after_delimeter and j != 0:
|
||||||
|
line[j] = " " + line[j]
|
||||||
|
|
||||||
|
file.store_csv_line(line, import_data.delimeter[0])
|
@ -0,0 +1,58 @@
|
|||||||
|
class_name ResourceTablesImportFormatCsv
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
|
||||||
|
static func can_edit_path(path : String):
|
||||||
|
return path.ends_with(".csv")
|
||||||
|
|
||||||
|
|
||||||
|
static func get_properties(entries, import_data):
|
||||||
|
return Array(entries[0])
|
||||||
|
|
||||||
|
|
||||||
|
static func import_as_arrays(import_data) -> Array:
|
||||||
|
var file = FileAccess.open(import_data.edited_path, FileAccess.READ)
|
||||||
|
|
||||||
|
import_data.delimeter = ";"
|
||||||
|
var text_lines := [file.get_line().split(import_data.delimeter)]
|
||||||
|
var space_after_delimeter = false
|
||||||
|
var line = text_lines[0]
|
||||||
|
if line.size() == 0:
|
||||||
|
return []
|
||||||
|
|
||||||
|
if line.size() == 1:
|
||||||
|
import_data.delimeter = ","
|
||||||
|
line = line[0].split(import_data.delimeter)
|
||||||
|
text_lines[0] = line
|
||||||
|
if line.size() <= 1:
|
||||||
|
return []
|
||||||
|
|
||||||
|
if line[1].begins_with(" "):
|
||||||
|
for i in line.size():
|
||||||
|
line[i] = line[i].trim_prefix(" ")
|
||||||
|
|
||||||
|
text_lines[0] = line
|
||||||
|
space_after_delimeter = true
|
||||||
|
import_data.delimeter += " "
|
||||||
|
|
||||||
|
while !file.eof_reached():
|
||||||
|
line = file.get_csv_line(import_data.delimeter[0])
|
||||||
|
if space_after_delimeter:
|
||||||
|
for i in line.size():
|
||||||
|
line[i] = line[i].trim_prefix(" ")
|
||||||
|
|
||||||
|
if line.size() == text_lines[0].size():
|
||||||
|
text_lines.append(line)
|
||||||
|
|
||||||
|
elif line.size() != 1:
|
||||||
|
line.resize(text_lines[0].size())
|
||||||
|
text_lines.append(line)
|
||||||
|
|
||||||
|
var entries = []
|
||||||
|
entries.resize(text_lines.size())
|
||||||
|
|
||||||
|
for i in entries.size():
|
||||||
|
entries[i] = text_lines[i]
|
||||||
|
|
||||||
|
import_data.prop_names = entries[0]
|
||||||
|
return entries
|
@ -0,0 +1,222 @@
|
|||||||
|
@tool
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
@export var prop_list_item_scene : PackedScene
|
||||||
|
@export var formats_export : Array[Script]
|
||||||
|
@export var formats_import : Array[Script]
|
||||||
|
|
||||||
|
@onready var editor_view := $"../../.."
|
||||||
|
@onready var filename_options := $"Import/Margins/Scroll/Box/Grid/UseAsFilename"
|
||||||
|
@onready var classname_world := $"Import/Margins/Scroll/Box/Grid/Classname"
|
||||||
|
@onready var prop_list := $"Import/Margins/Scroll/Box"
|
||||||
|
@onready var file_dialog = $"../../FileDialogText"
|
||||||
|
|
||||||
|
var format_extension := ".csv"
|
||||||
|
var entries := []
|
||||||
|
|
||||||
|
var property_used_as_filename := 0
|
||||||
|
var import_data : ResourceTablesImport
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
var create_file_button = Button.new()
|
||||||
|
file_dialog.get_child(3, true).get_child(3, true).add_child(create_file_button)
|
||||||
|
create_file_button.get_parent().move_child(create_file_button, 2)
|
||||||
|
create_file_button.text = "Create File"
|
||||||
|
create_file_button.visible = true
|
||||||
|
create_file_button.icon = get_theme_icon(&"New", &"EditorIcons")
|
||||||
|
create_file_button.pressed.connect(_on_create_file_pressed)
|
||||||
|
hide()
|
||||||
|
show()
|
||||||
|
get_parent().min_size = Vector2(600, 400)
|
||||||
|
get_parent().size = Vector2(600, 400)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_create_file_pressed():
|
||||||
|
var new_name = (
|
||||||
|
file_dialog.get_child(3, true).get_child(3, true).get_child(1, true).text
|
||||||
|
)
|
||||||
|
if new_name == "":
|
||||||
|
new_name += editor_view.current_path.get_base_dir().get_file()
|
||||||
|
|
||||||
|
var file = FileAccess.open((
|
||||||
|
file_dialog.get_child(3, true).get_child(0, true).get_child(6, true).text
|
||||||
|
+ "/"
|
||||||
|
+ new_name.get_basename() + format_extension
|
||||||
|
), FileAccess.WRITE)
|
||||||
|
file_dialog.invalidate()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_file_selected(path : String):
|
||||||
|
import_data = ResourceTablesImport.new()
|
||||||
|
import_data.initialize(path)
|
||||||
|
_reset_controls()
|
||||||
|
await get_tree().process_frame
|
||||||
|
_open_dialog(path)
|
||||||
|
get_parent().popup_centered()
|
||||||
|
position = Vector2.ZERO
|
||||||
|
|
||||||
|
|
||||||
|
func _open_dialog(path : String):
|
||||||
|
classname_world.text = import_data.edited_path.get_file().get_basename()\
|
||||||
|
.capitalize().replace(" ", "")
|
||||||
|
import_data.script_classname = classname_world.text
|
||||||
|
|
||||||
|
for x in formats_import:
|
||||||
|
if x.new().can_edit_path(path):
|
||||||
|
entries = x.new().import_as_arrays(import_data)
|
||||||
|
|
||||||
|
_load_property_names(path)
|
||||||
|
_create_prop_editors()
|
||||||
|
$"Import/Margins/Scroll/Box/StyleSettingsI"._send_signal()
|
||||||
|
|
||||||
|
|
||||||
|
func _load_property_names(path):
|
||||||
|
var prop_types = import_data.prop_types
|
||||||
|
prop_types.resize(import_data.prop_names.size())
|
||||||
|
prop_types.fill(4)
|
||||||
|
for i in import_data.prop_names.size():
|
||||||
|
import_data.prop_names[i] = entries[0][i]\
|
||||||
|
.replace("\"", "")\
|
||||||
|
.replace(" ", "_")\
|
||||||
|
.replace("-", "_")\
|
||||||
|
.replace(".", "_")\
|
||||||
|
.replace(",", "_")\
|
||||||
|
.replace("\t", "_")\
|
||||||
|
.replace("/", "_")\
|
||||||
|
.replace("\\", "_")\
|
||||||
|
.to_lower()
|
||||||
|
|
||||||
|
# Don't guess Ints automatically - further rows might have floats
|
||||||
|
if entries[1][i].is_valid_float():
|
||||||
|
prop_types[i] = ResourceTablesImport.PropType.FLOAT
|
||||||
|
|
||||||
|
elif entries[1][i].begins_with("res://"):
|
||||||
|
prop_types[i] = ResourceTablesImport.PropType.OBJECT
|
||||||
|
|
||||||
|
else:
|
||||||
|
prop_types[i] = ResourceTablesImport.PropType.STRING
|
||||||
|
|
||||||
|
filename_options.clear()
|
||||||
|
for i in import_data.prop_names.size():
|
||||||
|
filename_options.add_item(import_data.prop_names[i], i)
|
||||||
|
|
||||||
|
|
||||||
|
func _create_prop_editors():
|
||||||
|
for x in prop_list.get_children():
|
||||||
|
if !x is GridContainer: x.free()
|
||||||
|
|
||||||
|
for i in import_data.prop_names.size():
|
||||||
|
var new_node = prop_list_item_scene.instantiate()
|
||||||
|
prop_list.add_child(new_node)
|
||||||
|
new_node.display(import_data.prop_names[i], import_data.prop_types[i])
|
||||||
|
new_node.connect_all_signals(self, i)
|
||||||
|
|
||||||
|
|
||||||
|
func _generate_class(save_script = true):
|
||||||
|
save_script = true # Built-ins didn't work in 3.x, won't change because dont wanna test rn
|
||||||
|
import_data.new_script = import_data.generate_script(entries, save_script)
|
||||||
|
if save_script:
|
||||||
|
import_data.new_script.resource_path = import_data.edited_path.get_basename() + ".gd"
|
||||||
|
ResourceSaver.save(import_data.new_script)
|
||||||
|
# Because when instanced, objects have a copy of the script
|
||||||
|
import_data.new_script = load(import_data.edited_path.get_basename() + ".gd")
|
||||||
|
|
||||||
|
|
||||||
|
func _export_tres_folder():
|
||||||
|
DirAccess.open("res://").make_dir_recursive(import_data.edited_path.get_basename())
|
||||||
|
|
||||||
|
import_data.prop_used_as_filename = import_data.prop_names[property_used_as_filename]
|
||||||
|
var new_res : Resource
|
||||||
|
for i in entries.size():
|
||||||
|
if import_data.remove_first_row and i == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
new_res = import_data.strings_to_resource(entries[i])
|
||||||
|
ResourceSaver.save(new_res)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_import_to_tres_pressed():
|
||||||
|
_generate_class()
|
||||||
|
_export_tres_folder()
|
||||||
|
await get_tree().process_frame
|
||||||
|
editor_view.display_folder(import_data.edited_path.get_basename() + "/")
|
||||||
|
await get_tree().process_frame
|
||||||
|
editor_view.refresh()
|
||||||
|
close()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_import_edit_pressed():
|
||||||
|
_generate_class(false)
|
||||||
|
import_data.prop_used_as_filename = ""
|
||||||
|
import_data.save()
|
||||||
|
await get_tree().process_frame
|
||||||
|
editor_view.display_folder(import_data.resource_path)
|
||||||
|
editor_view.node_columns.hidden_columns[editor_view.current_path] = {
|
||||||
|
"resource_path" : true,
|
||||||
|
"resource_local_to_scene" : true,
|
||||||
|
}
|
||||||
|
editor_view.save_data()
|
||||||
|
await get_tree().process_frame
|
||||||
|
editor_view.refresh()
|
||||||
|
close()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_export_csv_pressed():
|
||||||
|
var exported_cols = editor_view.columns.duplicate()
|
||||||
|
exported_cols.erase("resource_local_to_scene")
|
||||||
|
for x in editor_view.node_columns.hidden_columns[editor_view.current_path].keys():
|
||||||
|
exported_cols.erase(x)
|
||||||
|
|
||||||
|
ResourceTablesExportFormatCsv.export_to_file(editor_view.rows, exported_cols, import_data.edited_path, import_data)
|
||||||
|
await get_tree().process_frame
|
||||||
|
editor_view.refresh()
|
||||||
|
close()
|
||||||
|
|
||||||
|
|
||||||
|
# Input controls
|
||||||
|
func _on_classname_world_text_changed(new_text : String):
|
||||||
|
import_data.script_classname = new_text.replace(" ", "")
|
||||||
|
|
||||||
|
|
||||||
|
func _on_remove_first_row_toggled(button_pressed : bool):
|
||||||
|
import_data.remove_first_row = button_pressed
|
||||||
|
# $"Export/Box2/Button".button_pressed = true
|
||||||
|
$"Export/Box3/CheckBox".button_pressed = button_pressed
|
||||||
|
|
||||||
|
|
||||||
|
func _on_filename_options_item_selected(index):
|
||||||
|
property_used_as_filename = index
|
||||||
|
|
||||||
|
|
||||||
|
func _on_list_item_type_selected(type : int, index : int):
|
||||||
|
import_data.prop_types[index] = type
|
||||||
|
|
||||||
|
|
||||||
|
func _on_list_item_name_changed(name : String, index : int):
|
||||||
|
import_data.prop_names[index] = name.replace(" ", "")
|
||||||
|
|
||||||
|
|
||||||
|
func _on_export_delimeter_pressed(del : String):
|
||||||
|
import_data.delimeter = del + import_data.delimeter.substr(1)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_export_space_toggled(button_pressed : bool):
|
||||||
|
import_data.delimeter = (
|
||||||
|
import_data.delimeter[0]
|
||||||
|
if !button_pressed else
|
||||||
|
import_data.delimeter + " "
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _reset_controls():
|
||||||
|
$"Export/Box/CheckBox".button_pressed = false
|
||||||
|
_on_remove_first_row_toggled(true)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_enum_format_changed(case, delimiter, bool_yes, bool_no):
|
||||||
|
import_data.enum_format = [case, delimiter, bool_yes, bool_no]
|
||||||
|
|
||||||
|
|
||||||
|
func close():
|
||||||
|
get_parent().hide()
|
@ -0,0 +1,188 @@
|
|||||||
|
[gd_scene load_steps=7 format=3 uid="uid://b413igx28kkvb"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/import_export/import_export_dialog.gd" id="1"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/import_export/formats_export/export_csv.gd" id="2_33c6s"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/import_export/formats_import/import_csv.gd" id="2_fxayt"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://b8llymigbprh6" path="res://addons/resources_spreadsheet_view/import_export/property_list_item.tscn" id="2_xfhmf"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://ckhf3bqy2rqjr" path="res://addons/resources_spreadsheet_view/import_export/import_export_enum_format.tscn" id="4"]
|
||||||
|
|
||||||
|
[sub_resource type="ButtonGroup" id="ButtonGroup_080hd"]
|
||||||
|
|
||||||
|
[node name="TabContainer" type="TabContainer"]
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1")
|
||||||
|
prop_list_item_scene = ExtResource("2_xfhmf")
|
||||||
|
formats_export = Array[Script]([ExtResource("2_33c6s")])
|
||||||
|
formats_import = Array[Script]([ExtResource("2_fxayt")])
|
||||||
|
|
||||||
|
[node name="Import" type="VBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Margins" type="MarginContainer" parent="Import"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="Scroll" type="ScrollContainer" parent="Import/Margins"]
|
||||||
|
layout_mode = 2
|
||||||
|
horizontal_scroll_mode = 0
|
||||||
|
|
||||||
|
[node name="Box" type="VBoxContainer" parent="Import/Margins/Scroll"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="Grid" type="GridContainer" parent="Import/Margins/Scroll/Box"]
|
||||||
|
layout_mode = 2
|
||||||
|
columns = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Import/Margins/Scroll/Box/Grid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Use as filename:"
|
||||||
|
|
||||||
|
[node name="UseAsFilename" type="OptionButton" parent="Import/Margins/Scroll/Box/Grid"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="Label2" type="Label" parent="Import/Margins/Scroll/Box/Grid"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Class Name"
|
||||||
|
|
||||||
|
[node name="Classname" type="LineEdit" parent="Import/Margins/Scroll/Box/Grid"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
caret_blink = true
|
||||||
|
caret_blink_interval = 0.5
|
||||||
|
|
||||||
|
[node name="RemoveFirstRow" type="CheckBox" parent="Import/Margins/Scroll/Box/Grid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "First row contains property names"
|
||||||
|
|
||||||
|
[node name="Control" type="Control" parent="Import/Margins/Scroll/Box/Grid"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Control2" type="Control" parent="Import/Margins/Scroll/Box/Grid"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="StyleSettingsI" parent="Import/Margins/Scroll/Box" instance=ExtResource("4")]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Box" type="HBoxContainer" parent="Import"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="Ok2" type="Button" parent="Import/Box"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Confirm and edit"
|
||||||
|
|
||||||
|
[node name="Ok" type="Button" parent="Import/Box"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Convert to Resources and edit"
|
||||||
|
|
||||||
|
[node name="Cancel" type="Button" parent="Import/Box"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Cancel"
|
||||||
|
|
||||||
|
[node name="Control" type="Control" parent="Import"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Export" type="VBoxContainer" parent="."]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Info" type="Label" parent="Export"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
text = "The currently edited folder will be exported into the selected file.
|
||||||
|
|
||||||
|
Rows hidden by the filter will NOT be exported, and order follows the current sorting key. Rows on non-selected pages will not be removed.
|
||||||
|
|
||||||
|
Hidden columns will NOT be exported."
|
||||||
|
autowrap_mode = 2
|
||||||
|
|
||||||
|
[node name="HSeparator" type="HSeparator" parent="Export"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Box" type="HBoxContainer" parent="Export"]
|
||||||
|
layout_mode = 2
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="Label2" type="Label" parent="Export/Box"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "Delimeter:"
|
||||||
|
|
||||||
|
[node name="Button" type="Button" parent="Export/Box"]
|
||||||
|
layout_mode = 2
|
||||||
|
toggle_mode = true
|
||||||
|
button_group = SubResource("ButtonGroup_080hd")
|
||||||
|
text = "Comma (,)"
|
||||||
|
|
||||||
|
[node name="Button2" type="Button" parent="Export/Box"]
|
||||||
|
layout_mode = 2
|
||||||
|
toggle_mode = true
|
||||||
|
button_group = SubResource("ButtonGroup_080hd")
|
||||||
|
text = "Semicolon (;)"
|
||||||
|
|
||||||
|
[node name="Button3" type="Button" parent="Export/Box"]
|
||||||
|
layout_mode = 2
|
||||||
|
toggle_mode = true
|
||||||
|
button_group = SubResource("ButtonGroup_080hd")
|
||||||
|
text = "Tab"
|
||||||
|
|
||||||
|
[node name="CheckBox" type="CheckBox" parent="Export/Box"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "With space after"
|
||||||
|
|
||||||
|
[node name="Box3" type="HBoxContainer" parent="Export"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="CheckBox" type="CheckBox" parent="Export/Box3"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "First row contains property names (CSV)"
|
||||||
|
|
||||||
|
[node name="StyleSettingsE" parent="Export" instance=ExtResource("4")]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Control" type="Control" parent="Export"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="Box2" type="HBoxContainer" parent="Export"]
|
||||||
|
layout_mode = 2
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="Button" type="Button" parent="Export/Box2"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Export to CSV"
|
||||||
|
|
||||||
|
[node name="Cancel" type="Button" parent="Export/Box2"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Cancel"
|
||||||
|
|
||||||
|
[node name="Control2" type="Control" parent="Export"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[connection signal="item_selected" from="Import/Margins/Scroll/Box/Grid/UseAsFilename" to="." method="_on_filename_options_item_selected"]
|
||||||
|
[connection signal="text_changed" from="Import/Margins/Scroll/Box/Grid/Classname" to="." method="_on_classname_field_text_changed"]
|
||||||
|
[connection signal="toggled" from="Import/Margins/Scroll/Box/Grid/RemoveFirstRow" to="." method="_on_remove_first_row_toggled"]
|
||||||
|
[connection signal="format_changed" from="Import/Margins/Scroll/Box/StyleSettingsI" to="." method="_on_enum_format_changed"]
|
||||||
|
[connection signal="format_changed" from="Import/Margins/Scroll/Box/StyleSettingsI" to="Export/StyleSettingsE" method="_on_format_changed"]
|
||||||
|
[connection signal="pressed" from="Import/Box/Ok2" to="." method="_on_import_edit_pressed"]
|
||||||
|
[connection signal="pressed" from="Import/Box/Ok" to="." method="_on_import_to_tres_pressed"]
|
||||||
|
[connection signal="pressed" from="Import/Box/Cancel" to="." method="close"]
|
||||||
|
[connection signal="pressed" from="Export/Box/Button" to="." method="_on_export_delimeter_pressed" binds= [","]]
|
||||||
|
[connection signal="pressed" from="Export/Box/Button2" to="." method="_on_export_delimeter_pressed" binds= [";"]]
|
||||||
|
[connection signal="pressed" from="Export/Box/Button3" to="." method="_on_export_delimeter_pressed" binds= [" "]]
|
||||||
|
[connection signal="toggled" from="Export/Box/CheckBox" to="." method="_on_export_space_toggled"]
|
||||||
|
[connection signal="toggled" from="Export/Box3/CheckBox" to="." method="_on_remove_first_row_toggled"]
|
||||||
|
[connection signal="format_changed" from="Export/StyleSettingsE" to="." method="_on_enum_format_changed"]
|
||||||
|
[connection signal="pressed" from="Export/Box2/Button" to="." method="_on_export_csv_pressed"]
|
||||||
|
[connection signal="pressed" from="Export/Box2/Cancel" to="." method="close"]
|
@ -0,0 +1,20 @@
|
|||||||
|
@tool
|
||||||
|
extends GridContainer
|
||||||
|
|
||||||
|
signal format_changed(case, delimiter, bool_yes, bool_no)
|
||||||
|
|
||||||
|
|
||||||
|
func _send_signal(arg1 = null):
|
||||||
|
format_changed.emit(
|
||||||
|
$"HBoxContainer/Case".selected,
|
||||||
|
[" ", "_", "-"][$"HBoxContainer/Separator".selected],
|
||||||
|
$"HBoxContainer2/True".text,
|
||||||
|
$"HBoxContainer2/False".text
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_format_changed(case, delimiter, bool_yes, bool_no):
|
||||||
|
$"HBoxContainer/Case".selected = case
|
||||||
|
$"HBoxContainer/Separator".selected = [" ", "_", "-"].find(delimiter)
|
||||||
|
$"HBoxContainer2/True".text = bool_yes
|
||||||
|
$"HBoxContainer2/False".text = bool_no
|
@ -0,0 +1,69 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://ckhf3bqy2rqjr"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/import_export/import_export_enum_format.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="EnumFormat" type="GridContainer"]
|
||||||
|
columns = 2
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="Label3" type="Label" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "Enum word case/separator"
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="Case" type="OptionButton" parent="HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
clip_text = true
|
||||||
|
item_count = 4
|
||||||
|
selected = 2
|
||||||
|
popup/item_0/text = "all lower"
|
||||||
|
popup/item_0/id = 0
|
||||||
|
popup/item_1/text = "caps Except First"
|
||||||
|
popup/item_1/id = 1
|
||||||
|
popup/item_2/text = "Caps Every Word"
|
||||||
|
popup/item_2/id = 2
|
||||||
|
popup/item_3/text = "ALL CAPS"
|
||||||
|
popup/item_3/id = 3
|
||||||
|
|
||||||
|
[node name="Separator" type="OptionButton" parent="HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_stretch_ratio = 0.5
|
||||||
|
clip_text = true
|
||||||
|
item_count = 3
|
||||||
|
popup/item_0/text = "Space \" \""
|
||||||
|
popup/item_0/id = 0
|
||||||
|
popup/item_1/text = "Underscore \"_\""
|
||||||
|
popup/item_1/id = 1
|
||||||
|
popup/item_2/text = "Kebab \"-\""
|
||||||
|
popup/item_2/id = 2
|
||||||
|
|
||||||
|
[node name="Label4" type="Label" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "Boolean True/False"
|
||||||
|
|
||||||
|
[node name="HBoxContainer2" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="True" type="LineEdit" parent="HBoxContainer2"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "Yes"
|
||||||
|
|
||||||
|
[node name="False" type="LineEdit" parent="HBoxContainer2"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "No"
|
||||||
|
|
||||||
|
[connection signal="mouse_entered" from="Label3" to="." method="_on_Label3_mouse_entered"]
|
||||||
|
[connection signal="item_selected" from="HBoxContainer/Case" to="." method="_send_signal"]
|
||||||
|
[connection signal="item_selected" from="HBoxContainer/Separator" to="." method="_send_signal"]
|
||||||
|
[connection signal="text_changed" from="HBoxContainer2/True" to="." method="_send_signal"]
|
||||||
|
[connection signal="text_changed" from="HBoxContainer2/False" to="." method="_send_signal"]
|
@ -0,0 +1,12 @@
|
|||||||
|
@tool
|
||||||
|
extends HBoxContainer
|
||||||
|
|
||||||
|
|
||||||
|
func display(name : String, type : int):
|
||||||
|
$"LineEdit".text = name
|
||||||
|
$"OptionButton".selected = type
|
||||||
|
|
||||||
|
|
||||||
|
func connect_all_signals(to : Object, index : int, prefix : String = "_on_list_item_"):
|
||||||
|
$"LineEdit".text_changed.connect(Callable(to, prefix + "name_changed").bind(index))
|
||||||
|
$"OptionButton".item_selected.connect(Callable(to, prefix + "type_selected").bind(index))
|
@ -0,0 +1,34 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://b8llymigbprh6"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/import_export/property_list_item.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="Entry" type="HBoxContainer"]
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="LineEdit" type="LineEdit" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_stretch_ratio = 0.5
|
||||||
|
text = "1"
|
||||||
|
|
||||||
|
[node name="OptionButton" type="OptionButton" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 5
|
||||||
|
size_flags_stretch_ratio = 0.25
|
||||||
|
item_count = 7
|
||||||
|
fit_to_longest_item = false
|
||||||
|
popup/item_0/text = "Bool"
|
||||||
|
popup/item_0/id = 1
|
||||||
|
popup/item_1/text = "Integer Number"
|
||||||
|
popup/item_1/id = 2
|
||||||
|
popup/item_2/text = "Floating Point Number"
|
||||||
|
popup/item_2/id = 3
|
||||||
|
popup/item_3/text = "String/Other"
|
||||||
|
popup/item_3/id = 4
|
||||||
|
popup/item_4/text = "Color"
|
||||||
|
popup/item_4/id = 14
|
||||||
|
popup/item_5/text = "Resource Path"
|
||||||
|
popup/item_5/id = 17
|
||||||
|
popup/item_6/text = "Enumeration"
|
||||||
|
popup/item_6/id = 101
|
@ -0,0 +1,292 @@
|
|||||||
|
@tool
|
||||||
|
class_name ResourceTablesImport
|
||||||
|
extends Resource
|
||||||
|
|
||||||
|
enum PropType {
|
||||||
|
BOOL,
|
||||||
|
INT,
|
||||||
|
FLOAT,
|
||||||
|
STRING,
|
||||||
|
COLOR,
|
||||||
|
OBJECT,
|
||||||
|
ENUM,
|
||||||
|
MAX,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NameCasing {
|
||||||
|
ALL_LOWER,
|
||||||
|
CAPS_WORD_EXCEPT_FIRST,
|
||||||
|
CAPS_WORD,
|
||||||
|
ALL_CAPS,
|
||||||
|
}
|
||||||
|
|
||||||
|
const SUFFIX := "_spreadsheet_import.tres"
|
||||||
|
const TYPE_MAP := {
|
||||||
|
TYPE_STRING : PropType.STRING,
|
||||||
|
TYPE_FLOAT : PropType.FLOAT,
|
||||||
|
TYPE_BOOL : PropType.BOOL,
|
||||||
|
TYPE_INT : PropType.INT,
|
||||||
|
TYPE_OBJECT : PropType.OBJECT,
|
||||||
|
TYPE_COLOR : PropType.COLOR,
|
||||||
|
}
|
||||||
|
|
||||||
|
@export var prop_types : Array
|
||||||
|
@export var prop_names : Array
|
||||||
|
|
||||||
|
@export var edited_path := "res://"
|
||||||
|
@export var prop_used_as_filename := ""
|
||||||
|
@export var script_classname := ""
|
||||||
|
@export var remove_first_row := true
|
||||||
|
|
||||||
|
@export var new_script : GDScript
|
||||||
|
@export var view_script : Script = ResourceTablesEditFormatCsv
|
||||||
|
@export var delimeter := ";"
|
||||||
|
@export var enum_format : Array = [NameCasing.CAPS_WORD, " ", "Yes", "No"]
|
||||||
|
|
||||||
|
@export var uniques : Dictionary
|
||||||
|
|
||||||
|
|
||||||
|
func initialize(path):
|
||||||
|
edited_path = path
|
||||||
|
prop_types = []
|
||||||
|
prop_names = []
|
||||||
|
|
||||||
|
|
||||||
|
func save():
|
||||||
|
resource_path = edited_path.get_basename() + SUFFIX
|
||||||
|
ResourceSaver.save.call_deferred(self)
|
||||||
|
|
||||||
|
|
||||||
|
func string_to_property(string : String, col_index : int):
|
||||||
|
match prop_types[col_index]:
|
||||||
|
PropType.STRING:
|
||||||
|
return string
|
||||||
|
|
||||||
|
PropType.BOOL:
|
||||||
|
string = string.to_lower()
|
||||||
|
if string == enum_format[2].to_lower(): return true
|
||||||
|
if string == enum_format[3].to_lower(): return false
|
||||||
|
return !string in ["no", "disabled", "-", "false", "absent", "wrong", "off", "0", ""]
|
||||||
|
|
||||||
|
PropType.FLOAT:
|
||||||
|
return string.to_float()
|
||||||
|
|
||||||
|
PropType.INT:
|
||||||
|
return string.to_int()
|
||||||
|
|
||||||
|
PropType.COLOR:
|
||||||
|
return Color(string)
|
||||||
|
|
||||||
|
PropType.OBJECT:
|
||||||
|
return load(string)
|
||||||
|
|
||||||
|
PropType.ENUM:
|
||||||
|
if string == "":
|
||||||
|
return int(uniques[col_index]["N_A"])
|
||||||
|
|
||||||
|
else:
|
||||||
|
return int(uniques[col_index][string.capitalize().replace(" ", "_").to_upper()])
|
||||||
|
|
||||||
|
|
||||||
|
func property_to_string(value, col_index : int) -> String:
|
||||||
|
if value == null: return ""
|
||||||
|
if col_index == 0:
|
||||||
|
if prop_names[col_index] == "resource_path":
|
||||||
|
return value.get_file().get_basename()
|
||||||
|
|
||||||
|
if prop_types[col_index] is PackedStringArray:
|
||||||
|
return prop_types[col_index][value].capitalize()
|
||||||
|
|
||||||
|
match prop_types[col_index]:
|
||||||
|
PropType.STRING:
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
PropType.BOOL:
|
||||||
|
return enum_format[2] if value else enum_format[3]
|
||||||
|
|
||||||
|
PropType.FLOAT, PropType.INT:
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
PropType.COLOR:
|
||||||
|
return value.to_html()
|
||||||
|
|
||||||
|
PropType.OBJECT:
|
||||||
|
return value.resource_path
|
||||||
|
|
||||||
|
PropType.ENUM:
|
||||||
|
var dict = uniques[col_index]
|
||||||
|
for k in dict:
|
||||||
|
if dict[k] == value:
|
||||||
|
return change_name_to_format(k, enum_format[0], enum_format[1])
|
||||||
|
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
func create_property_line_for_prop(col_index : int) -> String:
|
||||||
|
var result = "@export var " + prop_names[col_index] + " :"
|
||||||
|
match prop_types[col_index]:
|
||||||
|
PropType.STRING:
|
||||||
|
return result + "= \"\"\r\n"
|
||||||
|
|
||||||
|
PropType.BOOL:
|
||||||
|
return result + "= false\r\n"
|
||||||
|
|
||||||
|
PropType.FLOAT:
|
||||||
|
return result + "= 0.0\r\n"
|
||||||
|
|
||||||
|
PropType.INT:
|
||||||
|
return result + "= 0\r\n"
|
||||||
|
|
||||||
|
PropType.COLOR:
|
||||||
|
return result + "= Color.WHITE\r\n"
|
||||||
|
|
||||||
|
PropType.OBJECT:
|
||||||
|
return result + " Resource\r\n"
|
||||||
|
|
||||||
|
PropType.ENUM:
|
||||||
|
return result + " %s\r\n" % _escape_forbidden_enum_names(prop_names[col_index].capitalize().replace(" ", ""))
|
||||||
|
# return result.replace(
|
||||||
|
# "@export var",
|
||||||
|
# "@export_enum(" + _escape_forbidden_enum_names(
|
||||||
|
# prop_names[col_index].capitalize()\
|
||||||
|
# .replace(" ", "")
|
||||||
|
# ) + ") var"
|
||||||
|
# ) + "= 0\r\n"
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
func _escape_forbidden_enum_names(string : String) -> String:
|
||||||
|
if ClassDB.class_exists(string):
|
||||||
|
return string + "_"
|
||||||
|
|
||||||
|
# Not in ClassDB, but are engine types and can be property names
|
||||||
|
if string in [
|
||||||
|
"Color", "String", "Plane", "Projection",
|
||||||
|
"Basis", "Transform", "Variant",
|
||||||
|
]:
|
||||||
|
return string + "_"
|
||||||
|
|
||||||
|
return string
|
||||||
|
|
||||||
|
|
||||||
|
func create_enum_for_prop(col_index) -> String:
|
||||||
|
var result := (
|
||||||
|
"enum "
|
||||||
|
+ _escape_forbidden_enum_names(
|
||||||
|
prop_names[col_index].capitalize().replace(" ", "")
|
||||||
|
) + " {\r\n"
|
||||||
|
)
|
||||||
|
for k in uniques[col_index]:
|
||||||
|
result += (
|
||||||
|
"\t"
|
||||||
|
+ k # Enum Entry
|
||||||
|
+ " = "
|
||||||
|
+ str(uniques[col_index][k]) # Value
|
||||||
|
+ ",\r\n"
|
||||||
|
)
|
||||||
|
result += "\tMAX,\r\n}\r\n\r\n"
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
func generate_script(entries, has_classname = true) -> GDScript:
|
||||||
|
var source = ""
|
||||||
|
# if has_classname and script_classname != "":
|
||||||
|
# source = "class_name " + script_classname + " \r\nextends Resource\r\n\r\n"
|
||||||
|
#
|
||||||
|
# else:
|
||||||
|
source = "extends Resource\r\n\r\n"
|
||||||
|
|
||||||
|
# Enums
|
||||||
|
uniques = get_uniques(entries)
|
||||||
|
for i in prop_types.size():
|
||||||
|
if prop_types[i] == PropType.ENUM:
|
||||||
|
source += create_enum_for_prop(i)
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
for i in prop_names.size():
|
||||||
|
if (prop_names[i] != "resource_path") and (prop_names[i] != "resource_name"):
|
||||||
|
source += create_property_line_for_prop(i)
|
||||||
|
|
||||||
|
var created_script = GDScript.new()
|
||||||
|
created_script.source_code = source
|
||||||
|
created_script.reload()
|
||||||
|
return created_script
|
||||||
|
|
||||||
|
|
||||||
|
func strings_to_resource(strings : Array):
|
||||||
|
var new_res = new_script.new()
|
||||||
|
for j in min(prop_names.size(), strings.size()):
|
||||||
|
new_res.set(prop_names[j], string_to_property(strings[j], j))
|
||||||
|
|
||||||
|
if prop_used_as_filename != "":
|
||||||
|
new_res.resource_path = edited_path.get_basename() + "/" + new_res.get(prop_used_as_filename) + ".tres"
|
||||||
|
|
||||||
|
return new_res
|
||||||
|
|
||||||
|
|
||||||
|
func resource_to_strings(res : Resource):
|
||||||
|
var strings := []
|
||||||
|
strings.resize(prop_names.size())
|
||||||
|
for i in prop_names.size():
|
||||||
|
strings[i] = property_to_string(res.get(prop_names[i]), i)
|
||||||
|
|
||||||
|
return PackedStringArray(strings)
|
||||||
|
|
||||||
|
|
||||||
|
func get_uniques(entries : Array) -> Dictionary:
|
||||||
|
var result = {}
|
||||||
|
for i in prop_types.size():
|
||||||
|
if prop_types[i] == PropType.ENUM:
|
||||||
|
var cur_value := ""
|
||||||
|
result[i] = {}
|
||||||
|
for j in entries.size():
|
||||||
|
if j == 0 and remove_first_row: continue
|
||||||
|
|
||||||
|
cur_value = entries[j][i].capitalize().to_upper().replace(" ", "_")
|
||||||
|
if cur_value == "":
|
||||||
|
cur_value = "N_A"
|
||||||
|
|
||||||
|
if !result[i].has(cur_value):
|
||||||
|
result[i][cur_value] = result[i].size()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
static func change_name_to_format(name : String, case : int, delim : String):
|
||||||
|
var string = name.capitalize().replace(" ", delim)
|
||||||
|
if case == NameCasing.ALL_LOWER:
|
||||||
|
return string.to_lower()
|
||||||
|
|
||||||
|
if case == NameCasing.CAPS_WORD_EXCEPT_FIRST:
|
||||||
|
return string[0].to_lower() + string.substr(1)
|
||||||
|
|
||||||
|
if case == NameCasing.CAPS_WORD:
|
||||||
|
return string
|
||||||
|
|
||||||
|
if case == NameCasing.ALL_CAPS:
|
||||||
|
return string.to_upper()
|
||||||
|
|
||||||
|
|
||||||
|
static func get_resource_property_types(res : Resource, properties : Array) -> Array:
|
||||||
|
var result = []
|
||||||
|
result.resize(properties.size())
|
||||||
|
result.fill(PropType.STRING)
|
||||||
|
var cur_type := 0
|
||||||
|
for x in res.get_property_list():
|
||||||
|
var found = properties.find(x["name"])
|
||||||
|
if found == -1: continue
|
||||||
|
if x["usage"] & PROPERTY_USAGE_EDITOR != 0:
|
||||||
|
if x["hint"] == PROPERTY_HINT_ENUM:
|
||||||
|
var enum_values = x["hint_string"].split(",")
|
||||||
|
for i in enum_values.size():
|
||||||
|
var index_found : int = enum_values[i].find(":")
|
||||||
|
if index_found == -1: continue
|
||||||
|
enum_values[i] = enum_values[i].left(index_found)
|
||||||
|
|
||||||
|
result[found] = enum_values
|
||||||
|
|
||||||
|
else:
|
||||||
|
result[found] = TYPE_MAP.get(x["type"], PropType.STRING)
|
||||||
|
|
||||||
|
return result
|
@ -0,0 +1,153 @@
|
|||||||
|
@tool
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
const TablesPluginSettingsClass := preload("res://addons/resources_spreadsheet_view/settings_grid.gd")
|
||||||
|
|
||||||
|
@export var table_header_scene : PackedScene
|
||||||
|
|
||||||
|
@export @onready var editor_view : Control = $"../../../.."
|
||||||
|
@export @onready var hide_columns_button : BaseButton = $"../../MenuStrip/VisibleCols"
|
||||||
|
@export @onready var grid : GridContainer = $"../../../MarginContainer/FooterContentSplit/Panel/Scroll/MarginContainer/TableGrid"
|
||||||
|
|
||||||
|
|
||||||
|
var hidden_columns := {}
|
||||||
|
var columns := []:
|
||||||
|
set(v):
|
||||||
|
columns = v
|
||||||
|
for x in get_children():
|
||||||
|
x.queue_free()
|
||||||
|
|
||||||
|
var new_node : Control
|
||||||
|
for x in v:
|
||||||
|
new_node = table_header_scene.instantiate()
|
||||||
|
add_child(new_node)
|
||||||
|
new_node.manager = self
|
||||||
|
new_node.set_label(x)
|
||||||
|
new_node.get_node("Button").pressed.connect(editor_view._set_sorting.bind(x))
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
hide_columns_button\
|
||||||
|
.get_popup()\
|
||||||
|
.id_pressed\
|
||||||
|
.connect(_on_visible_cols_id_pressed)
|
||||||
|
$"../../../MarginContainer/FooterContentSplit/Panel/Scroll"\
|
||||||
|
.get_h_scroll_bar()\
|
||||||
|
.value_changed\
|
||||||
|
.connect(_on_h_scroll_changed)
|
||||||
|
|
||||||
|
|
||||||
|
func update():
|
||||||
|
_update_hidden_columns()
|
||||||
|
_update_column_sizes()
|
||||||
|
|
||||||
|
|
||||||
|
func hide_column(column_index : int):
|
||||||
|
hidden_columns[editor_view.current_path][editor_view.columns[column_index]] = true
|
||||||
|
editor_view.save_data()
|
||||||
|
update()
|
||||||
|
|
||||||
|
|
||||||
|
func select_column(column_index : int):
|
||||||
|
editor_view.select_column(column_index)
|
||||||
|
|
||||||
|
|
||||||
|
func _update_column_sizes():
|
||||||
|
if grid.get_child_count() == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
await get_tree().process_frame
|
||||||
|
var column_headers := get_children()
|
||||||
|
|
||||||
|
if grid.get_child_count() < column_headers.size(): return
|
||||||
|
if column_headers.size() != columns.size():
|
||||||
|
editor_view.refresh()
|
||||||
|
return
|
||||||
|
|
||||||
|
var clip_text : bool = ProjectSettings.get_setting(TablesPluginSettingsClass.PREFIX + "clip_headers")
|
||||||
|
var min_width := 0
|
||||||
|
var cell : Control
|
||||||
|
|
||||||
|
for i in column_headers.size():
|
||||||
|
var header = column_headers[i]
|
||||||
|
cell = grid.get_child(i)
|
||||||
|
|
||||||
|
header.get_child(0).clip_text = clip_text
|
||||||
|
header.custom_minimum_size.x = 0
|
||||||
|
cell.custom_minimum_size.x = 0
|
||||||
|
header.size.x = 0
|
||||||
|
|
||||||
|
min_width = max(header.size.x, cell.size.x)
|
||||||
|
header.custom_minimum_size.x = min_width
|
||||||
|
cell.custom_minimum_size.x = header.get_minimum_size().x
|
||||||
|
header.size.x = min_width
|
||||||
|
|
||||||
|
grid.hide()
|
||||||
|
grid.show()
|
||||||
|
hide()
|
||||||
|
show()
|
||||||
|
await get_tree().process_frame
|
||||||
|
|
||||||
|
# Abort if the node has been deleted since.
|
||||||
|
if !is_instance_valid(column_headers[0]):
|
||||||
|
return
|
||||||
|
|
||||||
|
get_parent().custom_minimum_size.y = column_headers[0].size.y
|
||||||
|
for i in column_headers.size():
|
||||||
|
column_headers[i].position.x = grid.get_child(i).position.x
|
||||||
|
column_headers[i].size.x = grid.get_child(i).size.x
|
||||||
|
|
||||||
|
|
||||||
|
func _update_hidden_columns():
|
||||||
|
var current_path = editor_view.current_path
|
||||||
|
var rows_shown = editor_view.last_row - editor_view.first_row
|
||||||
|
|
||||||
|
if !hidden_columns.has(current_path):
|
||||||
|
hidden_columns[current_path] = {
|
||||||
|
"resource_local_to_scene" : true,
|
||||||
|
"resource_name" : true,
|
||||||
|
}
|
||||||
|
editor_view.save_data()
|
||||||
|
|
||||||
|
var visible_column_count = 0
|
||||||
|
for i in columns.size():
|
||||||
|
var column_visible = !hidden_columns[current_path].has(columns[i])
|
||||||
|
|
||||||
|
get_child(i).visible = column_visible
|
||||||
|
for j in rows_shown:
|
||||||
|
grid.get_child(j * columns.size() + i).visible = column_visible
|
||||||
|
|
||||||
|
if column_visible:
|
||||||
|
visible_column_count += 1
|
||||||
|
|
||||||
|
grid.columns = visible_column_count
|
||||||
|
|
||||||
|
|
||||||
|
func _on_h_scroll_changed(value):
|
||||||
|
position.x = -value
|
||||||
|
|
||||||
|
|
||||||
|
func _on_visible_cols_about_to_popup():
|
||||||
|
var current_path = editor_view.current_path
|
||||||
|
var popup = hide_columns_button.get_popup()
|
||||||
|
popup.clear()
|
||||||
|
popup.hide_on_checkable_item_selection = false
|
||||||
|
|
||||||
|
for i in columns.size():
|
||||||
|
popup.add_check_item(columns[i].capitalize(), i)
|
||||||
|
popup.set_item_checked(i, not hidden_columns[current_path].has(columns[i]))
|
||||||
|
|
||||||
|
|
||||||
|
func _on_visible_cols_id_pressed(id : int):
|
||||||
|
var current_path = editor_view.current_path
|
||||||
|
var popup = hide_columns_button.get_popup()
|
||||||
|
if popup.is_item_checked(id):
|
||||||
|
popup.set_item_checked(id, false)
|
||||||
|
hidden_columns[current_path][columns[id]] = true
|
||||||
|
|
||||||
|
else:
|
||||||
|
popup.set_item_checked(id, true)
|
||||||
|
hidden_columns[current_path].erase(columns[id])
|
||||||
|
|
||||||
|
editor_view.save_data()
|
||||||
|
update()
|
@ -0,0 +1,173 @@
|
|||||||
|
@tool
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
@export var editor_view_path : NodePath
|
||||||
|
|
||||||
|
@export_enum("Filter", "Process", "Sort") var mode := 0
|
||||||
|
@export var title := ""
|
||||||
|
@export var default_text := "":
|
||||||
|
set(v):
|
||||||
|
default_text = v
|
||||||
|
if _textfield == null:
|
||||||
|
await ready
|
||||||
|
|
||||||
|
_textfield.text = v
|
||||||
|
@export_multiline var default_text_ml := "":
|
||||||
|
set(v):
|
||||||
|
default_text_ml = v
|
||||||
|
if _textfield_ml == null:
|
||||||
|
await ready
|
||||||
|
|
||||||
|
_textfield_ml.text = v
|
||||||
|
@export var function_save_key := ""
|
||||||
|
|
||||||
|
var _textfield : LineEdit
|
||||||
|
var _textfield_ml : TextEdit
|
||||||
|
var _togglable_popup : PopupPanel
|
||||||
|
var _saved_function_index_label : Label
|
||||||
|
|
||||||
|
var _saved_functions : Array = []
|
||||||
|
var _saved_function_selected := -1
|
||||||
|
|
||||||
|
|
||||||
|
func load_saved_functions(func_dict : Dictionary):
|
||||||
|
if !func_dict.has(function_save_key):
|
||||||
|
func_dict[function_save_key] = [default_text_ml]
|
||||||
|
|
||||||
|
_saved_functions = func_dict[function_save_key]
|
||||||
|
_on_saved_function_selected(0)
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
var toggle_button := Button.new()
|
||||||
|
var popup_box := VBoxContainer.new()
|
||||||
|
var popup_buttons_box := HBoxContainer.new()
|
||||||
|
var title_label := Label.new()
|
||||||
|
var submit_button := Button.new()
|
||||||
|
var move_label := Label.new()
|
||||||
|
var move_button_l := Button.new()
|
||||||
|
var move_button_r := Button.new()
|
||||||
|
_textfield = LineEdit.new()
|
||||||
|
_togglable_popup = PopupPanel.new()
|
||||||
|
_textfield_ml = TextEdit.new()
|
||||||
|
_saved_function_index_label = Label.new()
|
||||||
|
|
||||||
|
add_child(_textfield)
|
||||||
|
add_child(toggle_button)
|
||||||
|
_textfield.add_child(_togglable_popup)
|
||||||
|
_togglable_popup.add_child(popup_box)
|
||||||
|
popup_box.add_child(title_label)
|
||||||
|
popup_box.add_child(_textfield_ml)
|
||||||
|
popup_box.add_child(popup_buttons_box)
|
||||||
|
popup_buttons_box.add_child(submit_button)
|
||||||
|
popup_buttons_box.add_child(move_label)
|
||||||
|
popup_buttons_box.add_child(move_button_l)
|
||||||
|
popup_buttons_box.add_child(_saved_function_index_label)
|
||||||
|
popup_buttons_box.add_child(move_button_r)
|
||||||
|
|
||||||
|
title_label.text = title
|
||||||
|
|
||||||
|
toggle_button.icon = get_theme_icon("Collapse", "EditorIcons")
|
||||||
|
toggle_button.pressed.connect(_on_expand_pressed)
|
||||||
|
|
||||||
|
_textfield.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
_textfield.text_submitted.connect(_on_text_submitted.unbind(1))
|
||||||
|
|
||||||
|
_textfield_ml.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
_textfield_ml.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
|
|
||||||
|
submit_button.text = "Run multiline!"
|
||||||
|
submit_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
submit_button.pressed.connect(_on_text_submitted)
|
||||||
|
|
||||||
|
move_label.text = "Choose saved:"
|
||||||
|
move_button_l.icon = get_theme_icon("PagePrevious", "EditorIcons")
|
||||||
|
move_button_l.pressed.connect(_on_saved_function_bumped.bind(-1))
|
||||||
|
_on_saved_function_selected(0)
|
||||||
|
move_button_r.icon = get_theme_icon("PageNext", "EditorIcons")
|
||||||
|
move_button_r.pressed.connect(_on_saved_function_bumped.bind(+1))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func _on_expand_pressed():
|
||||||
|
_togglable_popup.popup(Rect2i(_textfield.get_screen_position(), Vector2(size.x, 256.0)))
|
||||||
|
|
||||||
|
|
||||||
|
func _on_text_submitted():
|
||||||
|
[_table_filter, _table_process][mode].call()
|
||||||
|
_saved_functions[_saved_function_selected] = _textfield_ml.text
|
||||||
|
get_node(editor_view_path).save_data.call_deferred()
|
||||||
|
|
||||||
|
|
||||||
|
func _get_script_source_code(first_line : String):
|
||||||
|
var new_text := ""
|
||||||
|
if !_togglable_popup.visible:
|
||||||
|
new_text = _textfield.text
|
||||||
|
if new_text == "":
|
||||||
|
new_text = default_text
|
||||||
|
|
||||||
|
return first_line + "\treturn " + new_text
|
||||||
|
|
||||||
|
else:
|
||||||
|
new_text = _textfield_ml.text
|
||||||
|
if new_text == "":
|
||||||
|
new_text = default_text_ml
|
||||||
|
|
||||||
|
var text_split := new_text.split("\n")
|
||||||
|
for i in text_split.size():
|
||||||
|
text_split[i] = "\t" + text_split[i]
|
||||||
|
|
||||||
|
return first_line + "\n".join(text_split)
|
||||||
|
|
||||||
|
|
||||||
|
func _table_filter():
|
||||||
|
var new_script := GDScript.new()
|
||||||
|
new_script.source_code = _get_script_source_code("static func can_show(res, index):\n")
|
||||||
|
new_script.reload()
|
||||||
|
|
||||||
|
var editor_view := get_node(editor_view_path)
|
||||||
|
editor_view.search_cond = new_script
|
||||||
|
editor_view.refresh()
|
||||||
|
|
||||||
|
|
||||||
|
func _table_process():
|
||||||
|
var new_script := GDScript.new()
|
||||||
|
new_script.source_code = _get_script_source_code("static func get_result(value, res, all_res, row_index):\n")
|
||||||
|
new_script.reload()
|
||||||
|
|
||||||
|
var editor_view := get_node(editor_view_path)
|
||||||
|
var new_script_instance = new_script.new()
|
||||||
|
var values = editor_view.get_edited_cells_values()
|
||||||
|
|
||||||
|
var edited_rows : Array[int] = editor_view._selection.get_edited_rows()
|
||||||
|
var edited_resources := edited_rows.map(func(x): return editor_view.rows[x])
|
||||||
|
for i in values.size():
|
||||||
|
values[i] = new_script_instance.get_result(values[i], editor_view.rows[edited_rows[i]], edited_resources, i)
|
||||||
|
|
||||||
|
editor_view.set_edited_cells_values(values)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_saved_function_selected(new_index : int):
|
||||||
|
if new_index < 0:
|
||||||
|
new_index = 0
|
||||||
|
|
||||||
|
if _saved_function_selected == _saved_functions.size() - 1 and _textfield_ml.text == default_text_ml:
|
||||||
|
_saved_functions.resize(_saved_functions.size() - 1)
|
||||||
|
|
||||||
|
elif _saved_function_selected >= 0:
|
||||||
|
_saved_functions[_saved_function_selected] = _textfield_ml.text
|
||||||
|
|
||||||
|
_saved_function_selected = new_index
|
||||||
|
if new_index >= _saved_functions.size():
|
||||||
|
_saved_functions.resize(new_index + 1)
|
||||||
|
for i in _saved_functions.size():
|
||||||
|
if _saved_functions[i] == null:
|
||||||
|
_saved_functions[i] = default_text_ml
|
||||||
|
|
||||||
|
_textfield_ml.text = _saved_functions[new_index]
|
||||||
|
_saved_function_index_label.text = "%d/%d" % [new_index + 1, _saved_functions.size()]
|
||||||
|
get_node(editor_view_path).save_data.call_deferred()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_saved_function_bumped(increment : int):
|
||||||
|
_on_saved_function_selected(_saved_function_selected + increment)
|
163
addons/resources_spreadsheet_view/main_screen/input_handler.gd
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
@tool
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
const TablesPluginEditorViewClass = preload("res://addons/resources_spreadsheet_view/editor_view.gd")
|
||||||
|
const TablesPluginSelectionManagerClass = preload("res://addons/resources_spreadsheet_view/main_screen/selection_manager.gd")
|
||||||
|
const TextEditingUtilsClass := preload("res://addons/resources_spreadsheet_view/text_editing_utils.gd")
|
||||||
|
|
||||||
|
@onready var editor_view : TablesPluginEditorViewClass = get_parent()
|
||||||
|
@onready var selection : TablesPluginSelectionManagerClass = get_node("../SelectionManager")
|
||||||
|
|
||||||
|
|
||||||
|
func _on_cell_gui_input(event : InputEvent, cell : Control):
|
||||||
|
if event is InputEventMouseButton:
|
||||||
|
editor_view.grab_focus()
|
||||||
|
if event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
|
||||||
|
if !cell in selection.edited_cells:
|
||||||
|
selection.deselect_all_cells()
|
||||||
|
selection.select_cell(cell)
|
||||||
|
|
||||||
|
selection.rightclick_cells()
|
||||||
|
|
||||||
|
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
|
||||||
|
if Input.is_key_pressed(KEY_CTRL):
|
||||||
|
if cell in selection.edited_cells:
|
||||||
|
selection.deselect_cell(cell)
|
||||||
|
|
||||||
|
else:
|
||||||
|
selection.select_cell(cell)
|
||||||
|
|
||||||
|
elif Input.is_key_pressed(KEY_SHIFT):
|
||||||
|
selection.select_cells_to(cell)
|
||||||
|
|
||||||
|
else:
|
||||||
|
selection.deselect_all_cells()
|
||||||
|
selection.select_cell(cell)
|
||||||
|
|
||||||
|
|
||||||
|
func _gui_input(event : InputEvent):
|
||||||
|
if event is InputEventMouseButton:
|
||||||
|
if event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed():
|
||||||
|
selection.rightclick_cells()
|
||||||
|
|
||||||
|
if event.button_index == MOUSE_BUTTON_LEFT:
|
||||||
|
editor_view.grab_focus()
|
||||||
|
if !event.pressed:
|
||||||
|
selection.deselect_all_cells()
|
||||||
|
|
||||||
|
|
||||||
|
func _input(event : InputEvent):
|
||||||
|
if !event is InputEventKey or !event.pressed:
|
||||||
|
return
|
||||||
|
|
||||||
|
if !editor_view.has_focus() or selection.edited_cells.size() == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
if event.keycode == KEY_CTRL or event.keycode == KEY_SHIFT:
|
||||||
|
# Modifier keys do not get processed.
|
||||||
|
return
|
||||||
|
|
||||||
|
# Ctrl + Z (before, and instead of, committing the action!)
|
||||||
|
if Input.is_key_pressed(KEY_CTRL):
|
||||||
|
if event.keycode == KEY_Z or event.keycode == KEY_Y:
|
||||||
|
return
|
||||||
|
|
||||||
|
_key_specific_action(event)
|
||||||
|
editor_view.grab_focus()
|
||||||
|
editor_view.editor_interface.get_resource_filesystem().scan()
|
||||||
|
|
||||||
|
|
||||||
|
func _key_specific_action(event : InputEvent):
|
||||||
|
var column = selection.get_cell_column(selection.edited_cells[0])
|
||||||
|
var ctrl_pressed := Input.is_key_pressed(KEY_CTRL)
|
||||||
|
|
||||||
|
# BETWEEN-CELL NAVIGATION
|
||||||
|
if event.keycode == KEY_UP:
|
||||||
|
_move_selection_on_grid(0, (-1 if !ctrl_pressed else -10))
|
||||||
|
|
||||||
|
elif event.keycode == KEY_DOWN:
|
||||||
|
_move_selection_on_grid(0, (1 if !ctrl_pressed else 10))
|
||||||
|
|
||||||
|
elif Input.is_key_pressed(KEY_SHIFT) and event.keycode == KEY_TAB:
|
||||||
|
_move_selection_on_grid((-1 if !ctrl_pressed else -10), 0)
|
||||||
|
|
||||||
|
elif event.keycode == KEY_TAB:
|
||||||
|
_move_selection_on_grid((1 if !ctrl_pressed else 10), 0)
|
||||||
|
|
||||||
|
# CURSOR MOVEMENT
|
||||||
|
if event.keycode == KEY_LEFT:
|
||||||
|
TextEditingUtilsClass.multi_move_left(
|
||||||
|
selection.edited_cells_text, selection.edit_cursor_positions, ctrl_pressed
|
||||||
|
)
|
||||||
|
|
||||||
|
elif event.keycode == KEY_RIGHT:
|
||||||
|
TextEditingUtilsClass.multi_move_right(
|
||||||
|
selection.edited_cells_text, selection.edit_cursor_positions, ctrl_pressed
|
||||||
|
)
|
||||||
|
|
||||||
|
elif event.keycode == KEY_HOME:
|
||||||
|
for i in selection.edit_cursor_positions.size():
|
||||||
|
selection.edit_cursor_positions[i] = 0
|
||||||
|
|
||||||
|
elif event.keycode == KEY_END:
|
||||||
|
for i in selection.edit_cursor_positions.size():
|
||||||
|
selection.edit_cursor_positions[i] = selection.edited_cells_text[i].length()
|
||||||
|
|
||||||
|
# Ctrl + C (so you can edit in a proper text editor instead of this wacky nonsense)
|
||||||
|
elif ctrl_pressed and event.keycode == KEY_C:
|
||||||
|
TextEditingUtilsClass.multi_copy(selection.edited_cells_text)
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
# The following actions do not work on non-editable cells.
|
||||||
|
if !selection.column_editors[column].is_text() or editor_view.columns[column] == "resource_path":
|
||||||
|
return
|
||||||
|
|
||||||
|
# Ctrl + V
|
||||||
|
elif ctrl_pressed and event.keycode == KEY_V:
|
||||||
|
editor_view.set_edited_cells_values(TextEditingUtilsClass.multi_paste(
|
||||||
|
selection.edited_cells_text, selection.edit_cursor_positions
|
||||||
|
))
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
# ERASING
|
||||||
|
elif event.keycode == KEY_BACKSPACE:
|
||||||
|
editor_view.set_edited_cells_values(TextEditingUtilsClass.multi_erase_left(
|
||||||
|
selection.edited_cells_text, selection.edit_cursor_positions, ctrl_pressed
|
||||||
|
))
|
||||||
|
|
||||||
|
elif event.keycode == KEY_DELETE:
|
||||||
|
editor_view.set_edited_cells_values(TextEditingUtilsClass.multi_erase_right(
|
||||||
|
selection.edited_cells_text, selection.edit_cursor_positions, ctrl_pressed
|
||||||
|
))
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
# And finally, text typing.
|
||||||
|
elif event.keycode == KEY_ENTER:
|
||||||
|
editor_view.set_edited_cells_values(TextEditingUtilsClass.multi_input(
|
||||||
|
"\n", selection.edited_cells_text, selection.edit_cursor_positions
|
||||||
|
))
|
||||||
|
|
||||||
|
elif event.unicode != 0 and event.unicode != 127:
|
||||||
|
editor_view.set_edited_cells_values(TextEditingUtilsClass.multi_input(
|
||||||
|
char(event.unicode), selection.edited_cells_text, selection.edit_cursor_positions
|
||||||
|
))
|
||||||
|
|
||||||
|
selection.queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
|
func _move_selection_on_grid(move_h : int, move_v : int):
|
||||||
|
var selected_cells := selection.edited_cells.duplicate()
|
||||||
|
var child_count := editor_view.node_table_root.get_child_count()
|
||||||
|
var new_child_index := 0
|
||||||
|
for i in selected_cells.size():
|
||||||
|
new_child_index = (
|
||||||
|
selected_cells[i].get_index()
|
||||||
|
+ move_h
|
||||||
|
+ move_v * editor_view.columns.size()
|
||||||
|
)
|
||||||
|
if child_count < new_child_index: continue
|
||||||
|
selected_cells[i] = editor_view.node_table_root.get_child(new_child_index)
|
||||||
|
|
||||||
|
editor_view.grab_focus()
|
||||||
|
selection.deselect_all_cells()
|
||||||
|
selection.select_cells(selected_cells)
|
@ -0,0 +1,54 @@
|
|||||||
|
@tool
|
||||||
|
extends OptionButton
|
||||||
|
|
||||||
|
@export @onready var editor_view := $"../../../../.."
|
||||||
|
|
||||||
|
var recent_paths := []
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
item_selected.connect(_on_item_selected)
|
||||||
|
|
||||||
|
|
||||||
|
func load_paths(paths):
|
||||||
|
clear()
|
||||||
|
for x in paths:
|
||||||
|
add_path_to_recent(x, true)
|
||||||
|
|
||||||
|
selected = 0
|
||||||
|
|
||||||
|
|
||||||
|
func add_path_to_recent(path : String, is_loading : bool = false):
|
||||||
|
if path in recent_paths: return
|
||||||
|
|
||||||
|
var idx_in_array := recent_paths.find(path)
|
||||||
|
if idx_in_array != -1:
|
||||||
|
remove_item(idx_in_array)
|
||||||
|
recent_paths.remove_at(idx_in_array)
|
||||||
|
|
||||||
|
recent_paths.append(path)
|
||||||
|
add_item(path)
|
||||||
|
select(get_item_count() - 1)
|
||||||
|
|
||||||
|
if !is_loading:
|
||||||
|
editor_view.save_data()
|
||||||
|
|
||||||
|
|
||||||
|
func remove_selected_path_from_recent():
|
||||||
|
if get_item_count() == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
var idx_in_array = selected
|
||||||
|
recent_paths.remove_at(idx_in_array)
|
||||||
|
remove_item(idx_in_array)
|
||||||
|
|
||||||
|
if get_item_count() != 0:
|
||||||
|
select(0)
|
||||||
|
editor_view.display_folder(recent_paths[0])
|
||||||
|
editor_view.save_data()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_item_selected(index : int):
|
||||||
|
editor_view.current_path = recent_paths[index]
|
||||||
|
editor_view.node_folder_path.text = recent_paths[index]
|
||||||
|
editor_view.refresh()
|
@ -0,0 +1,182 @@
|
|||||||
|
@tool
|
||||||
|
extends MarginContainer
|
||||||
|
|
||||||
|
enum {
|
||||||
|
EDITBOX_DUPLICATE = 1,
|
||||||
|
EDITBOX_RENAME,
|
||||||
|
EDITBOX_DELETE,
|
||||||
|
}
|
||||||
|
|
||||||
|
const TextEditingUtilsClass := preload("res://addons/resources_spreadsheet_view/text_editing_utils.gd")
|
||||||
|
const TablesPluginSettingsClass := preload("res://addons/resources_spreadsheet_view/settings_grid.gd")
|
||||||
|
|
||||||
|
@export @onready var editor_view := $"../.."
|
||||||
|
@export @onready var selection := $"../../SelectionManager"
|
||||||
|
|
||||||
|
@onready var editbox_node := $"Control/ColorRect/Popup"
|
||||||
|
@onready var editbox_label : Label = editbox_node.get_node("Panel/VBoxContainer/Label")
|
||||||
|
@onready var editbox_input : LineEdit = editbox_node.get_node("Panel/VBoxContainer/LineEdit")
|
||||||
|
|
||||||
|
var cell : Control
|
||||||
|
var editbox_action : int
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
editbox_input.get_node("../..").add_theme_stylebox_override(
|
||||||
|
"panel",
|
||||||
|
get_theme_stylebox(&"Content", &"EditorStyles")
|
||||||
|
)
|
||||||
|
editbox_input.text_submitted.connect(func(_new_text): _on_editbox_accepted())
|
||||||
|
close()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_grid_cells_rightclicked(cells):
|
||||||
|
open(cells)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_grid_cells_selected(cells):
|
||||||
|
open(cells, true, true)
|
||||||
|
|
||||||
|
|
||||||
|
func open(cells : Array, pin_to_cell : bool = false, from_leftclick : bool = false):
|
||||||
|
if cells.size() == 0 or (from_leftclick and !ProjectSettings.get_setting(TablesPluginSettingsClass.PREFIX + "context_menu_on_leftclick")):
|
||||||
|
hide()
|
||||||
|
cell = null
|
||||||
|
return
|
||||||
|
|
||||||
|
if pin_to_cell:
|
||||||
|
cell = cells[-1]
|
||||||
|
set_deferred(&"global_position", Vector2(
|
||||||
|
cell.global_position.x + cell.size.x,
|
||||||
|
cell.global_position.y
|
||||||
|
))
|
||||||
|
|
||||||
|
else:
|
||||||
|
cell = null
|
||||||
|
set_deferred(&"global_position", get_global_mouse_position() + Vector2.ONE)
|
||||||
|
|
||||||
|
show()
|
||||||
|
size = Vector2.ZERO
|
||||||
|
top_level = true
|
||||||
|
$"Control2/Label".text = str(cells.size()) + (" Cells" if cells.size() % 10 != 1 else " Cell")
|
||||||
|
$"GridContainer/Rename".visible = editor_view.has_row_names()
|
||||||
|
|
||||||
|
|
||||||
|
func close():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func _input(event : InputEvent):
|
||||||
|
if !editor_view.is_visible_in_tree():
|
||||||
|
close()
|
||||||
|
return
|
||||||
|
|
||||||
|
if event is InputEventMouseButton and event.is_pressed():
|
||||||
|
close()
|
||||||
|
return
|
||||||
|
|
||||||
|
if event is InputEventKey:
|
||||||
|
if event.is_pressed() and event.is_command_or_control_pressed():
|
||||||
|
global_position = get_global_mouse_position() + Vector2.ONE
|
||||||
|
if cell != null:
|
||||||
|
global_position = Vector2(
|
||||||
|
cell.global_position.x + cell.size.x,
|
||||||
|
cell.global_position.y
|
||||||
|
)
|
||||||
|
|
||||||
|
# Dupe
|
||||||
|
if event.keycode == KEY_D:
|
||||||
|
_on_Duplicate_pressed()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Rename
|
||||||
|
if event.keycode == KEY_R:
|
||||||
|
_on_Rename_pressed()
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Duplicate_pressed():
|
||||||
|
_show_editbox(EDITBOX_DUPLICATE)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_CbCopy_pressed():
|
||||||
|
TextEditingUtilsClass.multi_copy(selection.edited_cells_text)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_CbPaste_pressed():
|
||||||
|
editor_view.set_edited_cells_values(
|
||||||
|
TextEditingUtilsClass.multi_paste(
|
||||||
|
selection.edited_cells_text,
|
||||||
|
selection.edit_cursor_positions
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Rename_pressed():
|
||||||
|
_show_editbox(EDITBOX_RENAME)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Delete_pressed():
|
||||||
|
_show_editbox(EDITBOX_DELETE)
|
||||||
|
|
||||||
|
|
||||||
|
func _show_editbox(action):
|
||||||
|
editbox_action = action
|
||||||
|
match action:
|
||||||
|
EDITBOX_DUPLICATE:
|
||||||
|
if !editor_view.has_row_names():
|
||||||
|
_on_editbox_accepted()
|
||||||
|
return
|
||||||
|
|
||||||
|
if selection.edited_cells.size() == 1:
|
||||||
|
editbox_label.text = "Input new row's name..."
|
||||||
|
editbox_input.text = editor_view.get_last_selected_row()\
|
||||||
|
.resource_path.get_file().get_basename()
|
||||||
|
|
||||||
|
else:
|
||||||
|
editbox_label.text = "Input suffix to append to names..."
|
||||||
|
editbox_input.text = ""
|
||||||
|
|
||||||
|
EDITBOX_RENAME:
|
||||||
|
editbox_label.text = "Input new name for row..."
|
||||||
|
editbox_input.text = editor_view.get_last_selected_row()\
|
||||||
|
.resource_path.get_file().get_basename()
|
||||||
|
|
||||||
|
EDITBOX_DELETE:
|
||||||
|
editbox_label.text = "Really delete selected rows? (Irreversible!!!)"
|
||||||
|
editbox_input.text = editor_view.get_last_selected_row()\
|
||||||
|
.resource_path.get_file().get_basename()
|
||||||
|
|
||||||
|
show()
|
||||||
|
editbox_input.grab_focus()
|
||||||
|
editbox_input.caret_column = 999999999
|
||||||
|
editbox_node.size = Vector2.ZERO
|
||||||
|
editbox_node.show()
|
||||||
|
$"Control/ColorRect".show()
|
||||||
|
$"Control/ColorRect".top_level = true
|
||||||
|
$"Control/ColorRect".size = get_viewport_rect().size * 4.0
|
||||||
|
editbox_node.global_position = (
|
||||||
|
global_position
|
||||||
|
+ size * 0.5
|
||||||
|
- editbox_node.get_child(0).size * 0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_editbox_closed():
|
||||||
|
editbox_node.hide()
|
||||||
|
$"Control/ColorRect".hide()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_editbox_accepted():
|
||||||
|
match(editbox_action):
|
||||||
|
EDITBOX_DUPLICATE:
|
||||||
|
editor_view.duplicate_selected_rows(editbox_input.text)
|
||||||
|
|
||||||
|
EDITBOX_RENAME:
|
||||||
|
editor_view.rename_row(editor_view.get_last_selected_row(), editbox_input.text)
|
||||||
|
|
||||||
|
EDITBOX_DELETE:
|
||||||
|
editor_view.delete_selected_rows()
|
||||||
|
|
||||||
|
_on_editbox_closed()
|
@ -0,0 +1,182 @@
|
|||||||
|
[gd_scene load_steps=7 format=3 uid="uid://b51hnttsie7k5"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="1"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/main_screen/selection_actions.gd" id="1_qv6ov"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_color_setter.gd" id="2_a4ihj"]
|
||||||
|
|
||||||
|
[sub_resource type="Image" id="Image_1546g"]
|
||||||
|
data = {
|
||||||
|
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||||
|
"format": "LumAlpha8",
|
||||||
|
"height": 16,
|
||||||
|
"mipmaps": false,
|
||||||
|
"width": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id="2"]
|
||||||
|
image = SubResource("Image_1546g")
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ydxuk"]
|
||||||
|
content_margin_left = 4.0
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_right = 4.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
bg_color = Color(1, 0.365, 0.365, 1)
|
||||||
|
draw_center = false
|
||||||
|
border_width_left = 2
|
||||||
|
border_width_top = 2
|
||||||
|
border_width_right = 2
|
||||||
|
border_width_bottom = 2
|
||||||
|
corner_detail = 1
|
||||||
|
|
||||||
|
[node name="SelectionActions" type="MarginContainer"]
|
||||||
|
offset_right = 80.0
|
||||||
|
offset_bottom = 52.0
|
||||||
|
size_flags_horizontal = 9
|
||||||
|
mouse_filter = 2
|
||||||
|
script = ExtResource("1_qv6ov")
|
||||||
|
|
||||||
|
[node name="Control2" type="Panel" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="ColorRect" type="ColorRect" parent="Control2"]
|
||||||
|
show_behind_parent = true
|
||||||
|
layout_mode = 0
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="ColorRect2" type="ColorRect" parent="Control2"]
|
||||||
|
modulate = Color(0, 0, 0, 1)
|
||||||
|
show_behind_parent = true
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 9
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = -2.0
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
script = ExtResource("2_a4ihj")
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Control2"]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_top = -26.0
|
||||||
|
offset_right = 57.0
|
||||||
|
text = "Actions"
|
||||||
|
vertical_alignment = 2
|
||||||
|
|
||||||
|
[node name="ColorRect3" type="Panel" parent="Control2/Label"]
|
||||||
|
show_behind_parent = true
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = -2.0
|
||||||
|
offset_top = 2.0
|
||||||
|
offset_right = 2.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="GridContainer" type="GridContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 9
|
||||||
|
size_flags_vertical = 9
|
||||||
|
mouse_filter = 2
|
||||||
|
columns = 3
|
||||||
|
|
||||||
|
[node name="Duplicate" type="Button" parent="GridContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Duplicate Selected Rows (Ctrl+D)"
|
||||||
|
mouse_filter = 1
|
||||||
|
icon = SubResource("2")
|
||||||
|
script = ExtResource("1")
|
||||||
|
icon_name = "Duplicate"
|
||||||
|
|
||||||
|
[node name="CbCopy" type="Button" parent="GridContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Copy to Clipboard (Ctrl+C)"
|
||||||
|
mouse_filter = 1
|
||||||
|
icon = SubResource("2")
|
||||||
|
script = ExtResource("1")
|
||||||
|
icon_name = "ActionCopy"
|
||||||
|
|
||||||
|
[node name="CbPaste" type="Button" parent="GridContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Paste Clipboard (Ctrl+V)"
|
||||||
|
mouse_filter = 1
|
||||||
|
icon = SubResource("2")
|
||||||
|
script = ExtResource("1")
|
||||||
|
icon_name = "ActionPaste"
|
||||||
|
|
||||||
|
[node name="Rename" type="Button" parent="GridContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Rename Selected Rows (Ctrl+R)"
|
||||||
|
mouse_filter = 1
|
||||||
|
icon = SubResource("2")
|
||||||
|
script = ExtResource("1")
|
||||||
|
icon_name = "Edit"
|
||||||
|
|
||||||
|
[node name="Delete" type="Button" parent="GridContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "DELETE Selected Rows"
|
||||||
|
mouse_filter = 1
|
||||||
|
icon = SubResource("2")
|
||||||
|
script = ExtResource("1")
|
||||||
|
icon_name = "Remove"
|
||||||
|
|
||||||
|
[node name="Control" type="Control" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="ColorRect" type="ColorRect" parent="Control"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_right = -80.0
|
||||||
|
offset_bottom = -52.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
color = Color(0, 0, 0, 0.498039)
|
||||||
|
|
||||||
|
[node name="Popup" type="MarginContainer" parent="Control/ColorRect"]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_top = 100.0
|
||||||
|
offset_right = 140.0
|
||||||
|
offset_bottom = 196.0
|
||||||
|
|
||||||
|
[node name="Panel" type="PanelContainer" parent="Control/ColorRect/Popup"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_ydxuk")
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="Control/ColorRect/Popup/Panel"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Control/ColorRect/Popup/Panel/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Input new name..."
|
||||||
|
|
||||||
|
[node name="LineEdit" type="LineEdit" parent="Control/ColorRect/Popup/Panel/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="Control/ColorRect/Popup/Panel/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="Button" type="Button" parent="Control/ColorRect/Popup/Panel/VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "OK"
|
||||||
|
|
||||||
|
[node name="Button2" type="Button" parent="Control/ColorRect/Popup/Panel/VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Cancel"
|
||||||
|
|
||||||
|
[connection signal="pressed" from="GridContainer/Duplicate" to="." method="_on_Duplicate_pressed"]
|
||||||
|
[connection signal="pressed" from="GridContainer/CbCopy" to="." method="_on_CbCopy_pressed"]
|
||||||
|
[connection signal="pressed" from="GridContainer/CbPaste" to="." method="_on_CbPaste_pressed"]
|
||||||
|
[connection signal="pressed" from="GridContainer/Rename" to="." method="_on_Rename_pressed"]
|
||||||
|
[connection signal="pressed" from="GridContainer/Delete" to="." method="_on_Delete_pressed"]
|
||||||
|
[connection signal="pressed" from="Control/ColorRect/Popup/Panel/VBoxContainer/HBoxContainer/Button" to="." method="_on_editbox_accepted"]
|
||||||
|
[connection signal="pressed" from="Control/ColorRect/Popup/Panel/VBoxContainer/HBoxContainer/Button2" to="." method="_on_editbox_closed"]
|
@ -0,0 +1,251 @@
|
|||||||
|
@tool
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
signal cells_selected(cells)
|
||||||
|
signal cells_rightclicked(cells)
|
||||||
|
|
||||||
|
const EditorViewClass = preload("res://addons/resources_spreadsheet_view/editor_view.gd")
|
||||||
|
|
||||||
|
@export var cell_editor_classes : Array[Script] = []
|
||||||
|
|
||||||
|
@export @onready var node_property_editors : VBoxContainer = $"../HeaderContentSplit/MarginContainer/FooterContentSplit/Footer/PropertyEditors"
|
||||||
|
@export @onready var scrollbar : ScrollContainer = $"../HeaderContentSplit/MarginContainer/FooterContentSplit/Panel/Scroll"
|
||||||
|
|
||||||
|
@onready var editor_view : EditorViewClass = get_parent()
|
||||||
|
|
||||||
|
var edited_cells : Array = []
|
||||||
|
var edited_cells_text : Array = []
|
||||||
|
var edit_cursor_positions : Array[int] = []
|
||||||
|
|
||||||
|
var all_cell_editors : Array = []
|
||||||
|
var column_editors : Array[Object] = []
|
||||||
|
var inspector_resource : Resource
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
# Load cell editors and instantiate them
|
||||||
|
for x in cell_editor_classes:
|
||||||
|
all_cell_editors.append(x.new())
|
||||||
|
all_cell_editors[all_cell_editors.size() - 1].hint_strings_array = editor_view.column_hint_strings
|
||||||
|
|
||||||
|
get_parent()\
|
||||||
|
.editor_interface\
|
||||||
|
.get_inspector()\
|
||||||
|
.property_edited\
|
||||||
|
.connect(_on_inspector_property_edited)
|
||||||
|
|
||||||
|
scrollbar.get_h_scroll_bar().value_changed.connect(queue_redraw.unbind(1), CONNECT_DEFERRED)
|
||||||
|
scrollbar.get_v_scroll_bar().value_changed.connect(queue_redraw.unbind(1), CONNECT_DEFERRED)
|
||||||
|
|
||||||
|
|
||||||
|
func _draw():
|
||||||
|
var font := get_theme_font("font", "Label")
|
||||||
|
var font_size := get_theme_font_size("font", "Label")
|
||||||
|
var label_padding_left := 2.0
|
||||||
|
var newline_char := 10
|
||||||
|
if edit_cursor_positions.size() != edited_cells.size():
|
||||||
|
return
|
||||||
|
|
||||||
|
for i in edited_cells.size():
|
||||||
|
if edit_cursor_positions[i] >= edited_cells_text[i].length():
|
||||||
|
continue
|
||||||
|
|
||||||
|
var char_size := Vector2(0, font.get_ascent(font_size))
|
||||||
|
var cursor_pos := Vector2(label_padding_left, 0)
|
||||||
|
var cell_text : String = edited_cells_text[i]
|
||||||
|
var cell : Control = edited_cells[i]
|
||||||
|
if cell is Label and cell.horizontal_alignment == HORIZONTAL_ALIGNMENT_RIGHT:
|
||||||
|
cursor_pos.x += cell.size.x - font.get_multiline_string_size(edited_cells[i].text, HORIZONTAL_ALIGNMENT_RIGHT, -1, font_size).x
|
||||||
|
|
||||||
|
for j in max(edit_cursor_positions[i], 0) + 1:
|
||||||
|
if j == 0: continue
|
||||||
|
if cell_text.unicode_at(j - 1) == newline_char:
|
||||||
|
cursor_pos.x = label_padding_left
|
||||||
|
cursor_pos.y += font.get_ascent(font_size)
|
||||||
|
continue
|
||||||
|
|
||||||
|
char_size = font.get_char_size(cell_text.unicode_at(j - 1), font_size)
|
||||||
|
cursor_pos.x += char_size.x
|
||||||
|
|
||||||
|
draw_rect(Rect2(cursor_pos + cell.global_position - global_position, Vector2(2, char_size.y)), Color(1, 1, 1, 0.5))
|
||||||
|
|
||||||
|
|
||||||
|
func initialize_editors(column_values, column_types, column_hints):
|
||||||
|
deselect_all_cells()
|
||||||
|
edited_cells.clear()
|
||||||
|
edited_cells_text.clear()
|
||||||
|
edit_cursor_positions.clear()
|
||||||
|
|
||||||
|
column_editors.clear()
|
||||||
|
for i in column_values.size():
|
||||||
|
for x in all_cell_editors:
|
||||||
|
if x.can_edit_value(column_values[i], column_types[i], column_hints[i], i):
|
||||||
|
column_editors.append(x)
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
func deselect_all_cells():
|
||||||
|
for x in edited_cells:
|
||||||
|
column_editors[get_cell_column(x)].set_selected(x, false)
|
||||||
|
|
||||||
|
edited_cells.clear()
|
||||||
|
edited_cells_text.clear()
|
||||||
|
edit_cursor_positions.clear()
|
||||||
|
_selection_changed()
|
||||||
|
|
||||||
|
|
||||||
|
func deselect_cell(cell : Control):
|
||||||
|
var idx := edited_cells.find(cell)
|
||||||
|
if idx == -1: return
|
||||||
|
|
||||||
|
column_editors[get_cell_column(cell)].set_selected(cell, false)
|
||||||
|
edited_cells.remove_at(idx)
|
||||||
|
if edited_cells_text.size() != 0:
|
||||||
|
edited_cells_text.remove_at(idx)
|
||||||
|
edit_cursor_positions.remove_at(idx)
|
||||||
|
|
||||||
|
_selection_changed()
|
||||||
|
|
||||||
|
|
||||||
|
func select_cell(cell : Control):
|
||||||
|
var column_index := get_cell_column(cell)
|
||||||
|
if can_select_cell(cell):
|
||||||
|
_add_cell_to_selection(cell)
|
||||||
|
_try_open_docks(cell)
|
||||||
|
inspector_resource = editor_view.rows[get_cell_row(cell)]
|
||||||
|
# inspector_resource = editor_view.rows[get_cell_row(cell)].duplicate()
|
||||||
|
# inspector_resource.resource_path = ""
|
||||||
|
editor_view.editor_plugin.get_editor_interface().edit_resource(inspector_resource)
|
||||||
|
|
||||||
|
_selection_changed()
|
||||||
|
|
||||||
|
|
||||||
|
func select_cells(cells : Array):
|
||||||
|
var last_selectible : Control = null
|
||||||
|
for x in cells:
|
||||||
|
if x.mouse_filter != MOUSE_FILTER_IGNORE and can_select_cell(x):
|
||||||
|
_add_cell_to_selection(x)
|
||||||
|
last_selectible = x
|
||||||
|
|
||||||
|
if last_selectible != null:
|
||||||
|
select_cell(last_selectible)
|
||||||
|
|
||||||
|
|
||||||
|
func select_cells_to(cell : Control):
|
||||||
|
var column_index := get_cell_column(cell)
|
||||||
|
if edited_cells.size() == 0 or column_index != get_cell_column(edited_cells[edited_cells.size() - 1]):
|
||||||
|
return
|
||||||
|
|
||||||
|
var row_start := get_cell_row(edited_cells[edited_cells.size() - 1]) - editor_view.first_row
|
||||||
|
var row_end := get_cell_row(cell) - editor_view.first_row
|
||||||
|
var edge_shift := -1 if row_start > row_end else 1
|
||||||
|
row_start += edge_shift
|
||||||
|
row_end += edge_shift
|
||||||
|
|
||||||
|
for i in range(row_start, row_end, edge_shift):
|
||||||
|
var cur_cell : Control = editor_view.node_table_root.get_child(i * editor_view.columns.size() + column_index)
|
||||||
|
if !cur_cell.visible or cur_cell.mouse_filter == MOUSE_FILTER_IGNORE:
|
||||||
|
# When search is active, some cells will be hidden.
|
||||||
|
# When showing several classes, empty cells will be non-selectable.
|
||||||
|
continue
|
||||||
|
|
||||||
|
column_editors[column_index].set_selected(cur_cell, true)
|
||||||
|
if !cur_cell in edited_cells:
|
||||||
|
edited_cells.append(cur_cell)
|
||||||
|
if column_editors[column_index].is_text():
|
||||||
|
edited_cells_text.append(str(cur_cell.text))
|
||||||
|
edit_cursor_positions.append(cur_cell.text.length())
|
||||||
|
|
||||||
|
_selection_changed()
|
||||||
|
|
||||||
|
|
||||||
|
func rightclick_cells():
|
||||||
|
cells_rightclicked.emit(edited_cells)
|
||||||
|
|
||||||
|
|
||||||
|
func can_select_cell(cell : Control) -> bool:
|
||||||
|
if edited_cells.size() == 0:
|
||||||
|
return true
|
||||||
|
|
||||||
|
if (
|
||||||
|
get_cell_column(cell)
|
||||||
|
!= get_cell_column(edited_cells[0])
|
||||||
|
):
|
||||||
|
return false
|
||||||
|
|
||||||
|
return !cell in edited_cells
|
||||||
|
|
||||||
|
|
||||||
|
func get_cell_column(cell : Control) -> int:
|
||||||
|
return cell.get_index() % editor_view.columns.size()
|
||||||
|
|
||||||
|
|
||||||
|
func get_cell_row(cell : Control) -> int:
|
||||||
|
return cell.get_index() / editor_view.columns.size() + editor_view.first_row
|
||||||
|
|
||||||
|
|
||||||
|
func get_edited_rows() -> Array[int]:
|
||||||
|
var rows : Array[int] = []
|
||||||
|
rows.resize(edited_cells.size())
|
||||||
|
for i in rows.size():
|
||||||
|
rows[i] = get_cell_row(edited_cells[i])
|
||||||
|
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
func _selection_changed():
|
||||||
|
queue_redraw()
|
||||||
|
cells_selected.emit(edited_cells)
|
||||||
|
|
||||||
|
|
||||||
|
func _add_cell_to_selection(cell : Control):
|
||||||
|
var column_editor = column_editors[get_cell_column(cell)]
|
||||||
|
column_editor.set_selected(cell, true)
|
||||||
|
edited_cells.append(cell)
|
||||||
|
if column_editor.is_text():
|
||||||
|
edited_cells_text.append(str(cell.text))
|
||||||
|
edit_cursor_positions.append(cell.text.length())
|
||||||
|
|
||||||
|
|
||||||
|
func _update_selected_cells_text():
|
||||||
|
if edited_cells_text.size() == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
var column_dtype : int = editor_view.column_types[get_cell_column(edited_cells[0])]
|
||||||
|
for i in edited_cells.size():
|
||||||
|
edited_cells_text[i] = editor_view.try_convert(edited_cells[i].text, column_dtype)
|
||||||
|
edit_cursor_positions[i] = edited_cells_text[i].length()
|
||||||
|
|
||||||
|
|
||||||
|
func _try_open_docks(cell : Control):
|
||||||
|
var column_index = get_cell_column(cell)
|
||||||
|
var row = editor_view.rows[get_cell_row(cell)]
|
||||||
|
var column = editor_view.columns[column_index]
|
||||||
|
var type = editor_view.column_types[column_index]
|
||||||
|
var hints = editor_view.column_hints[column_index]
|
||||||
|
|
||||||
|
for x in node_property_editors.get_children():
|
||||||
|
x.visible = x.try_edit_value(editor_view.io.get_value(row, column), type, hints)
|
||||||
|
x.get_node(x.path_property_name).text = column
|
||||||
|
|
||||||
|
|
||||||
|
func _on_inspector_property_edited(property : String):
|
||||||
|
if !editor_view.is_visible_in_tree(): return
|
||||||
|
if inspector_resource == null: return
|
||||||
|
|
||||||
|
if editor_view.columns[get_cell_column(edited_cells[0])] != property:
|
||||||
|
var columns := editor_view.columns
|
||||||
|
var previously_edited = edited_cells.duplicate()
|
||||||
|
var new_column := columns.find(property)
|
||||||
|
deselect_all_cells()
|
||||||
|
var index := 0
|
||||||
|
for i in previously_edited.size():
|
||||||
|
index = get_cell_row(previously_edited[i]) * columns.size() + new_column
|
||||||
|
_add_cell_to_selection(editor_view.node_table_root.get_child(index - editor_view.first_row))
|
||||||
|
|
||||||
|
var values = []
|
||||||
|
values.resize(edited_cells.size())
|
||||||
|
values.fill(inspector_resource[property])
|
||||||
|
|
||||||
|
editor_view.set_edited_cells_values.call_deferred(values)
|
||||||
|
_try_open_docks(edited_cells[0])
|
@ -0,0 +1,34 @@
|
|||||||
|
@tool
|
||||||
|
extends HBoxContainer
|
||||||
|
|
||||||
|
var manager : Control
|
||||||
|
|
||||||
|
|
||||||
|
func set_label(label : String):
|
||||||
|
$"Button".text = label.capitalize()
|
||||||
|
$"Button".tooltip_text = label + "\nClick to sort."
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
$"Button".gui_input.connect(_on_main_gui_input)
|
||||||
|
$"Button2".get_popup().id_pressed.connect(_on_list_id_pressed)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_main_gui_input(event):
|
||||||
|
if event is InputEventMouseButton and event.pressed:
|
||||||
|
var popup = $"Button2".get_popup()
|
||||||
|
if event.button_index == MOUSE_BUTTON_RIGHT:
|
||||||
|
popup.visible = !popup.visible
|
||||||
|
popup.size = Vector2.ZERO
|
||||||
|
popup.position = Vector2i(get_global_mouse_position()) + get_viewport().position
|
||||||
|
|
||||||
|
else:
|
||||||
|
popup.visible = false
|
||||||
|
|
||||||
|
|
||||||
|
func _on_list_id_pressed(id : int):
|
||||||
|
if id == 0:
|
||||||
|
manager.select_column(get_index())
|
||||||
|
|
||||||
|
else:
|
||||||
|
manager.hide_column(get_index())
|
@ -0,0 +1,28 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://d1s6oihqedvo5"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/main_screen/table_header.gd" id="1_5fd1m"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="2_0ymob"]
|
||||||
|
|
||||||
|
[node name="Header" type="HBoxContainer"]
|
||||||
|
offset_right = 179.0
|
||||||
|
offset_bottom = 31.0
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
script = ExtResource("1_5fd1m")
|
||||||
|
|
||||||
|
[node name="Button" type="Button" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 0
|
||||||
|
text = "resource_name"
|
||||||
|
clip_text = true
|
||||||
|
|
||||||
|
[node name="Button2" type="MenuButton" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 9
|
||||||
|
item_count = 2
|
||||||
|
popup/item_0/text = "Select All"
|
||||||
|
popup/item_0/id = 0
|
||||||
|
popup/item_1/text = "Hide"
|
||||||
|
popup/item_1/id = 1
|
||||||
|
script = ExtResource("2_0ymob")
|
||||||
|
icon_name = "ArrowDown"
|
110
addons/resources_spreadsheet_view/main_screen/table_pages.gd
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
@tool
|
||||||
|
extends HBoxContainer
|
||||||
|
|
||||||
|
@export @onready var node_editor_view_root : Control = $"../../../.."
|
||||||
|
|
||||||
|
var rows_per_page := 50
|
||||||
|
var current_page := 0
|
||||||
|
var first_row := 0
|
||||||
|
var last_row := 50
|
||||||
|
|
||||||
|
|
||||||
|
func _on_grid_updated():
|
||||||
|
visible = true
|
||||||
|
|
||||||
|
var page_count = (node_editor_view_root.rows.size() - 1) / rows_per_page + 1
|
||||||
|
first_row = min(current_page, page_count) * rows_per_page
|
||||||
|
last_row = min(first_row + rows_per_page, node_editor_view_root.rows.size())
|
||||||
|
|
||||||
|
var pagelist_node = $"Pagelist"
|
||||||
|
for x in pagelist_node.get_children():
|
||||||
|
x.queue_free()
|
||||||
|
|
||||||
|
var button_group = ButtonGroup.new()
|
||||||
|
var btns = []
|
||||||
|
btns.resize(page_count)
|
||||||
|
for i in page_count:
|
||||||
|
var btn = Button.new()
|
||||||
|
btns[i] = btn
|
||||||
|
btn.toggle_mode = true
|
||||||
|
btn.button_group = button_group
|
||||||
|
btn.text = str(i + 1)
|
||||||
|
btn.pressed.connect(_on_button_pressed.bind(btn))
|
||||||
|
pagelist_node.add_child(btn)
|
||||||
|
|
||||||
|
btns[current_page].button_pressed = true
|
||||||
|
|
||||||
|
var sort_property = node_editor_view_root.sorting_by
|
||||||
|
if sort_property == "": sort_property = "resource_path"
|
||||||
|
var sort_type = node_editor_view_root.column_types[node_editor_view_root.columns.find(sort_property)]
|
||||||
|
var property_values = []
|
||||||
|
property_values.resize(page_count)
|
||||||
|
if(node_editor_view_root.rows.size() == 0):
|
||||||
|
return
|
||||||
|
for i in page_count:
|
||||||
|
property_values[i] = node_editor_view_root.rows[i * rows_per_page].get(sort_property)
|
||||||
|
|
||||||
|
if sort_type == TYPE_FLOAT or sort_type == TYPE_INT:
|
||||||
|
for i in page_count:
|
||||||
|
btns[i].text = str(property_values[i])
|
||||||
|
|
||||||
|
elif sort_type == TYPE_COLOR:
|
||||||
|
for i in page_count:
|
||||||
|
btns[i].self_modulate = property_values[i] * 0.75 + Color(0.25, 0.25, 0.25, 1.0)
|
||||||
|
|
||||||
|
elif sort_type == TYPE_STRING:
|
||||||
|
var strings = []
|
||||||
|
strings.resize(page_count)
|
||||||
|
for i in page_count:
|
||||||
|
strings[i] = property_values[i].get_file()
|
||||||
|
if strings[i] == "":
|
||||||
|
strings[i] = str(i)
|
||||||
|
|
||||||
|
_fill_buttons_with_prefixes(btns, strings, page_count)
|
||||||
|
|
||||||
|
elif sort_type == TYPE_OBJECT:
|
||||||
|
var strings = []
|
||||||
|
strings.resize(page_count + 1)
|
||||||
|
for i in page_count:
|
||||||
|
if is_instance_valid(property_values[i]):
|
||||||
|
strings[i] = property_values[i].resource_path.get_file()
|
||||||
|
|
||||||
|
_fill_buttons_with_prefixes(btns, strings, page_count)
|
||||||
|
|
||||||
|
|
||||||
|
func _fill_buttons_with_prefixes(btns : Array, strings : Array, page_count : int):
|
||||||
|
for i in page_count:
|
||||||
|
if strings[i] == null:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if i == 0:
|
||||||
|
btns[0].text = strings[0][0]
|
||||||
|
continue
|
||||||
|
|
||||||
|
for j in strings[i].length():
|
||||||
|
if strings[i].unicode_at(j) != strings[i - 1].unicode_at(j):
|
||||||
|
btns[i].text = strings[i].left(j + 1)
|
||||||
|
btns[i - 1].text = strings[i - 1].left(max(j + 1, btns[i - 1].text.length()))
|
||||||
|
break
|
||||||
|
|
||||||
|
for i in page_count - 1:
|
||||||
|
btns[i].text = btns[i].text + "-" + btns[i + 1].text
|
||||||
|
|
||||||
|
btns[page_count - 1].text += "-[End]"
|
||||||
|
|
||||||
|
|
||||||
|
func _on_button_pressed(button):
|
||||||
|
button.button_pressed = true
|
||||||
|
current_page = button.get_index()
|
||||||
|
_update_view()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_LineEdit_value_changed(value):
|
||||||
|
rows_per_page = value
|
||||||
|
current_page = 0
|
||||||
|
_update_view()
|
||||||
|
|
||||||
|
|
||||||
|
func _update_view():
|
||||||
|
_on_grid_updated()
|
||||||
|
node_editor_view_root.refresh(false)
|
9
addons/resources_spreadsheet_view/plugin.cfg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[plugin]
|
||||||
|
|
||||||
|
name="Edit Resources as Table"
|
||||||
|
description="Edit Many Resources from one Folder as a table.
|
||||||
|
|
||||||
|
Heavily inspired by Multi-Cursor-Editing in text editors, so after selecting multiple cells (in the same column!) using Ctrl+Click or Shift+Click, most Basic-to-Intermediate movements should be available."
|
||||||
|
author="Don Tnowe"
|
||||||
|
version="2.7"
|
||||||
|
script="plugin.gd"
|
43
addons/resources_spreadsheet_view/plugin.gd
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
@tool
|
||||||
|
extends EditorPlugin
|
||||||
|
|
||||||
|
var editor_view : Control
|
||||||
|
var undo_redo : EditorUndoRedoManager
|
||||||
|
|
||||||
|
|
||||||
|
func _enter_tree() -> void:
|
||||||
|
editor_view = load(get_script().resource_path.get_base_dir() + "/editor_view.tscn").instantiate()
|
||||||
|
editor_view.editor_interface = get_editor_interface()
|
||||||
|
if editor_view.editor_interface == null:
|
||||||
|
# 4.2: now a singleton
|
||||||
|
editor_view.editor_interface = Engine.get_singleton("EditorInterface")
|
||||||
|
|
||||||
|
editor_view.editor_plugin = self
|
||||||
|
undo_redo = get_undo_redo()
|
||||||
|
get_editor_interface().get_editor_main_screen().add_child(editor_view)
|
||||||
|
_make_visible(false)
|
||||||
|
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
if is_instance_valid(editor_view):
|
||||||
|
editor_view.queue_free()
|
||||||
|
|
||||||
|
|
||||||
|
func _get_plugin_name():
|
||||||
|
return "ResourceTables"
|
||||||
|
|
||||||
|
|
||||||
|
func _make_visible(visible):
|
||||||
|
if is_instance_valid(editor_view):
|
||||||
|
editor_view.visible = visible
|
||||||
|
if visible:
|
||||||
|
editor_view.display_folder(editor_view.current_path)
|
||||||
|
|
||||||
|
|
||||||
|
func _has_main_screen():
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _get_plugin_icon():
|
||||||
|
# Until I add an actual icon, this'll do.
|
||||||
|
return get_editor_interface().get_base_control().get_theme_icon("VisualShaderNodeComment", "EditorIcons")
|
14
addons/resources_spreadsheet_view/saved_state.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"hidden_columns": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"recent_paths": [],
|
||||||
|
"table_functions": {
|
||||||
|
"filter": [
|
||||||
|
"return true"
|
||||||
|
],
|
||||||
|
"process": [
|
||||||
|
"return value"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
31
addons/resources_spreadsheet_view/settings_grid.gd
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
@tool
|
||||||
|
extends GridContainer
|
||||||
|
|
||||||
|
const PREFIX = "addons/resources_spreadsheet_view/"
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
ProjectSettings.set_setting(PREFIX + "array_color_tint", 100.0 if ProjectSettings.get_setting(PREFIX + "color_arrays", true) else 0.0)
|
||||||
|
ProjectSettings.set_setting(PREFIX + "color_arrays", null)
|
||||||
|
|
||||||
|
for x in get_children():
|
||||||
|
var setting : String = PREFIX + x.name.to_snake_case()
|
||||||
|
if x is BaseButton:
|
||||||
|
x.toggled.connect(_set_setting.bind(setting))
|
||||||
|
if !ProjectSettings.has_setting(setting):
|
||||||
|
_set_setting(x.button_pressed, setting)
|
||||||
|
|
||||||
|
else:
|
||||||
|
x.button_pressed = ProjectSettings.get_setting(setting)
|
||||||
|
|
||||||
|
elif x is Range:
|
||||||
|
x.value_changed.connect(_set_setting.bind(setting))
|
||||||
|
if !ProjectSettings.has_setting(setting):
|
||||||
|
_set_setting(x.value, setting)
|
||||||
|
|
||||||
|
else:
|
||||||
|
x.value = ProjectSettings.get_setting(setting)
|
||||||
|
|
||||||
|
|
||||||
|
func _set_setting(new_value, setting : String):
|
||||||
|
ProjectSettings.set_setting(setting, new_value)
|
100
addons/resources_spreadsheet_view/settings_grid.tscn
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://dhunxgcae6h1"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/settings_grid.gd" id="1_s8s2f"]
|
||||||
|
|
||||||
|
[node name="Settings" type="ScrollContainer"]
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = 8.0
|
||||||
|
offset_top = 8.0
|
||||||
|
offset_right = -622.0
|
||||||
|
offset_bottom = -322.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="RichTextLabel" type="VBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="GridContainer" type="GridContainer" parent="RichTextLabel"]
|
||||||
|
layout_mode = 2
|
||||||
|
columns = 2
|
||||||
|
script = ExtResource("1_s8s2f")
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="RichTextLabel/GridContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "Color-type cells style rows"
|
||||||
|
autowrap_mode = 2
|
||||||
|
|
||||||
|
[node name="ColorRows" type="CheckBox" parent="RichTextLabel/GridContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
button_pressed = true
|
||||||
|
text = "Enable"
|
||||||
|
|
||||||
|
[node name="Label2" type="Label" parent="RichTextLabel/GridContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "Collection item color tint"
|
||||||
|
autowrap_mode = 2
|
||||||
|
|
||||||
|
[node name="ArrayColorTint" type="SpinBox" parent="RichTextLabel/GridContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
value = 100.0
|
||||||
|
|
||||||
|
[node name="Label3" type="Label" parent="RichTextLabel/GridContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "Array cell min width"
|
||||||
|
autowrap_mode = 2
|
||||||
|
|
||||||
|
[node name="ArrayMinWidth" type="SpinBox" parent="RichTextLabel/GridContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
min_value = 32.0
|
||||||
|
max_value = 512.0
|
||||||
|
value = 128.0
|
||||||
|
|
||||||
|
[node name="Label8" type="Label" parent="RichTextLabel/GridContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "Resource preview size (Including Textures)"
|
||||||
|
autowrap_mode = 2
|
||||||
|
|
||||||
|
[node name="ResourcePreviewSize" type="SpinBox" parent="RichTextLabel/GridContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
min_value = 8.0
|
||||||
|
max_value = 1024.0
|
||||||
|
value = 32.0
|
||||||
|
|
||||||
|
[node name="Label7" type="Label" parent="RichTextLabel/GridContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "Clip header text (makes columns smaller)"
|
||||||
|
autowrap_mode = 2
|
||||||
|
|
||||||
|
[node name="ClipHeaders" type="CheckBox" parent="RichTextLabel/GridContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Enable"
|
||||||
|
|
||||||
|
[node name="Label5" type="Label" parent="RichTextLabel/GridContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "Duplicate arrays on edit (slower, but can be undone)"
|
||||||
|
autowrap_mode = 2
|
||||||
|
|
||||||
|
[node name="DupeArrays" type="CheckBox" parent="RichTextLabel/GridContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
button_pressed = true
|
||||||
|
text = "Enable"
|
||||||
|
|
||||||
|
[node name="Label6" type="Label" parent="RichTextLabel/GridContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "Show context menu on left-click (and not just rightclick)"
|
||||||
|
autowrap_mode = 2
|
||||||
|
|
||||||
|
[node name="ContextMenuOnLeftclick" type="CheckBox" parent="RichTextLabel/GridContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Enable"
|
149
addons/resources_spreadsheet_view/text_editing_utils.gd
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
const non_typing_paragraph := "¶"
|
||||||
|
const non_typing_space := "●"
|
||||||
|
const whitespace_chars := [
|
||||||
|
32, # " "
|
||||||
|
44, # ","
|
||||||
|
58, # ":"
|
||||||
|
45, # "-"
|
||||||
|
59, # ";"
|
||||||
|
40, # "("
|
||||||
|
41, # ")"
|
||||||
|
46, # "."
|
||||||
|
182, # "¶" Linefeed
|
||||||
|
10, # "\n" Actual Linefeed
|
||||||
|
967, # "●" Whitespace
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
static func is_character_whitespace(text : String, idx : int) -> bool:
|
||||||
|
if idx <= 0: return true # Stop at the edges.
|
||||||
|
if idx >= text.length(): return true
|
||||||
|
return text.unicode_at(idx) in whitespace_chars
|
||||||
|
|
||||||
|
|
||||||
|
static func show_non_typing(text : String) -> String:
|
||||||
|
text = text\
|
||||||
|
.replace(non_typing_paragraph, "\n")\
|
||||||
|
.replace(non_typing_space, " ")
|
||||||
|
|
||||||
|
if text.ends_with("\n"):
|
||||||
|
text = text.left(text.length() - 1) + non_typing_paragraph
|
||||||
|
|
||||||
|
elif text.ends_with(" "):
|
||||||
|
text = text.left(text.length() - 1) + non_typing_space
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
static func revert_non_typing(text : String) -> String:
|
||||||
|
if text.ends_with(non_typing_paragraph):
|
||||||
|
text = text.left(text.length() - 1) + "\n"
|
||||||
|
|
||||||
|
elif text.ends_with(non_typing_space):
|
||||||
|
text = text.left(text.length() - 1) + " "
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
static func multi_erase_right(values : Array, cursor_positions : Array, ctrl_pressed : bool):
|
||||||
|
for i in values.size():
|
||||||
|
var start_pos = cursor_positions[i]
|
||||||
|
cursor_positions[i] = _step_cursor(values[i], cursor_positions[i], 1, ctrl_pressed)
|
||||||
|
|
||||||
|
cursor_positions[i] = min(
|
||||||
|
cursor_positions[i],
|
||||||
|
values[i].length()
|
||||||
|
)
|
||||||
|
values[i] = (
|
||||||
|
values[i].left(start_pos)
|
||||||
|
+ values[i].substr(cursor_positions[i])
|
||||||
|
)
|
||||||
|
cursor_positions[i] = start_pos
|
||||||
|
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
static func multi_erase_left(values : Array, cursor_positions : Array, ctrl_pressed : bool):
|
||||||
|
for i in values.size():
|
||||||
|
var start_pos = cursor_positions[i]
|
||||||
|
|
||||||
|
cursor_positions[i] = _step_cursor(values[i], cursor_positions[i], -1, ctrl_pressed)
|
||||||
|
values[i] = (
|
||||||
|
values[i].substr(0, cursor_positions[i])
|
||||||
|
+ values[i].substr(start_pos)
|
||||||
|
)
|
||||||
|
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
static func multi_move_left(values : Array, cursor_positions : Array, ctrl_pressed : bool):
|
||||||
|
for i in cursor_positions.size():
|
||||||
|
cursor_positions[i] = _step_cursor(values[i], cursor_positions[i], -1, ctrl_pressed)
|
||||||
|
|
||||||
|
|
||||||
|
static func multi_move_right(values : Array, cursor_positions : Array, ctrl_pressed : bool):
|
||||||
|
for i in cursor_positions.size():
|
||||||
|
cursor_positions[i] = _step_cursor(values[i], cursor_positions[i], 1, ctrl_pressed)
|
||||||
|
|
||||||
|
|
||||||
|
static func multi_paste(values : Array, cursor_positions : Array):
|
||||||
|
var pasted_lines := DisplayServer.clipboard_get().replace("\r", "").split("\n")
|
||||||
|
var paste_each_line := pasted_lines.size() == values.size()
|
||||||
|
|
||||||
|
for i in values.size():
|
||||||
|
if paste_each_line:
|
||||||
|
cursor_positions[i] += pasted_lines[i].length()
|
||||||
|
|
||||||
|
else:
|
||||||
|
cursor_positions[i] += DisplayServer.clipboard_get().length()
|
||||||
|
|
||||||
|
values[i] = (
|
||||||
|
values[i].left(cursor_positions[i])
|
||||||
|
+ (pasted_lines[i] if paste_each_line else DisplayServer.clipboard_get())
|
||||||
|
+ values[i].substr(cursor_positions[i])
|
||||||
|
)
|
||||||
|
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
static func multi_copy(values : Array):
|
||||||
|
for i in values.size():
|
||||||
|
values[i] = values[i]
|
||||||
|
|
||||||
|
DisplayServer.clipboard_set("\n".join(values))
|
||||||
|
|
||||||
|
|
||||||
|
static func multi_input(input_char : String, values : Array, cursor_positions : Array):
|
||||||
|
for i in values.size():
|
||||||
|
values[i] = (
|
||||||
|
values[i].left(cursor_positions[i])
|
||||||
|
+ input_char
|
||||||
|
+ values[i].substr(cursor_positions[i])
|
||||||
|
)
|
||||||
|
cursor_positions[i] = min(cursor_positions[i] + 1, values[i].length())
|
||||||
|
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
static func _step_cursor(text : String, start : int, step : int = 1, ctrl_pressed : bool = false) -> int:
|
||||||
|
var cur := start
|
||||||
|
if ctrl_pressed and is_character_whitespace(text, cur + step):
|
||||||
|
cur += step
|
||||||
|
|
||||||
|
while true:
|
||||||
|
cur += step
|
||||||
|
if !ctrl_pressed or is_character_whitespace(text, cur):
|
||||||
|
if cur > text.length():
|
||||||
|
return text.length()
|
||||||
|
|
||||||
|
if cur <= 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if ctrl_pressed and step < 0:
|
||||||
|
return cur + 1
|
||||||
|
|
||||||
|
return cur
|
||||||
|
|
||||||
|
return 0
|
36
addons/resources_spreadsheet_view/typed_cells/array.tscn
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
[gd_scene format=3 uid="uid://ydrs54md3knl"]
|
||||||
|
|
||||||
|
[node name="Label" type="MarginContainer"]
|
||||||
|
offset_right = 16.0
|
||||||
|
offset_bottom = 16.0
|
||||||
|
size_flags_vertical = 9
|
||||||
|
mouse_filter = 0
|
||||||
|
|
||||||
|
[node name="Box" type="HFlowContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Back" type="Control" parent="."]
|
||||||
|
show_behind_parent = true
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="ColorRect" type="ColorRect" parent="Back"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = 1.0
|
||||||
|
offset_top = 1.0
|
||||||
|
offset_right = -1.0
|
||||||
|
offset_bottom = -1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
color = Color(0.247059, 0.247059, 0.247059, 0.498039)
|
||||||
|
|
||||||
|
[node name="Selected" type="ColorRect" parent="."]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
color = Color(1, 1, 1, 0.247059)
|
33
addons/resources_spreadsheet_view/typed_cells/basic.tscn
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
[gd_scene format=3 uid="uid://cghfjg6qt3rb1"]
|
||||||
|
|
||||||
|
[node name="Label" type="Label"]
|
||||||
|
offset_right = 20.0
|
||||||
|
offset_bottom = 23.0
|
||||||
|
size_flags_vertical = 9
|
||||||
|
mouse_filter = 0
|
||||||
|
|
||||||
|
[node name="Back" type="ColorRect" parent="."]
|
||||||
|
show_behind_parent = true
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = 1.0
|
||||||
|
offset_top = 1.0
|
||||||
|
offset_right = -1.0
|
||||||
|
offset_bottom = -1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
color = Color(0.247059, 0.247059, 0.247059, 0.498039)
|
||||||
|
|
||||||
|
[node name="Selected" type="ColorRect" parent="."]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
color = Color(1, 1, 1, 0.247059)
|
34
addons/resources_spreadsheet_view/typed_cells/cell_editor.gd
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
class_name ResourceTablesCellEditor
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
const TextEditingUtilsClass := preload("res://addons/resources_spreadsheet_view/text_editing_utils.gd")
|
||||||
|
|
||||||
|
const CELL_SCENE_DIR = "res://addons/resources_spreadsheet_view/typed_cells/"
|
||||||
|
|
||||||
|
var hint_strings_array := []
|
||||||
|
|
||||||
|
|
||||||
|
## Override to define where the cell should be shown.
|
||||||
|
func can_edit_value(value, type, property_hint, column_index) -> bool:
|
||||||
|
return value != null
|
||||||
|
|
||||||
|
## Override to change how the cell is created; preload a scene or create nodes from code.
|
||||||
|
## Caller is an instance of [code]editor_view.tscn[/code].
|
||||||
|
func create_cell(caller : Control) -> Control:
|
||||||
|
return load(CELL_SCENE_DIR + "basic.tscn").instantiate()
|
||||||
|
|
||||||
|
## Override to change behaviour when the cell is clicked to be selected.
|
||||||
|
func set_selected(node : Control, selected : bool):
|
||||||
|
node.get_node("Selected").visible = selected
|
||||||
|
|
||||||
|
## Override to change how the value is displayed.
|
||||||
|
func set_value(node : Control, value):
|
||||||
|
node.text = TextEditingUtilsClass.show_non_typing(str(value))
|
||||||
|
|
||||||
|
## Override to prevent the cell from being edited as text.
|
||||||
|
func is_text():
|
||||||
|
return true
|
||||||
|
|
||||||
|
## Override to change behaviour when there are color cells to the left of this cell.
|
||||||
|
func set_color(node : Control, color : Color):
|
||||||
|
node.get_node("Back").modulate = color * 1.0
|
@ -0,0 +1,49 @@
|
|||||||
|
extends ResourceTablesCellEditor
|
||||||
|
|
||||||
|
const TablesPluginSettingsClass := preload("res://addons/resources_spreadsheet_view/settings_grid.gd")
|
||||||
|
|
||||||
|
|
||||||
|
func can_edit_value(value, type, property_hint, column_index) -> bool:
|
||||||
|
return type == TYPE_PACKED_STRING_ARRAY or type == TYPE_ARRAY
|
||||||
|
|
||||||
|
|
||||||
|
func create_cell(caller : Control) -> Control:
|
||||||
|
return load(CELL_SCENE_DIR + "array.tscn").instantiate()
|
||||||
|
|
||||||
|
|
||||||
|
func set_value(node : Control, value):
|
||||||
|
var children := node.get_node("Box").get_children()
|
||||||
|
node.custom_minimum_size.x = ProjectSettings.get_setting(TablesPluginSettingsClass.PREFIX + "array_min_width")
|
||||||
|
var color_tint : float = 0.01 * ProjectSettings.get_setting(TablesPluginSettingsClass.PREFIX + "array_color_tint", 100.0)
|
||||||
|
while children.size() < value.size():
|
||||||
|
children.append(Label.new())
|
||||||
|
node.get_node("Box").add_child(children[children.size() - 1])
|
||||||
|
|
||||||
|
var column_hints = hint_strings_array[node.get_index() % hint_strings_array.size()]
|
||||||
|
for i in children.size():
|
||||||
|
if i >= value.size():
|
||||||
|
children[i].visible = false
|
||||||
|
|
||||||
|
else:
|
||||||
|
children[i].visible = true
|
||||||
|
_write_value_to_child(value[i], value[i], column_hints, children[i], color_tint)
|
||||||
|
|
||||||
|
|
||||||
|
func _write_value_to_child(value, key, hint_arr : PackedStringArray, child : Label, color_tint : float):
|
||||||
|
if value is Resource:
|
||||||
|
value = _resource_to_string(value)
|
||||||
|
|
||||||
|
child.text = str(value)
|
||||||
|
child.self_modulate = (
|
||||||
|
Color.WHITE * (1.0 - color_tint)
|
||||||
|
+
|
||||||
|
(Color(str(key).hash()) + Color(0.2, 0.2, 0.2, 1.0)) * color_tint
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _resource_to_string(res : Resource):
|
||||||
|
return res.resource_name if res.resource_name != "" else res.resource_path.get_file()
|
||||||
|
|
||||||
|
|
||||||
|
func is_text():
|
||||||
|
return false
|
@ -0,0 +1,18 @@
|
|||||||
|
extends ResourceTablesCellEditor
|
||||||
|
|
||||||
|
|
||||||
|
func can_edit_value(value, type, property_hint, column_index) -> bool:
|
||||||
|
return type == TYPE_BOOL
|
||||||
|
|
||||||
|
|
||||||
|
func set_value(node : Control, value):
|
||||||
|
if value is bool:
|
||||||
|
_set_value_internal(node, value)
|
||||||
|
|
||||||
|
else:
|
||||||
|
_set_value_internal(node, !node.text.begins_with("O"))
|
||||||
|
|
||||||
|
|
||||||
|
func _set_value_internal(node, value):
|
||||||
|
node.text = "ON" if value else "off"
|
||||||
|
node.self_modulate.a = 1.0 if value else 0.2
|
@ -0,0 +1,35 @@
|
|||||||
|
extends ResourceTablesCellEditor
|
||||||
|
|
||||||
|
var _cached_color := Color.WHITE
|
||||||
|
|
||||||
|
|
||||||
|
func create_cell(caller : Control) -> Control:
|
||||||
|
var node : Label = load(CELL_SCENE_DIR + "basic.tscn").instantiate()
|
||||||
|
var color := ColorRect.new()
|
||||||
|
node.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||||
|
node.custom_minimum_size.x = 56
|
||||||
|
node.add_child(color)
|
||||||
|
color.name = "Color"
|
||||||
|
_resize_color_rect.call_deferred(color)
|
||||||
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
func _resize_color_rect(rect):
|
||||||
|
if !is_instance_valid(rect): return # Table refreshed twice, probably? Either way, this fix is easier
|
||||||
|
rect.size = Vector2(8, 0)
|
||||||
|
rect.set_anchors_and_offsets_preset(Control.PRESET_LEFT_WIDE, Control.PRESET_MODE_KEEP_WIDTH)
|
||||||
|
|
||||||
|
|
||||||
|
func can_edit_value(value, type, property_hint, property_hint_string) -> bool:
|
||||||
|
return type == TYPE_COLOR
|
||||||
|
|
||||||
|
|
||||||
|
func set_value(node : Control, value):
|
||||||
|
if value is String:
|
||||||
|
node.text = TextEditingUtilsClass.show_non_typing(str(value))
|
||||||
|
|
||||||
|
else:
|
||||||
|
node.text = value.to_html(true)
|
||||||
|
_cached_color = value
|
||||||
|
|
||||||
|
node.get_node("Color").color = value
|
@ -0,0 +1,37 @@
|
|||||||
|
extends "res://addons/resources_spreadsheet_view/typed_cells/cell_editor_array.gd"
|
||||||
|
|
||||||
|
|
||||||
|
func can_edit_value(value, type, property_hint, column_index) -> bool:
|
||||||
|
return type == TYPE_DICTIONARY
|
||||||
|
|
||||||
|
|
||||||
|
func create_cell(caller : Control) -> Control:
|
||||||
|
return load(CELL_SCENE_DIR + "array.tscn").instantiate()
|
||||||
|
|
||||||
|
|
||||||
|
func set_value(node : Control, value):
|
||||||
|
var children = node.get_node("Box").get_children()
|
||||||
|
node.custom_minimum_size.x = ProjectSettings.get_setting(TablesPluginSettingsClass.PREFIX + "array_min_width")
|
||||||
|
var color_tint : float = 0.01 * ProjectSettings.get_setting(TablesPluginSettingsClass.PREFIX + "array_color_tint", 100.0)
|
||||||
|
while children.size() < value.size():
|
||||||
|
children.append(Label.new())
|
||||||
|
node.get_node("Box").add_child(children[children.size() - 1])
|
||||||
|
|
||||||
|
var column_hints = hint_strings_array[node.get_index() % hint_strings_array.size()]
|
||||||
|
var values : Array = value.values()
|
||||||
|
var keys : Array = value.keys()
|
||||||
|
|
||||||
|
for i in children.size():
|
||||||
|
if i >= values.size():
|
||||||
|
children[i].visible = false
|
||||||
|
|
||||||
|
else:
|
||||||
|
children[i].visible = true
|
||||||
|
if values[i] is Resource: values[i] = _resource_to_string(values[i])
|
||||||
|
if keys[i] is Resource: keys[i] = _resource_to_string(keys[i])
|
||||||
|
|
||||||
|
_write_value_to_child("%s ◆ %s" % [keys[i], values[i]], keys[i], column_hints, children[i], color_tint)
|
||||||
|
|
||||||
|
|
||||||
|
func is_text():
|
||||||
|
return false
|
@ -0,0 +1,38 @@
|
|||||||
|
extends ResourceTablesCellEditor
|
||||||
|
|
||||||
|
|
||||||
|
func can_edit_value(value, type, property_hint, column_index) -> bool:
|
||||||
|
return type == TYPE_INT and property_hint == PROPERTY_HINT_ENUM
|
||||||
|
|
||||||
|
|
||||||
|
func set_value(node : Control, value):
|
||||||
|
if value == null:
|
||||||
|
# Sometimes, when creating new property, becomes null
|
||||||
|
value = 0
|
||||||
|
|
||||||
|
var value_str : String
|
||||||
|
var key_found := -1
|
||||||
|
var hint_arr : Array = hint_strings_array[node.get_index() % hint_strings_array.size()]
|
||||||
|
for i in hint_arr.size():
|
||||||
|
var colon_found : int = hint_arr[i].rfind(":")
|
||||||
|
if colon_found == -1:
|
||||||
|
key_found = value
|
||||||
|
break
|
||||||
|
|
||||||
|
if hint_arr[i].substr(colon_found + 1).to_int() == value:
|
||||||
|
key_found = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if key_found != -1:
|
||||||
|
value_str = hint_arr[key_found]
|
||||||
|
|
||||||
|
else:
|
||||||
|
value_str = "?:%s" % value
|
||||||
|
|
||||||
|
node.text = value_str
|
||||||
|
node.self_modulate = Color(node.text.hash()) + Color(0.25, 0.25, 0.25, 1.0)
|
||||||
|
node.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
|
||||||
|
|
||||||
|
func is_text():
|
||||||
|
return false
|
@ -0,0 +1,39 @@
|
|||||||
|
extends "res://addons/resources_spreadsheet_view/typed_cells/cell_editor_array.gd"
|
||||||
|
|
||||||
|
|
||||||
|
func can_edit_value(value, type, property_hint, column_index) -> bool:
|
||||||
|
if (
|
||||||
|
type != TYPE_PACKED_INT32_ARRAY
|
||||||
|
and type != TYPE_PACKED_INT64_ARRAY
|
||||||
|
and type != TYPE_ARRAY
|
||||||
|
) or property_hint != PROPERTY_HINT_TYPE_STRING:
|
||||||
|
return false
|
||||||
|
|
||||||
|
return hint_strings_array[column_index][0].begins_with("2/2:")
|
||||||
|
|
||||||
|
|
||||||
|
func _write_value_to_child(value, key, hint_arr : PackedStringArray, child : Label, color_tint : float):
|
||||||
|
var value_str : String
|
||||||
|
var key_found := -1
|
||||||
|
for i in hint_arr.size():
|
||||||
|
var colon_found := hint_arr[i].rfind(":")
|
||||||
|
if colon_found == -1:
|
||||||
|
key_found = value
|
||||||
|
break
|
||||||
|
|
||||||
|
if hint_arr[i].substr(colon_found + 1).to_int() == value:
|
||||||
|
key_found = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if key_found == 0:
|
||||||
|
# Enum array hints have "2/3:" before list.
|
||||||
|
var found := hint_arr[0].find(":") + 1
|
||||||
|
value_str = hint_arr[0].substr(hint_arr[0].find(":") + 1)
|
||||||
|
|
||||||
|
elif key_found != -1:
|
||||||
|
value_str = hint_arr[key_found]
|
||||||
|
|
||||||
|
else:
|
||||||
|
value_str = "?:%s" % value
|
||||||
|
|
||||||
|
super(value_str, value_str, hint_arr, child, color_tint)
|
@ -0,0 +1,60 @@
|
|||||||
|
extends ResourceTablesCellEditor
|
||||||
|
|
||||||
|
const TablesPluginSettingsClass := preload("res://addons/resources_spreadsheet_view/settings_grid.gd")
|
||||||
|
|
||||||
|
var previewer : EditorResourcePreview
|
||||||
|
|
||||||
|
|
||||||
|
func can_edit_value(value, type, property_hint, column_index) -> bool:
|
||||||
|
return type == TYPE_OBJECT
|
||||||
|
|
||||||
|
|
||||||
|
func create_cell(caller : Control) -> Control:
|
||||||
|
if previewer == null:
|
||||||
|
previewer = caller.editor_plugin.get_editor_interface().get_resource_previewer()
|
||||||
|
|
||||||
|
var node = load(CELL_SCENE_DIR + "resource.tscn").instantiate()
|
||||||
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
func set_value(node : Control, value):
|
||||||
|
if value == null:
|
||||||
|
node.get_node("Box/Tex").visible = false
|
||||||
|
node.get_node("Box/Label").text = "[empty]"
|
||||||
|
node.editor_description = ""
|
||||||
|
|
||||||
|
if !value is Resource: return
|
||||||
|
|
||||||
|
node.editor_description = value.resource_path
|
||||||
|
if value.resource_name == "":
|
||||||
|
node.get_node("Box/Label").text = "[" + value.resource_path.get_file().get_basename() + "]"
|
||||||
|
|
||||||
|
else:
|
||||||
|
node.get_node("Box/Label").text = value.resource_name
|
||||||
|
|
||||||
|
if value is Texture:
|
||||||
|
node.get_node("Box/Tex").visible = true
|
||||||
|
node.get_node("Box/Tex").texture = value
|
||||||
|
|
||||||
|
else:
|
||||||
|
node.get_node("Box/Tex").visible = false
|
||||||
|
previewer.queue_resource_preview(value.resource_path, self, &"_on_preview_loaded", node)
|
||||||
|
|
||||||
|
node.get_node("Box/Tex").custom_minimum_size = Vector2.ONE * ProjectSettings.get_setting(
|
||||||
|
TablesPluginSettingsClass.PREFIX + "resource_preview_size"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func set_color(node : Control, color : Color):
|
||||||
|
node.get_node("Back").modulate = color * 0.6 if node.editor_description == "" else color
|
||||||
|
|
||||||
|
|
||||||
|
func is_text():
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
func _on_preview_loaded(path : String, preview : Texture, thumbnail_preview : Texture, node):
|
||||||
|
# Abort if the node has been deleted since.
|
||||||
|
if is_instance_valid(node):
|
||||||
|
node.get_node("Box/Tex").visible = true
|
||||||
|
node.get_node("Box/Tex").texture = preview
|
@ -0,0 +1,5 @@
|
|||||||
|
extends ResourceTablesCellEditor
|
||||||
|
|
||||||
|
|
||||||
|
func set_color(node : Control, color : Color):
|
||||||
|
node.get_node("Back").modulate = color * 0.6 if !node.text.is_valid_float() else color
|
45
addons/resources_spreadsheet_view/typed_cells/resource.tscn
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
[gd_scene format=3 uid="uid://clcndgxaty503"]
|
||||||
|
|
||||||
|
[node name="Label" type="MarginContainer"]
|
||||||
|
size_flags_vertical = 9
|
||||||
|
mouse_filter = 0
|
||||||
|
|
||||||
|
[node name="Back" type="Control" parent="."]
|
||||||
|
show_behind_parent = true
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="ColorRect" type="ColorRect" parent="Back"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = 1.0
|
||||||
|
offset_top = 1.0
|
||||||
|
offset_right = -2.0
|
||||||
|
offset_bottom = -1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
color = Color(0.247059, 0.247059, 0.247059, 0.498039)
|
||||||
|
|
||||||
|
[node name="Selected" type="ColorRect" parent="."]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
color = Color(1, 1, 1, 0.247059)
|
||||||
|
|
||||||
|
[node name="Box" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Tex" type="TextureRect" parent="Box"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
expand_mode = 1
|
||||||
|
stretch_mode = 5
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Box"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "res.tres"
|
222
addons/resources_spreadsheet_view/typed_editors/dock_array.gd
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
@tool
|
||||||
|
extends ResourceTablesDockEditor
|
||||||
|
|
||||||
|
@onready var recent_container := $"HBoxContainer/Control2/HBoxContainer/HFlowContainer"
|
||||||
|
@onready var contents_label := $"HBoxContainer/HBoxContainer/Panel/Label"
|
||||||
|
@onready var button_box := $"HBoxContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer"
|
||||||
|
@onready var value_input := $"HBoxContainer/HBoxContainer/Control/VBoxContainer/LineEdit"
|
||||||
|
|
||||||
|
var _stored_value
|
||||||
|
var _stored_type := 0
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
super()
|
||||||
|
contents_label.text_changed.connect(_on_contents_edit_text_changed)
|
||||||
|
|
||||||
|
|
||||||
|
func try_edit_value(value, type, property_hint) -> bool:
|
||||||
|
if (
|
||||||
|
type != TYPE_ARRAY and type != TYPE_PACKED_STRING_ARRAY
|
||||||
|
and type != TYPE_PACKED_INT32_ARRAY and type != TYPE_PACKED_FLOAT32_ARRAY
|
||||||
|
and type != TYPE_PACKED_INT64_ARRAY and type != TYPE_PACKED_FLOAT64_ARRAY
|
||||||
|
):
|
||||||
|
return false
|
||||||
|
|
||||||
|
if sheet.column_hint_strings[sheet.get_selected_column()][0].begins_with("2/2:"):
|
||||||
|
# For enums, prefer the specialized dock.
|
||||||
|
return false
|
||||||
|
|
||||||
|
_stored_type = type
|
||||||
|
_stored_value = value.duplicate() # Generic arrays are passed by reference
|
||||||
|
contents_label.text = str(value)
|
||||||
|
|
||||||
|
var is_generic_array = _stored_type == TYPE_ARRAY and !value.is_typed()
|
||||||
|
button_box.get_child(1).visible = (
|
||||||
|
is_generic_array or value.get_typed_builtin() == TYPE_STRING
|
||||||
|
or _stored_type == TYPE_PACKED_STRING_ARRAY
|
||||||
|
)
|
||||||
|
button_box.get_child(2).visible = (
|
||||||
|
is_generic_array or value.get_typed_builtin() == TYPE_INT
|
||||||
|
or _stored_type == TYPE_PACKED_INT32_ARRAY or _stored_type == TYPE_PACKED_INT64_ARRAY
|
||||||
|
)
|
||||||
|
button_box.get_child(3).visible = (
|
||||||
|
is_generic_array or value.get_typed_builtin() == TYPE_FLOAT
|
||||||
|
or _stored_type == TYPE_PACKED_FLOAT32_ARRAY or _stored_type == TYPE_PACKED_FLOAT64_ARRAY
|
||||||
|
)
|
||||||
|
button_box.get_child(5).visible = (
|
||||||
|
is_generic_array or value.get_typed_builtin() == TYPE_OBJECT
|
||||||
|
)
|
||||||
|
|
||||||
|
if value.get_typed_builtin() == TYPE_OBJECT:
|
||||||
|
if !value_input is EditorResourcePicker:
|
||||||
|
var new_input := EditorResourcePicker.new()
|
||||||
|
new_input.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||||
|
new_input.base_type = value.get_typed_class_name()
|
||||||
|
|
||||||
|
value_input.replace_by(new_input)
|
||||||
|
value_input.free()
|
||||||
|
value_input = new_input
|
||||||
|
|
||||||
|
else:
|
||||||
|
if !value_input is LineEdit:
|
||||||
|
var new_input := LineEdit.new()
|
||||||
|
new_input.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||||
|
|
||||||
|
value_input.replace_by(new_input)
|
||||||
|
value_input.free()
|
||||||
|
value_input = new_input
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _add_value(value):
|
||||||
|
_stored_value.append(value)
|
||||||
|
var values = sheet.get_edited_cells_values()
|
||||||
|
var cur_value
|
||||||
|
var dupe_array : bool = ProjectSettings.get_setting(TablesPluginSettingsClass.PREFIX + "dupe_arrays")
|
||||||
|
for i in values.size():
|
||||||
|
cur_value = values[i]
|
||||||
|
if dupe_array:
|
||||||
|
cur_value = cur_value.duplicate()
|
||||||
|
|
||||||
|
cur_value.append(value)
|
||||||
|
values[i] = cur_value
|
||||||
|
|
||||||
|
sheet.set_edited_cells_values(values)
|
||||||
|
|
||||||
|
|
||||||
|
func _remove_value(value):
|
||||||
|
_stored_value.remove_at(_stored_value.find(value))
|
||||||
|
var values = sheet.get_edited_cells_values()
|
||||||
|
var cur_value : Array
|
||||||
|
var dupe_array : bool = ProjectSettings.get_setting(TablesPluginSettingsClass.PREFIX + "dupe_arrays")
|
||||||
|
for i in values.size():
|
||||||
|
cur_value = values[i]
|
||||||
|
if dupe_array:
|
||||||
|
cur_value = cur_value.duplicate()
|
||||||
|
|
||||||
|
if cur_value.has(value): # erase() not defined in PoolArrays
|
||||||
|
cur_value.remove_at(cur_value.find(value))
|
||||||
|
|
||||||
|
values[i] = cur_value
|
||||||
|
|
||||||
|
sheet.set_edited_cells_values(values)
|
||||||
|
|
||||||
|
|
||||||
|
func _add_recent(value):
|
||||||
|
for x in recent_container.get_children():
|
||||||
|
if x.text == str(value):
|
||||||
|
return
|
||||||
|
|
||||||
|
if value is Resource and x.tooltip_text == value.resource_path:
|
||||||
|
return
|
||||||
|
|
||||||
|
var node := Button.new()
|
||||||
|
var value_str : String = str(value)
|
||||||
|
if value is Resource:
|
||||||
|
value_str = value.resource_path.get_file()
|
||||||
|
node.tooltip_text = value.resource_path
|
||||||
|
value = value.resource_path
|
||||||
|
|
||||||
|
node.text = value_str
|
||||||
|
node.self_modulate = Color(value_str.hash()) + Color(0.25, 0.25, 0.25, 1.0)
|
||||||
|
node.pressed.connect(_on_recent_clicked.bind(node, value))
|
||||||
|
recent_container.add_child(node)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_recent_clicked(button, value):
|
||||||
|
var val = recent_container.get_child(1).selected
|
||||||
|
value_input.text = str(value)
|
||||||
|
if val == 0:
|
||||||
|
_add_value(value)
|
||||||
|
|
||||||
|
if val == 1:
|
||||||
|
_remove_value(value)
|
||||||
|
|
||||||
|
if val == 2:
|
||||||
|
button.queue_free()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Remove_pressed():
|
||||||
|
if value_input is EditorResourcePicker:
|
||||||
|
_remove_value(value_input.edited_resource)
|
||||||
|
|
||||||
|
elif str_to_var(value_input.text) != null:
|
||||||
|
_remove_value(str_to_var(value_input.text))
|
||||||
|
|
||||||
|
else:
|
||||||
|
_remove_value(value_input.text)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_RemoveLast_pressed():
|
||||||
|
_stored_value.pop_back()
|
||||||
|
var values = sheet.get_edited_cells_values()
|
||||||
|
var cur_value : Array
|
||||||
|
var dupe_array : bool = ProjectSettings.get_setting(TablesPluginSettingsClass.PREFIX + "dupe_arrays")
|
||||||
|
for i in values.size():
|
||||||
|
cur_value = values[i]
|
||||||
|
if dupe_array:
|
||||||
|
cur_value = cur_value.duplicate()
|
||||||
|
|
||||||
|
cur_value.pop_back()
|
||||||
|
values[i] = cur_value
|
||||||
|
|
||||||
|
sheet.set_edited_cells_values(values)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_ClearRecent_pressed():
|
||||||
|
for i in recent_container.get_child_count():
|
||||||
|
if i == 0: continue
|
||||||
|
recent_container.get_child(i).free()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Float_pressed():
|
||||||
|
_add_value(value_input.text.to_float())
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Int_pressed():
|
||||||
|
_add_value(value_input.text.to_int())
|
||||||
|
|
||||||
|
|
||||||
|
func _on_String_pressed():
|
||||||
|
_add_value(value_input.text)
|
||||||
|
_add_recent(value_input.text)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Variant_pressed():
|
||||||
|
if value_input is EditorResourcePicker:
|
||||||
|
_add_value(value_input.edited_resource)
|
||||||
|
|
||||||
|
else:
|
||||||
|
_add_value(str_to_var(value_input.text))
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Resource_pressed():
|
||||||
|
if value_input is LineEdit:
|
||||||
|
_add_value(load(value_input.text))
|
||||||
|
|
||||||
|
elif value_input is EditorResourcePicker:
|
||||||
|
_add_value(value_input.edited_resource)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_AddRecentFromSel_pressed():
|
||||||
|
for x in sheet.get_edited_cells_values():
|
||||||
|
for y in x:
|
||||||
|
_add_recent(y)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_contents_edit_text_changed():
|
||||||
|
var value := str_to_var(contents_label.text)
|
||||||
|
if !value is Array:
|
||||||
|
return
|
||||||
|
|
||||||
|
var values = sheet.get_edited_cells_values()
|
||||||
|
for i in values.size():
|
||||||
|
values[i] = values[i].duplicate()
|
||||||
|
values[i].resize(value.size())
|
||||||
|
for j in value.size():
|
||||||
|
values[i][j] = value[j]
|
||||||
|
|
||||||
|
_stored_value = value
|
||||||
|
sheet.set_edited_cells_values(values)
|
190
addons/resources_spreadsheet_view/typed_editors/dock_array.tscn
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://c3a2cip8ffccv"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_editors/dock_array.gd" id="1"]
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="2"]
|
||||||
|
|
||||||
|
[sub_resource type="Image" id="Image_ytggl"]
|
||||||
|
data = {
|
||||||
|
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||||
|
"format": "RGBA8",
|
||||||
|
"height": 16,
|
||||||
|
"mipmaps": false,
|
||||||
|
"width": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id="ImageTexture_3oshq"]
|
||||||
|
image = SubResource("Image_ytggl")
|
||||||
|
|
||||||
|
[node name="EditArray" type="VBoxContainer"]
|
||||||
|
anchors_preset = 10
|
||||||
|
anchor_right = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
mouse_filter = 0
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="Header" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="Header"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Header/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "EDIT: Array"
|
||||||
|
|
||||||
|
[node name="HSeparator" type="HSeparator" parent="Header/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Header"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "PROPERTY NAME"
|
||||||
|
|
||||||
|
[node name="HSeparator2" type="HSeparator" parent="Header"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HSplitContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
split_offset = 380
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
alignment = 2
|
||||||
|
|
||||||
|
[node name="Panel" type="MarginContainer" parent="HBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="Label" type="TextEdit" parent="HBoxContainer/HBoxContainer/Panel"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 5
|
||||||
|
text = "[]"
|
||||||
|
|
||||||
|
[node name="VSeparator2" type="VSeparator" parent="HBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Control" type="MarginContainer" parent="HBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer/HBoxContainer/Control"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="LineEdit" type="LineEdit" parent="HBoxContainer/HBoxContainer/Control/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
placeholder_text = "Input value to add/erase..."
|
||||||
|
clear_button_enabled = true
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer/HBoxContainer/Control/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="HBoxContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Add:"
|
||||||
|
|
||||||
|
[node name="String" type="Button" parent="HBoxContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Add string"
|
||||||
|
icon = SubResource("ImageTexture_3oshq")
|
||||||
|
script = ExtResource("2")
|
||||||
|
icon_name = "String"
|
||||||
|
|
||||||
|
[node name="Int" type="Button" parent="HBoxContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Add integer"
|
||||||
|
icon = SubResource("ImageTexture_3oshq")
|
||||||
|
script = ExtResource("2")
|
||||||
|
icon_name = "int"
|
||||||
|
|
||||||
|
[node name="Float" type="Button" parent="HBoxContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Add float"
|
||||||
|
icon = SubResource("ImageTexture_3oshq")
|
||||||
|
script = ExtResource("2")
|
||||||
|
icon_name = "float"
|
||||||
|
|
||||||
|
[node name="Variant" type="Button" parent="HBoxContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Guess type and add"
|
||||||
|
icon = SubResource("ImageTexture_3oshq")
|
||||||
|
script = ExtResource("2")
|
||||||
|
icon_name = "Variant"
|
||||||
|
|
||||||
|
[node name="Object" type="Button" parent="HBoxContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Add resource (by path if string)"
|
||||||
|
icon = SubResource("ImageTexture_3oshq")
|
||||||
|
script = ExtResource("2")
|
||||||
|
icon_name = "Object"
|
||||||
|
|
||||||
|
[node name="Label2" type="Label" parent="HBoxContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Erase:"
|
||||||
|
|
||||||
|
[node name="Remove" type="Button" parent="HBoxContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Find and erase value in textbox"
|
||||||
|
icon = SubResource("ImageTexture_3oshq")
|
||||||
|
script = ExtResource("2")
|
||||||
|
icon_name = "Remove"
|
||||||
|
|
||||||
|
[node name="Remove2" type="Button" parent="HBoxContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Remove last value"
|
||||||
|
icon = SubResource("ImageTexture_3oshq")
|
||||||
|
script = ExtResource("2")
|
||||||
|
icon_name = "MoveLeft"
|
||||||
|
|
||||||
|
[node name="VSeparator" type="VSeparator" parent="HBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Control2" type="MarginContainer" parent="HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer/Control2"]
|
||||||
|
layout_mode = 2
|
||||||
|
alignment = 2
|
||||||
|
|
||||||
|
[node name="VSeparator2" type="VSeparator" parent="HBoxContainer/Control2/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="HFlowContainer" type="HFlowContainer" parent="HBoxContainer/Control2/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="HBoxContainer/Control2/HBoxContainer/HFlowContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Recent:"
|
||||||
|
|
||||||
|
[node name="OptionButton" type="OptionButton" parent="HBoxContainer/Control2/HBoxContainer/HFlowContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
item_count = 3
|
||||||
|
selected = 0
|
||||||
|
fit_to_longest_item = false
|
||||||
|
popup/item_0/text = "Add"
|
||||||
|
popup/item_0/id = 0
|
||||||
|
popup/item_1/text = "Erase"
|
||||||
|
popup/item_1/id = 1
|
||||||
|
popup/item_2/text = "Remove From Recent"
|
||||||
|
popup/item_2/id = 2
|
||||||
|
|
||||||
|
[node name="AddRecentFromSel" type="Button" parent="HBoxContainer/Control2/HBoxContainer/HFlowContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Add from selected cells"
|
||||||
|
icon = SubResource("ImageTexture_3oshq")
|
||||||
|
script = ExtResource("2")
|
||||||
|
icon_name = "ListSelect"
|
||||||
|
|
||||||
|
[connection signal="pressed" from="HBoxContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer/String" to="." method="_on_String_pressed"]
|
||||||
|
[connection signal="pressed" from="HBoxContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer/Int" to="." method="_on_Int_pressed"]
|
||||||
|
[connection signal="pressed" from="HBoxContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer/Float" to="." method="_on_Float_pressed"]
|
||||||
|
[connection signal="pressed" from="HBoxContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer/Variant" to="." method="_on_Variant_pressed"]
|
||||||
|
[connection signal="pressed" from="HBoxContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer/Object" to="." method="_on_Resource_pressed"]
|
||||||
|
[connection signal="pressed" from="HBoxContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer/Remove" to="." method="_on_Remove_pressed"]
|
||||||
|
[connection signal="pressed" from="HBoxContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer/Remove2" to="." method="_on_RemoveLast_pressed"]
|
||||||
|
[connection signal="pressed" from="HBoxContainer/Control2/HBoxContainer/HFlowContainer/AddRecentFromSel" to="." method="_on_AddRecentFromSel_pressed"]
|
46
addons/resources_spreadsheet_view/typed_editors/dock_base.gd
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
@tool
|
||||||
|
class_name ResourceTablesDockEditor
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
const TablesPluginSettingsClass := preload("res://addons/resources_spreadsheet_view/settings_grid.gd")
|
||||||
|
|
||||||
|
@export var path_property_name := NodePath("Header/Label")
|
||||||
|
|
||||||
|
var sheet : Control
|
||||||
|
var selection : Array
|
||||||
|
|
||||||
|
var _resize_target_height := 0.0
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
var parent := get_parent()
|
||||||
|
while parent != null and !parent.has_method(&"display_folder"):
|
||||||
|
parent = parent.get_parent()
|
||||||
|
|
||||||
|
sheet = parent
|
||||||
|
get_node(path_property_name).add_theme_font_override(&"normal", get_theme_font(&"bold", &"EditorFonts"))
|
||||||
|
|
||||||
|
$"Header".gui_input.connect(_on_header_gui_input)
|
||||||
|
$"Header".mouse_filter = MOUSE_FILTER_STOP
|
||||||
|
$"Header".mouse_default_cursor_shape = CURSOR_VSIZE
|
||||||
|
|
||||||
|
## Override to define when to show the dock and, if it can edit the value, how to handle it.
|
||||||
|
func try_edit_value(value, type : int, property_hint : String) -> bool:
|
||||||
|
return true
|
||||||
|
|
||||||
|
## Override to define behaviour when stretching the header to change size.
|
||||||
|
func resize_drag(to_height : float):
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
func _on_header_gui_input(event : InputEvent):
|
||||||
|
if event is InputEventMouseMotion:
|
||||||
|
var pressed := Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)
|
||||||
|
if pressed:
|
||||||
|
_resize_target_height -= event.relative.y
|
||||||
|
custom_minimum_size.y = clamp(_resize_target_height, 0.0, get_viewport().size.y * 0.75)
|
||||||
|
resize_drag(_resize_target_height)
|
||||||
|
get_child(1).visible = _resize_target_height > $"Header".size.y
|
||||||
|
|
||||||
|
if event is InputEventMouseButton:
|
||||||
|
_resize_target_height = custom_minimum_size.y
|
139
addons/resources_spreadsheet_view/typed_editors/dock_color.gd
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
@tool
|
||||||
|
extends ResourceTablesDockEditor
|
||||||
|
|
||||||
|
@onready var _value_rect := $"EditColor/ColorProper/ColorRect"
|
||||||
|
@onready var _color_picker_panel := $"EditColor/VSeparator6/Panel"
|
||||||
|
@onready var _color_picker := $"EditColor/VSeparator6/Panel/MarginContainer/ColorPicker"
|
||||||
|
@onready var _custom_value_edit := $"EditColor/CustomX/Box/LineEdit"
|
||||||
|
|
||||||
|
var _stored_value := Color.WHITE
|
||||||
|
var _resize_height_small := 0.0
|
||||||
|
var _resize_expanded := true
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
super._ready()
|
||||||
|
_connect_buttons($"EditColor/RGBGrid", 0, 0)
|
||||||
|
_connect_buttons($"EditColor/RGBGrid", 5, 1)
|
||||||
|
_connect_buttons($"EditColor/RGBGrid", 10, 2)
|
||||||
|
_connect_buttons($"EditColor/HSVGrid", 0, 3)
|
||||||
|
_connect_buttons($"EditColor/HSVGrid", 5, 4)
|
||||||
|
_connect_buttons($"EditColor/HSVGrid", 10, 5)
|
||||||
|
|
||||||
|
_resize_height_small = get_child(1).get_minimum_size().y
|
||||||
|
|
||||||
|
|
||||||
|
func _connect_buttons(grid, start_index, property_bind):
|
||||||
|
grid.get_child(start_index + 0).pressed.connect(_increment_values_custom.bind(-1.0, property_bind))
|
||||||
|
grid.get_child(start_index + 1).pressed.connect(_increment_values.bind(-10.0, property_bind))
|
||||||
|
grid.get_child(start_index + 3).pressed.connect(_increment_values.bind(10.0, property_bind))
|
||||||
|
grid.get_child(start_index + 4).pressed.connect(_increment_values_custom.bind(1.0, property_bind))
|
||||||
|
|
||||||
|
|
||||||
|
func try_edit_value(value, type, property_hint) -> bool:
|
||||||
|
_color_picker_panel.top_level = false
|
||||||
|
if type != TYPE_COLOR:
|
||||||
|
return false
|
||||||
|
|
||||||
|
_set_stored_value(value)
|
||||||
|
_color_picker_panel.visible = false
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func resize_drag(to_height : float):
|
||||||
|
var expanded := to_height > _resize_height_small
|
||||||
|
if _resize_expanded == expanded:
|
||||||
|
return
|
||||||
|
|
||||||
|
_resize_expanded = expanded
|
||||||
|
$"EditColor/RGBGrid".visible = expanded
|
||||||
|
$"EditColor/ColorProper".visible = expanded
|
||||||
|
$"EditColor/HSVGrid".columns = 5 if expanded else 15
|
||||||
|
$"EditColor/CustomX/Label".visible = expanded
|
||||||
|
|
||||||
|
|
||||||
|
func _set_stored_value(v):
|
||||||
|
_stored_value = v
|
||||||
|
_color_picker.color = v
|
||||||
|
_value_rect.color = v
|
||||||
|
|
||||||
|
|
||||||
|
func _increment_values(by : float, property : int):
|
||||||
|
var cell_values = sheet.get_edited_cells_values()
|
||||||
|
match property:
|
||||||
|
0:
|
||||||
|
_stored_value.r += by / 255.0
|
||||||
|
for i in cell_values.size():
|
||||||
|
cell_values[i].r += by / 255.0
|
||||||
|
|
||||||
|
1:
|
||||||
|
_stored_value.g += by / 255.0
|
||||||
|
for i in cell_values.size():
|
||||||
|
cell_values[i].g += by / 255.0
|
||||||
|
|
||||||
|
2:
|
||||||
|
_stored_value.b += by / 255.0
|
||||||
|
for i in cell_values.size():
|
||||||
|
cell_values[i].b += by / 255.0
|
||||||
|
|
||||||
|
3:
|
||||||
|
# Hue has 360 degrees and loops
|
||||||
|
_stored_value.h += by / 360.0
|
||||||
|
for i in cell_values.size():
|
||||||
|
cell_values[i].h = fposmod(cell_values[i].h + by / 360.0, 1.0)
|
||||||
|
|
||||||
|
4:
|
||||||
|
_stored_value.s += by * 0.005
|
||||||
|
for i in cell_values.size():
|
||||||
|
cell_values[i].s += by * 0.005
|
||||||
|
|
||||||
|
5:
|
||||||
|
_stored_value.v += by * 0.005
|
||||||
|
for i in cell_values.size():
|
||||||
|
cell_values[i].v += by * 0.005
|
||||||
|
|
||||||
|
_set_stored_value(_stored_value)
|
||||||
|
sheet.set_edited_cells_values(cell_values)
|
||||||
|
|
||||||
|
|
||||||
|
func _increment_values_custom(multiplier : float, property : int):
|
||||||
|
if property == 4 or property == 5:
|
||||||
|
# Numbered buttons increment by 5 for Sat and Value, so hue is x0.5 effect. Negate it here
|
||||||
|
multiplier *= 2.0
|
||||||
|
|
||||||
|
_increment_values(_custom_value_edit.text.to_float() * multiplier, property)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Button_pressed():
|
||||||
|
_color_picker_panel.visible = !_color_picker_panel.visible
|
||||||
|
if _color_picker_panel.visible:
|
||||||
|
_color_picker_panel.top_level = true
|
||||||
|
_color_picker_panel.global_position = (
|
||||||
|
sheet.global_position
|
||||||
|
+ Vector2(0, sheet.size.y - _color_picker_panel.size.y)
|
||||||
|
+ Vector2(16, -16)
|
||||||
|
)
|
||||||
|
_color_picker_panel.global_position.y = clamp(
|
||||||
|
_color_picker_panel.global_position.y,
|
||||||
|
0,
|
||||||
|
sheet.editor_plugin.get_editor_interface().get_base_control().size.y
|
||||||
|
)
|
||||||
|
_color_picker.color = _stored_value
|
||||||
|
|
||||||
|
elif _color_picker.color != _stored_value:
|
||||||
|
_set_stored_value(_color_picker.color)
|
||||||
|
update_cell_values()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_ColorPicker_gui_input(event : InputEvent):
|
||||||
|
if event is InputEventMouseButton and !event.pressed:
|
||||||
|
_set_stored_value(_color_picker.color)
|
||||||
|
update_cell_values()
|
||||||
|
|
||||||
|
|
||||||
|
func update_cell_values():
|
||||||
|
var values = sheet.get_edited_cells_values()
|
||||||
|
for i in values.size():
|
||||||
|
values[i] = _stored_value
|
||||||
|
|
||||||
|
sheet.set_edited_cells_values(values)
|
295
addons/resources_spreadsheet_view/typed_editors/dock_color.tscn
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://b3a3bo6cfyh5t"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_editors/dock_color.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="EditColor" type="VBoxContainer"]
|
||||||
|
offset_bottom = 131.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
mouse_filter = 0
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="Header" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 0
|
||||||
|
mouse_default_cursor_shape = 9
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="Header"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Header/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "EDIT: Color"
|
||||||
|
|
||||||
|
[node name="HSeparator" type="HSeparator" parent="Header/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Header"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "PROPERTY NAME"
|
||||||
|
|
||||||
|
[node name="HSeparator2" type="HSeparator" parent="Header"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="EditColor" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="VSeparator7" type="Control" parent="EditColor"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="VSeparator3" type="Control" parent="EditColor"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="ButtonRowTemplate" type="Control" parent="EditColor"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Button3" type="Button" parent="EditColor/ButtonRowTemplate"]
|
||||||
|
layout_mode = 0
|
||||||
|
text = "-X"
|
||||||
|
|
||||||
|
[node name="Button2" type="Button" parent="EditColor/ButtonRowTemplate"]
|
||||||
|
layout_mode = 0
|
||||||
|
text = "-5"
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="EditColor/ButtonRowTemplate"]
|
||||||
|
layout_mode = 0
|
||||||
|
text = "Red"
|
||||||
|
|
||||||
|
[node name="Button5" type="Button" parent="EditColor/ButtonRowTemplate"]
|
||||||
|
layout_mode = 0
|
||||||
|
text = "+5"
|
||||||
|
|
||||||
|
[node name="Button6" type="Button" parent="EditColor/ButtonRowTemplate"]
|
||||||
|
layout_mode = 0
|
||||||
|
text = "+X"
|
||||||
|
|
||||||
|
[node name="RGBGrid" type="GridContainer" parent="EditColor"]
|
||||||
|
layout_mode = 2
|
||||||
|
columns = 5
|
||||||
|
|
||||||
|
[node name="Button3" type="Button" parent="EditColor/RGBGrid"]
|
||||||
|
modulate = Color(1, 0.780392, 0.780392, 1)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "-X"
|
||||||
|
|
||||||
|
[node name="Button2" type="Button" parent="EditColor/RGBGrid"]
|
||||||
|
modulate = Color(1, 0.780392, 0.780392, 1)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "-10"
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="EditColor/RGBGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Red"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Button5" type="Button" parent="EditColor/RGBGrid"]
|
||||||
|
modulate = Color(1, 0.780392, 0.780392, 1)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "+10"
|
||||||
|
|
||||||
|
[node name="Button6" type="Button" parent="EditColor/RGBGrid"]
|
||||||
|
modulate = Color(1, 0.780392, 0.780392, 1)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "+X"
|
||||||
|
|
||||||
|
[node name="Button7" type="Button" parent="EditColor/RGBGrid"]
|
||||||
|
modulate = Color(0.666667, 1, 0.745098, 1)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "-X"
|
||||||
|
|
||||||
|
[node name="Button8" type="Button" parent="EditColor/RGBGrid"]
|
||||||
|
modulate = Color(0.666667, 1, 0.745098, 1)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "-10"
|
||||||
|
|
||||||
|
[node name="Label2" type="Label" parent="EditColor/RGBGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Green"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Button11" type="Button" parent="EditColor/RGBGrid"]
|
||||||
|
modulate = Color(0.666667, 1, 0.745098, 1)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "+10"
|
||||||
|
|
||||||
|
[node name="Button12" type="Button" parent="EditColor/RGBGrid"]
|
||||||
|
modulate = Color(0.666667, 1, 0.745098, 1)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "+X"
|
||||||
|
|
||||||
|
[node name="Button13" type="Button" parent="EditColor/RGBGrid"]
|
||||||
|
modulate = Color(0.772549, 0.792157, 1, 1)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "-X"
|
||||||
|
|
||||||
|
[node name="Button14" type="Button" parent="EditColor/RGBGrid"]
|
||||||
|
modulate = Color(0.772549, 0.792157, 1, 1)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "-10"
|
||||||
|
|
||||||
|
[node name="Label3" type="Label" parent="EditColor/RGBGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Blue"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Button17" type="Button" parent="EditColor/RGBGrid"]
|
||||||
|
modulate = Color(0.772549, 0.792157, 1, 1)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "+10"
|
||||||
|
|
||||||
|
[node name="Button18" type="Button" parent="EditColor/RGBGrid"]
|
||||||
|
modulate = Color(0.772549, 0.792157, 1, 1)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "+X"
|
||||||
|
|
||||||
|
[node name="VSeparator" type="VSeparator" parent="EditColor"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="ColorProper" type="VBoxContainer" parent="EditColor"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="ColorRect" type="ColorRect" parent="EditColor/ColorProper"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="Button" type="Button" parent="EditColor/ColorProper"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Choose Color"
|
||||||
|
|
||||||
|
[node name="VSeparator2" type="VSeparator" parent="EditColor"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="HSVGrid" type="GridContainer" parent="EditColor"]
|
||||||
|
layout_mode = 2
|
||||||
|
columns = 5
|
||||||
|
|
||||||
|
[node name="Button3" type="Button" parent="EditColor/HSVGrid"]
|
||||||
|
modulate = Color(1, 0.913725, 0.776471, 1)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "-X"
|
||||||
|
|
||||||
|
[node name="Button2" type="Button" parent="EditColor/HSVGrid"]
|
||||||
|
modulate = Color(0.898039, 1, 0.698039, 1)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "-10"
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="EditColor/HSVGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Hue"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Button5" type="Button" parent="EditColor/HSVGrid"]
|
||||||
|
modulate = Color(0.717647, 1, 0.980392, 1)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "+10"
|
||||||
|
|
||||||
|
[node name="Button6" type="Button" parent="EditColor/HSVGrid"]
|
||||||
|
modulate = Color(0.74902, 0.729412, 1, 1)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "+X"
|
||||||
|
|
||||||
|
[node name="Button7" type="Button" parent="EditColor/HSVGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "-X"
|
||||||
|
|
||||||
|
[node name="Button8" type="Button" parent="EditColor/HSVGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "-5"
|
||||||
|
|
||||||
|
[node name="Label2" type="Label" parent="EditColor/HSVGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Sat"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Button11" type="Button" parent="EditColor/HSVGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "+5"
|
||||||
|
|
||||||
|
[node name="Button12" type="Button" parent="EditColor/HSVGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "+X"
|
||||||
|
|
||||||
|
[node name="Button13" type="Button" parent="EditColor/HSVGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "-X"
|
||||||
|
|
||||||
|
[node name="Button14" type="Button" parent="EditColor/HSVGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "-5"
|
||||||
|
|
||||||
|
[node name="Label3" type="Label" parent="EditColor/HSVGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Value"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Button17" type="Button" parent="EditColor/HSVGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "+5"
|
||||||
|
|
||||||
|
[node name="Button18" type="Button" parent="EditColor/HSVGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "+X"
|
||||||
|
|
||||||
|
[node name="VSeparator4" type="VSeparator" parent="EditColor"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="CustomX" type="VBoxContainer" parent="EditColor"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="EditColor/CustomX"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 6
|
||||||
|
text = "Custom Value"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Box" type="HBoxContainer" parent="EditColor/CustomX"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label2" type="Label" parent="EditColor/CustomX/Box"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 6
|
||||||
|
text = "X ="
|
||||||
|
|
||||||
|
[node name="LineEdit" type="LineEdit" parent="EditColor/CustomX/Box"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "20"
|
||||||
|
|
||||||
|
[node name="VSeparator6" type="Control" parent="EditColor"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="Panel" type="PanelContainer" parent="EditColor/VSeparator6"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 2
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_vertical = 0
|
||||||
|
|
||||||
|
[node name="Panel2" type="Panel" parent="EditColor/VSeparator6/Panel"]
|
||||||
|
self_modulate = Color(1.5, 1.5, 1.5, 1)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="EditColor/VSeparator6/Panel"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="ColorPicker" type="ColorPicker" parent="EditColor/VSeparator6/Panel/MarginContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Button" type="Button" parent="EditColor/VSeparator6/Panel/MarginContainer/ColorPicker"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "OK"
|
||||||
|
|
||||||
|
[connection signal="pressed" from="EditColor/ColorProper/Button" to="." method="_on_Button_pressed"]
|
||||||
|
[connection signal="gui_input" from="EditColor/VSeparator6/Panel/MarginContainer/ColorPicker" to="." method="_on_ColorPicker_gui_input"]
|
||||||
|
[connection signal="pressed" from="EditColor/VSeparator6/Panel/MarginContainer/ColorPicker/Button" to="." method="_on_Button_pressed"]
|
196
addons/resources_spreadsheet_view/typed_editors/dock_dict.gd
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
@tool
|
||||||
|
extends "res://addons/resources_spreadsheet_view/typed_editors/dock_array.gd"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
KEY_TYPE_STRINGNAME = 0,
|
||||||
|
KEY_TYPE_INT,
|
||||||
|
KEY_TYPE_FLOAT,
|
||||||
|
KEY_TYPE_OBJECT,
|
||||||
|
KEY_TYPE_VARIANT,
|
||||||
|
}
|
||||||
|
|
||||||
|
@onready var key_input := $"HBoxContainer/HBoxContainer/Control/VBoxContainer/KeyEdit/KeyEdit"
|
||||||
|
@onready var key_type := $"HBoxContainer/HBoxContainer/Control/VBoxContainer/KeyEdit/KeyType"
|
||||||
|
|
||||||
|
var _key_type_selected := 0
|
||||||
|
|
||||||
|
|
||||||
|
func try_edit_value(value, type, property_hint) -> bool:
|
||||||
|
if type != TYPE_DICTIONARY and type != TYPE_OBJECT:
|
||||||
|
return false
|
||||||
|
|
||||||
|
if value is Texture2D:
|
||||||
|
# For textures, prefer the specialized dock.
|
||||||
|
return false
|
||||||
|
|
||||||
|
key_type.visible = type != TYPE_OBJECT
|
||||||
|
|
||||||
|
_stored_type = type
|
||||||
|
if type == TYPE_DICTIONARY:
|
||||||
|
_stored_value = value.duplicate()
|
||||||
|
|
||||||
|
contents_label.text = var_to_str_no_sort(value)
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _add_value(value):
|
||||||
|
var key = _get_key_from_box()
|
||||||
|
_stored_value[key] = value
|
||||||
|
|
||||||
|
var values = sheet.get_edited_cells_values()
|
||||||
|
var cur_value
|
||||||
|
var dupe_value : bool = ProjectSettings.get_setting(TablesPluginSettingsClass.PREFIX + "dupe_arrays")
|
||||||
|
for i in values.size():
|
||||||
|
cur_value = values[i]
|
||||||
|
if dupe_value and (_stored_type == TYPE_DICTIONARY or cur_value.resource_path.rfind("::") != -1):
|
||||||
|
cur_value = cur_value.duplicate()
|
||||||
|
|
||||||
|
cur_value[key] = value
|
||||||
|
values[i] = cur_value
|
||||||
|
|
||||||
|
sheet.set_edited_cells_values(values)
|
||||||
|
super._add_recent(key)
|
||||||
|
|
||||||
|
|
||||||
|
func _remove_value(_value):
|
||||||
|
var key = _get_key_from_box()
|
||||||
|
_stored_value.erase(key)
|
||||||
|
|
||||||
|
var values = sheet.get_edited_cells_values()
|
||||||
|
var cur_value
|
||||||
|
var dupe_value : bool = ProjectSettings.get_setting(TablesPluginSettingsClass.PREFIX + "dupe_arrays")
|
||||||
|
for i in values.size():
|
||||||
|
cur_value = values[i]
|
||||||
|
if dupe_value and (_stored_type == TYPE_DICTIONARY or cur_value.resource_path.rfind("::") != -1):
|
||||||
|
cur_value = cur_value.duplicate()
|
||||||
|
|
||||||
|
cur_value.erase(key)
|
||||||
|
values[i] = cur_value
|
||||||
|
|
||||||
|
sheet.set_edited_cells_values(values)
|
||||||
|
|
||||||
|
|
||||||
|
func _get_key_from_box():
|
||||||
|
if _stored_type == TYPE_OBJECT:
|
||||||
|
return StringName(key_input.text)
|
||||||
|
|
||||||
|
return _to_key(key_input.text, _key_type_selected)
|
||||||
|
|
||||||
|
|
||||||
|
func _to_key(from : String, key_type : int):
|
||||||
|
match key_type:
|
||||||
|
KEY_TYPE_STRINGNAME:
|
||||||
|
return StringName(from)
|
||||||
|
|
||||||
|
KEY_TYPE_INT:
|
||||||
|
return from.to_int()
|
||||||
|
|
||||||
|
KEY_TYPE_FLOAT:
|
||||||
|
return from.to_float()
|
||||||
|
|
||||||
|
KEY_TYPE_OBJECT:
|
||||||
|
return load(from)
|
||||||
|
|
||||||
|
KEY_TYPE_VARIANT:
|
||||||
|
return str_to_var(from)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Replace_pressed():
|
||||||
|
var old_key = _to_key(key_input.text, _key_type_selected)
|
||||||
|
var new_key = _to_key(value_input.text, _key_type_selected)
|
||||||
|
_stored_value[new_key] = _stored_value[old_key]
|
||||||
|
|
||||||
|
var values = sheet.get_edited_cells_values()
|
||||||
|
var cur_value
|
||||||
|
var dupe_value : bool = ProjectSettings.get_setting(TablesPluginSettingsClass.PREFIX + "dupe_arrays")
|
||||||
|
for i in values.size():
|
||||||
|
cur_value = values[i]
|
||||||
|
if dupe_value and (_stored_type == TYPE_DICTIONARY or cur_value.resource_path.rfind("::") != -1):
|
||||||
|
cur_value = cur_value.duplicate()
|
||||||
|
|
||||||
|
cur_value[new_key] = cur_value[old_key]
|
||||||
|
values[i] = cur_value
|
||||||
|
|
||||||
|
sheet.set_edited_cells_values(values)
|
||||||
|
|
||||||
|
|
||||||
|
func _add_recent(_value):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func _on_recent_clicked(button, value):
|
||||||
|
var val = recent_container.get_child(1).selected
|
||||||
|
key_input.text = str(value)
|
||||||
|
if val == 0:
|
||||||
|
# Do nothing! What if the value for the key doesn't match?
|
||||||
|
pass
|
||||||
|
|
||||||
|
if val == 1:
|
||||||
|
_remove_value(value)
|
||||||
|
|
||||||
|
if val == 2:
|
||||||
|
button.queue_free()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_key_type_selected(index : int):
|
||||||
|
_key_type_selected = index
|
||||||
|
|
||||||
|
|
||||||
|
func _on_AddRecentFromSel_pressed():
|
||||||
|
for x in sheet.get_edited_cells_values():
|
||||||
|
if _stored_type == TYPE_OBJECT:
|
||||||
|
for y in x.get_property_list():
|
||||||
|
if y[&"usage"] & PROPERTY_USAGE_EDITOR != 0:
|
||||||
|
super._add_recent(y[&"name"])
|
||||||
|
|
||||||
|
else:
|
||||||
|
for y in x:
|
||||||
|
super._add_recent(y)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_contents_edit_text_changed():
|
||||||
|
var value := str_to_var(contents_label.text)
|
||||||
|
if !value is Dictionary:
|
||||||
|
return
|
||||||
|
|
||||||
|
var values = sheet.get_edited_cells_values()
|
||||||
|
for i in values.size():
|
||||||
|
values[i] = value.duplicate()
|
||||||
|
|
||||||
|
_stored_value = value
|
||||||
|
sheet.set_edited_cells_values(values)
|
||||||
|
|
||||||
|
|
||||||
|
func var_to_str_no_sort(value, indent = " ", cur_indent = ""):
|
||||||
|
var lines : Array[String] = []
|
||||||
|
|
||||||
|
if value is Array:
|
||||||
|
cur_indent += indent
|
||||||
|
lines.resize(value.size())
|
||||||
|
for i in lines.size():
|
||||||
|
if value[i] is Array or value[i] is Dictionary:
|
||||||
|
lines[i] = "%s%s" % [cur_indent, var_to_str_no_sort(value[i])]
|
||||||
|
|
||||||
|
else:
|
||||||
|
lines[i] = "%s%s" % [cur_indent, var_to_str(value[i])]
|
||||||
|
|
||||||
|
cur_indent = cur_indent.substr(0, cur_indent.length() - indent.length())
|
||||||
|
return "[\n" + ",\n".join(lines) + "\n]"
|
||||||
|
|
||||||
|
if value is Dictionary:
|
||||||
|
var keys : Array = value.keys()
|
||||||
|
var values : Array = value.values()
|
||||||
|
cur_indent += indent
|
||||||
|
lines.resize(keys.size())
|
||||||
|
for i in lines.size():
|
||||||
|
if values[i] is Array or values[i] is Dictionary:
|
||||||
|
lines[i] = "%s%s : %s" % [cur_indent, var_to_str(keys[i]), var_to_str_no_sort(values[i])]
|
||||||
|
|
||||||
|
else:
|
||||||
|
lines[i] = "%s%s : %s" % [cur_indent, var_to_str(keys[i]), var_to_str(values[i])]
|
||||||
|
|
||||||
|
cur_indent = cur_indent.substr(0, cur_indent.length() - indent.length())
|
||||||
|
return "{\n" + ",\n".join(lines) + "\n}"
|
||||||
|
|
||||||
|
return ",\n".join(lines)
|