Removed all editor functionality and improved new viewer-only UI #54

This commit is contained in:
ChaoticByte 2025-02-03 17:40:56 +01:00
parent e40ab18683
commit 9f5011aaac
No known key found for this signature in database
21 changed files with 391 additions and 642 deletions

View file

@ -11,11 +11,11 @@ config_version=5
[application] [application]
config/name="Fragmented" config/name="Fragmented"
config/version="v8.2" config/version="v9.0"
run/main_scene="res://scenes/main.tscn" run/main_scene="res://src/scenes/main.tscn"
config/features=PackedStringArray("4.3", "Mobile") config/features=PackedStringArray("4.3", "Mobile")
run/low_processor_mode=true run/low_processor_mode=true
config/icon="res://assets/icon.png" config/icon="res://src/assets/icon.png"
[autoload] [autoload]
@ -24,8 +24,8 @@ ShaderDirectiveParser="*res://src/ShaderDirectiveParser.gd"
[display] [display]
window/size/viewport_width=704 window/size/viewport_width=640
window/size/viewport_height=704 window/size/viewport_height=672
window/energy_saving/keep_screen_on=false window/energy_saving/keep_screen_on=false
window/subwindows/embed_subwindows=false window/subwindows/embed_subwindows=false
window/vsync/vsync_mode=0 window/vsync/vsync_mode=0

View file

@ -1,46 +0,0 @@
[gd_scene load_steps=10 format=3 uid="uid://bjah7k4bxo044"]
[ext_resource type="Script" path="res://src/Main.gd" id="1_2625y"]
[ext_resource type="Script" path="res://src/ImageCompositor.gd" id="2_4thch"]
[ext_resource type="Shader" path="res://src/shader/ivd_outline.gdshader" id="3_6xihe"]
[ext_resource type="Script" path="res://src/ImageViewportDisplay.gd" id="3_n4itb"]
[ext_resource type="Script" path="res://src/UIWindow.gd" id="6_8k0ha"]
[ext_resource type="PackedScene" uid="uid://btgits2mfup0h" path="res://scenes/ui_container.tscn" id="7_5ci0e"]
[ext_resource type="Script" path="res://src/Camera.gd" id="8_mls06"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_y2ea0"]
shader = ExtResource("3_6xihe")
shader_parameter/zoom_level = Vector2(1, 1)
[sub_resource type="ViewportTexture" id="ViewportTexture_lct1c"]
viewport_path = NodePath("Compositor")
[node name="Main" type="Node2D"]
script = ExtResource("1_2625y")
[node name="Compositor" type="SubViewport" parent="."]
unique_name_in_owner = true
script = ExtResource("2_4thch")
[node name="ImageViewportDisplay" type="Sprite2D" parent="."]
unique_name_in_owner = true
material = SubResource("ShaderMaterial_y2ea0")
texture = SubResource("ViewportTexture_lct1c")
script = ExtResource("3_n4itb")
[node name="Camera" type="Camera2D" parent="."]
unique_name_in_owner = true
script = ExtResource("8_mls06")
[node name="EditorWindow" type="Window" parent="."]
unique_name_in_owner = true
disable_3d = true
position = Vector2i(48, 36)
size = Vector2i(704, 704)
visible = false
script = ExtResource("6_8k0ha")
[node name="UserInterfaceContainer" parent="EditorWindow" instance=ExtResource("7_5ci0e")]
unique_name_in_owner = true
[connection signal="close_requested" from="EditorWindow" to="EditorWindow" method="_on_close_requested"]

View file

@ -1,204 +0,0 @@
[gd_scene load_steps=3 format=3 uid="uid://btgits2mfup0h"]
[ext_resource type="Script" path="res://src/UIAppVersion.gd" id="1_5qvnb"]
[ext_resource type="Script" path="res://src/Editor.gd" id="2_haub5"]
[node name="UserInterfaceContainer" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="AppName" type="Label" parent="."]
layout_mode = 0
offset_left = 24.0
offset_top = 16.0
offset_right = 208.0
offset_bottom = 48.0
theme_override_font_sizes/font_size = 20
text = "Fragmented"
vertical_alignment = 2
[node name="AppVersion" type="Label" parent="."]
layout_mode = 0
offset_left = 152.0
offset_top = 17.0
offset_right = 208.0
offset_bottom = 47.0
theme_override_font_sizes/font_size = 14
text = "v0
"
vertical_alignment = 2
script = ExtResource("1_5qvnb")
[node name="Editor" type="Control" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 24.0
offset_top = 64.0
offset_right = -24.0
offset_bottom = -16.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("2_haub5")
[node name="OpenShaderDialog" type="FileDialog" parent="Editor"]
unique_name_in_owner = true
auto_translate_mode = 1
title = "Load Shader"
size = Vector2i(521, 175)
ok_button_text = "Open"
mode_overrides_title = false
file_mode = 0
access = 2
filters = PackedStringArray("*.gdshader")
use_native_dialog = true
[node name="SaveShaderDialog" type="FileDialog" parent="Editor"]
unique_name_in_owner = true
auto_translate_mode = 1
title = "Save Shader"
size = Vector2i(661, 175)
ok_button_text = "Save"
mode_overrides_title = false
access = 2
filters = PackedStringArray("*.gdshader")
use_native_dialog = true
[node name="SaveImageDialog" type="FileDialog" parent="Editor"]
unique_name_in_owner = true
auto_translate_mode = 1
title = "Export Image"
size = Vector2i(661, 175)
ok_button_text = "Save"
mode_overrides_title = false
access = 2
filters = PackedStringArray("*.png")
use_native_dialog = true
[node name="CodeEdit" type="CodeEdit" parent="Editor"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = 48.0
grow_horizontal = 2
grow_vertical = 2
theme_override_font_sizes/font_size = 14
placeholder_text = "// Test"
wrap_mode = 1
minimap_draw = true
minimap_width = 40
caret_blink = true
draw_control_chars = true
draw_tabs = true
draw_spaces = true
line_length_guidelines = Array[int]([80])
gutters_draw_line_numbers = true
code_completion_enabled = true
indent_automatic = true
auto_brace_completion_enabled = true
auto_brace_completion_highlight_matching = true
[node name="NewShaderButton" type="Button" parent="Editor"]
layout_mode = 1
offset_right = 48.0
offset_bottom = 32.0
text = "New"
[node name="OpenShaderButton" type="Button" parent="Editor"]
layout_mode = 1
offset_left = 56.0
offset_right = 112.0
offset_bottom = 32.0
text = "Open"
[node name="SaveShaderButton" type="Button" parent="Editor"]
layout_mode = 1
offset_left = 120.0
offset_top = -1.0
offset_right = 176.0
offset_bottom = 31.0
text = "Save"
[node name="SaveShaderAsButton" type="Button" parent="Editor"]
layout_mode = 1
offset_left = 184.0
offset_right = 263.0
offset_bottom = 32.0
text = "Save As"
[node name="SaveImageButton" type="Button" parent="Editor"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -72.0
offset_bottom = 32.0
grow_horizontal = 0
disabled = true
text = "Export"
[node name="FitImageButton" type="Button" parent="Editor"]
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -128.0
offset_right = -80.0
offset_bottom = 32.0
grow_horizontal = 0
text = "Fit"
[node name="ApplyShaderButton" type="Button" parent="Editor"]
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -232.0
offset_right = -136.0
offset_bottom = 31.0
grow_horizontal = 0
text = "Apply (F5)"
[node name="StatusIndicator" type="TextureButton" parent="Editor"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -76.0
offset_top = 56.0
offset_right = -56.0
offset_bottom = 76.0
grow_horizontal = 0
disabled = true
ignore_texture_size = true
stretch_mode = 0
[node name="ErrorMessageDialog" type="AcceptDialog" parent="Editor"]
unique_name_in_owner = true
title = "Status"
initial_position = 2
size = Vector2i(256, 128)
popup_window = true
ok_button_text = "Close"
[connection signal="file_selected" from="Editor/OpenShaderDialog" to="Editor" method="_on_open_shader_dialog_file_selected"]
[connection signal="file_selected" from="Editor/SaveShaderDialog" to="Editor" method="_on_save_shader_dialog_file_selected"]
[connection signal="file_selected" from="Editor/SaveImageDialog" to="Editor" method="_on_save_image_dialog_file_selected"]
[connection signal="code_completion_requested" from="Editor/CodeEdit" to="Editor" method="_on_code_edit_code_completion_requested"]
[connection signal="pressed" from="Editor/NewShaderButton" to="Editor" method="_on_new_shader_button_pressed"]
[connection signal="pressed" from="Editor/OpenShaderButton" to="Editor" method="_on_open_shader_button_pressed"]
[connection signal="pressed" from="Editor/SaveShaderButton" to="Editor" method="_on_save_shader_button_pressed"]
[connection signal="pressed" from="Editor/SaveShaderAsButton" to="Editor" method="_on_save_shader_as_button_pressed"]
[connection signal="pressed" from="Editor/SaveImageButton" to="Editor" method="_on_save_image_button_pressed"]
[connection signal="pressed" from="Editor/FitImageButton" to="Editor" method="_on_fit_image_button_pressed"]
[connection signal="pressed" from="Editor/ApplyShaderButton" to="Editor" method="_on_apply_shader_button_pressed"]
[connection signal="pressed" from="Editor/StatusIndicator" to="Editor" method="_on_status_indicator_pressed"]

View file

@ -1,9 +1,9 @@
extends Camera2D extends Camera2D
var drag = false
@onready var image_viewport_display = %ImageViewportDisplay @onready var image_viewport_display = %ImageViewportDisplay
var drag = false
func _input(event): func _input(event):
if event.is_action_pressed("zoom_out"): if event.is_action_pressed("zoom_out"):
zoom_out() zoom_out()
@ -20,9 +20,11 @@ func fit_image():
if Filesystem.original_image != null: if Filesystem.original_image != null:
var image_size = Filesystem.original_image.get_size() var image_size = Filesystem.original_image.get_size()
var viewport_size = get_viewport_rect().size var viewport_size = get_viewport_rect().size
var zoomf = viewport_size.x / image_size.x / 1.1 var zoomf = 1.0
if zoomf * image_size.y > viewport_size.y: if viewport_size.x / image_size.x * image_size.y > viewport_size.y:
zoomf = viewport_size.y / image_size.y / 1.1 zoomf = viewport_size.y / image_size.y / 1.25
else:
zoomf = viewport_size.x / image_size.x / 1.2
self.zoom = Vector2(zoomf, zoomf) self.zoom = Vector2(zoomf, zoomf)
self.global_position = Vector2(0, 0) self.global_position = Vector2(0, 0)

View file

@ -1,307 +0,0 @@
extends Control
@onready var code_editor = %CodeEdit
@onready var open_shader_dialog = %OpenShaderDialog
@onready var save_shader_dialog = %SaveShaderDialog
@onready var ui_control_filesave = %SaveImageDialog
@onready var save_image_button = %SaveImageButton
@onready var status_indicator = %StatusIndicator
@onready var error_msg_dialog = %ErrorMessageDialog
@onready var main = get_tree().root.get_node("Main")
@onready var compositor = main.get_node("%Compositor")
@onready var camera = main.get_node("%Camera")
#
var status_okay_texture: CompressedTexture2D = preload("uid://m1omb6g45vst")
var status_error_texture: CompressedTexture2D = preload("uid://04iv1gogpuhu")
# # # # # # # # # # #
# GDShader keywords #
# https://github.com/godotengine/godot/blob/e96ad5af98547df71b50c4c4695ac348638113e0/servers/rendering/shader_language.cpp
# https://github.com/godotengine/godot/blob/e96ad5af98547df71b50c4c4695ac348638113e0/servers/rendering/shader_types.cpp
#
const gdshader_boolean_values = [
"true", "false"
]
const gdshader_datatypes = [
"void",
"bool", "bvec2", "bvec3", "bvec4",
"int", "ivec2", "ivec3", "ivec4",
"uint", "uvec2", "uvec3", "uvec4",
"float", "vec2", "vec3", "vec4",
"mat2", "mat3", "mat4",
"sampler2D", "isampler2D", "usampler2D",
"sampler2DArray", "isampler2DArray", "usampler2DArray",
]
const gdshader_precision_modifiers = [
"lowp", "mediump", "heighp"
]
const gdshader_global_space_keywords = [
"uniform", "group_uniforms", "varying", "const",
"struct", "shader_type", "render_mode"
]
const gdshader_uniform_qualifiers = [
"instance", "global"
]
const gdshader_block_keywords = [
"if", "else",
"for", "while", "do",
"switch", "case",
"default", "break", "continue",
"return", "discard"
]
const gdshader_function_specifier_keywords = [
"in", "out", "inout"
]
const gdshader_hints = [
"source_color", "hint_range", "instance_index"
]
const gdshader_sampler_hints = [
"hint_normal",
"hint_default_white", "hint_default_black", "hint_default_transparent",
"hint_anisotropy",
"hint_roughness_r", "hint_roughness_g", "hint_roughness_b", "hint_roughness_a",
"hint_roughness_normal", "hint_roughness_gray",
"hint_screen_texture", "hint_normal_roughness_texture",
"hint_depth_texture",
"filter_nearest", "filter_linear",
"filter_nearest_mipmap", "filter_linear_mipmap",
"filter_nearest_mipmap_anisotropic", "filter_linear_mipmap_anisotropic",
"repeat_enable", "repeat_disable"
]
const gdshader_builtin_functions = [
"radians", "degrees",
"sin", "cos", "tan", "asin", "acos", "atan", "sinh", "cosh", "tanh",
"asinh", "acosh", "atanh",
"pow", "exp", "exp2", "log", "log2", "sqrt", "inversesqrt",
"abs", "sign", "floor", "trunc", "round", "roundEven", "ceil", "fract",
"mod", "modf", "min", "max", "clamp",
"mix", "step", "smoothstep",
"isnan", "isinf",
"floatBitsToInt", "floatBitsToUint", "intBitsToFloat", "uintBitsToFloat",
"length", "distance",
"dot", "cross",
"normalize", "reflect", "refract", "faceforward",
"matrixCompMult", "outerProduct", "transpose",
"determinant", "inverse",
"lessThan", "greaterThan", "lessThanEqual", "greaterThanEqual",
"equal", "notEqual",
"any", "all", "not",
"textureSize", "texture", "textureProj", "textureLod", "texelFetch",
"textureProjLod", "textureGrad", "textureProjGrad", "textureGather",
"textureQueryLod", "textureQueryLevels",
"dFdx", "dFdxCoarse", "dFdxFine", "dFdy", "dFdyCoarse", "dFdyFine",
"fwidth", "fwidthCoarse", "fwidthFine"
]
const gdshader_sub_functions = [
"length", "fma",
"packHalf2x16", "packUnorm2x16", "packSnorm2x16", "packUnorm4x8", "packSnorm4x8",
"unpackHalf2x16", "unpackUnorm2x16", "unpackSnorm2x16", "unpackUnorm4x8", "unpackSnorm4x8",
"bitfieldExtract", "bitfieldInsert", "bitfieldReverse", "bitCount",
"findLSB", "findMSB",
"umulExtended", "imulExtended",
"uaddCarry", "usubBorrow",
"ldexp", "frexp"
]
const gdshader_builtins = [
"TIME", "PI", "TAU", "E",
"VERTEX",
"UV",
"COLOR",
"POINT_SIZE",
"AT_LIGHT_PASS",
"TEXTURE_PIXEL_SIZE",
"SHADOW_VERTEX", "LIGHT_VERTEX",
"FRAGCOORD",
"NORMAL", "NORMAL_MAP", "NORMAL_MAP_DEPTH",
"TEXTURE",
"POINT_COORD",
"SPECULAR_SHININESS"
]
const gdshader_preprocessor = [
"define", "undef", "include", "pragma",
"if", "elif", "ifdef", "ifndef", "else", "endif"
]
# shaderlib
var shaderlib_regex = {} # auto-generated
const shaderlib_functions = {
"blur": ["gaussian_blur"],
"common": ["alpha_blend", "cbrt"],
"denoise": ["smart_denoise"],
"hsv": ["rgb2hsv", "hsv2rgb",],
"kuwahara": ["kuwahara",],
"oklab": ["oklab2rgb", "rgb2oklab", "oklab2oklch", "oklch2oklab"],
"pixelate": ["pixelate"],
"place_texture": ["place_texture"],
}
#
# configure Highlighter
#
class ShaderSyntaxHighlighter extends CodeHighlighter:
func _init():
add_color_region("//", "", Color.WEB_GRAY, true)
add_color_region("/*", "*/", Color.WEB_GRAY, false)
function_color = Color.INDIAN_RED
for k in gdshader_boolean_values:
keyword_colors[k] = Color.INDIAN_RED
for k in ( gdshader_datatypes
+ gdshader_hints
+ gdshader_sampler_hints
+ gdshader_global_space_keywords
+ gdshader_function_specifier_keywords
+ gdshader_precision_modifiers
+ gdshader_uniform_qualifiers):
keyword_colors[k] = Color.ORCHID;
for k in gdshader_block_keywords:
keyword_colors[k] = Color.CORAL
for k in gdshader_builtins:
keyword_colors[k] = Color.DARK_TURQUOISE
member_variable_color = Color.LIGHT_BLUE
number_color = Color.AQUA
symbol_color = Color.GRAY
#
# and code completion
#
func _on_code_edit_code_completion_requested():
for k in gdshader_boolean_values:
code_editor.code_completion_prefixes.append(k)
code_editor.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, k, k, Color.INDIAN_RED)
for k in ( gdshader_datatypes
+ gdshader_hints
+ gdshader_sampler_hints
+ gdshader_global_space_keywords
+ gdshader_function_specifier_keywords
+ gdshader_precision_modifiers
+ gdshader_uniform_qualifiers):
code_editor.code_completion_prefixes.append(k)
code_editor.add_code_completion_option(CodeEdit.KIND_CLASS, k, k, Color.ORCHID)
for k in gdshader_block_keywords:
code_editor.code_completion_prefixes.append(k)
code_editor.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, k, k, Color.CORAL)
for k in gdshader_builtins:
code_editor.code_completion_prefixes.append(k)
code_editor.add_code_completion_option(CodeEdit.KIND_CONSTANT, k, k, Color.DARK_TURQUOISE)
for k in gdshader_builtin_functions + gdshader_sub_functions:
code_editor.code_completion_prefixes.append(k)
code_editor.add_code_completion_option(CodeEdit.KIND_FUNCTION, k, k+"(", Color.INDIAN_RED)
for k in gdshader_preprocessor:
code_editor.code_completion_prefixes.append(k)
code_editor.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, "#" + k, k)
# shaderlib #
var shader_code = code_editor.text
for key in shaderlib_regex:
if shaderlib_regex[key].search(shader_code) != null:
if key in shaderlib_functions:
for k in shaderlib_functions[key]:
code_editor.code_completion_prefixes.append(k)
code_editor.add_code_completion_option(CodeEdit.KIND_FUNCTION, k, k+"(", Color.INDIAN_RED)
# # # # # # #
code_editor.update_code_completion_options(true)
#
# # # # # # # # # # # #
func _ready():
# generate regexes
for k in shaderlib_functions:
shaderlib_regex[k] = RegEx.create_from_string(
r'\s*\#include\s+\"res\:\/\/shaderlib\/' + k + r'\.gdshaderinc\"')
#
code_editor.code_completion_enabled = true
code_editor.syntax_highlighter = ShaderSyntaxHighlighter.new()
self.update_code_edit()
func _input(event):
if event.is_action_pressed("apply_shader"):
_on_apply_shader_button_pressed()
elif event.is_action_pressed("save_shader"):
accept_event() # Event is now handled.
_on_save_shader_button_pressed()
func update_code_edit():
code_editor.text = Filesystem.shader_code
enum Status {OKAY, ERROR, UNKNOWN = -1}
func update_status(status: Status, msg: String = ""):
error_msg_dialog.dialog_text = msg
error_msg_dialog.reset_size()
if status == Status.OKAY:
status_indicator.texture_normal = status_okay_texture
elif status == Status.ERROR:
status_indicator.texture_normal = status_error_texture
else:
status_indicator.texture_normal = null
if msg == "":
status_indicator.disabled = true
else:
status_indicator.disabled = false
#
func _on_new_shader_button_pressed():
main.update_title()
Filesystem.reset()
self.update_code_edit()
compositor.update()
update_status(Status.UNKNOWN)
func _on_open_shader_button_pressed():
open_shader_dialog.show()
func _on_save_shader_button_pressed():
Filesystem.shader_code = code_editor.text
if Filesystem.last_shader_savepath == "":
_on_save_shader_as_button_pressed()
else:
_on_save_shader_dialog_file_selected(Filesystem.last_shader_savepath)
func _on_save_shader_as_button_pressed() -> void:
Filesystem.shader_code = code_editor.text
if Filesystem.last_shader_savepath == "":
save_shader_dialog.current_file = "filter.gdshader"
else:
save_shader_dialog.current_path = Filesystem.last_shader_savepath
save_shader_dialog.show()
func _on_fit_image_button_pressed():
camera.fit_image()
func _on_apply_shader_button_pressed():
save_image_button.disabled = true
Filesystem.shader_code = code_editor.text
var errors = await compositor.update()
if len(errors) > 0:
update_status(Status.ERROR, "\n".join(errors))
else:
update_status(Status.OKAY)
save_image_button.disabled = false
func _on_save_image_button_pressed():
if Filesystem.result != null:
ui_control_filesave.current_path = Filesystem.last_image_savepath
ui_control_filesave.show()
#
func _on_open_shader_dialog_file_selected(path: String):
Filesystem.load_shader(path)
main.update_title(path.split("/")[-1])
self.update_code_edit()
self._on_apply_shader_button_pressed()
func _on_save_shader_dialog_file_selected(path):
Filesystem.save_shader(path)
main.update_title(path.split("/")[-1])
func _on_save_image_dialog_file_selected(path):
Filesystem.save_result(path)
#
func _on_status_indicator_pressed() -> void:
error_msg_dialog.show()

View file

@ -1,25 +1,34 @@
extends Node extends Node
@onready var template_shader: Shader = load("res://src/shader/template.gdshader") var cwd = "."
@onready var shader_code: String = template_shader.code
var shader_path = "":
get():
return shader_path
set(v):
var old = shader_path
shader_path = v
if "/" in v: # update current working directory
cwd = v.substr(0, v.rfind("/"))
if old != shader_path:
store_last_opened_file()
var shader_code: String:
get():
print("Load ", shader_path)
var file = FileAccess.open(shader_path, FileAccess.READ)
if file != null:
return file.get_as_text()
else: return ""
var original_image: ImageTexture var original_image: ImageTexture
var additional_images: Dictionary var additional_images: Dictionary
var result: Image var result: Image
var cwd = "."
var last_image_savepath = "" var last_image_savepath = ""
var last_shader_savepath = ""
var last_original_image_path = "" var last_original_image_path = ""
func reset():
self.shader_code = self.template_shader.code
self.last_image_savepath = ""
self.last_shader_savepath = ""
self.last_original_image_path = ""
self.original_image = null
self.result = null
func get_absolute_path(p: String) -> String: func get_absolute_path(p: String) -> String:
# this only works on Linux! # this only works on Linux!
if !p.begins_with("/"): if !p.begins_with("/"):
@ -27,14 +36,14 @@ func get_absolute_path(p: String) -> String:
return p return p
func load_original_image(path: String) -> String: # returns an error message func load_original_image(path: String) -> String: # returns an error message
print("Load ", path)
var img = Image.new() var img = Image.new()
var err = img.load(path) var err = img.load(path)
if err == OK: if err == OK:
original_image = ImageTexture.create_from_image(img) original_image = ImageTexture.create_from_image(img)
if path != self.last_original_image_path: if self.last_image_savepath == "" or path != self.last_original_image_path:
self.last_original_image_path = path
if self.last_image_savepath == "":
self.last_image_savepath = path self.last_image_savepath = path
self.last_original_image_path = path
return "" return ""
return error_string(err) + " " + path return error_string(err) + " " + path
@ -42,6 +51,7 @@ func clear_additional_images():
additional_images.clear() additional_images.clear()
func load_additional_image(key: String, path: String) -> String: # returns Error Message String func load_additional_image(key: String, path: String) -> String: # returns Error Message String
print("Load ", path)
var img = Image.new() var img = Image.new()
var err = img.load(path) var err = img.load(path)
if err == OK: if err == OK:
@ -58,33 +68,13 @@ func save_result(path: String):
else: else:
self.last_image_savepath = path self.last_image_savepath = path
func load_shader(path: String):
print("Load ", path)
var file = FileAccess.open(path, FileAccess.READ)
if file != null:
self.shader_code = file.get_as_text()
if "/" in path: # update current working directory
self.cwd = path.substr(0, path.rfind("/"))
self.last_shader_savepath = path
store_last_opened_file()
func save_shader(path: String):
print("Save ", path)
var file = FileAccess.open(path, FileAccess.WRITE)
file.store_string(self.shader_code)
file.flush()
if "/" in path: # update current working directory
self.cwd = path.substr(0, path.rfind("/"))
self.last_shader_savepath = path
store_last_opened_file()
func store_last_opened_file(): func store_last_opened_file():
var f = FileAccess.open("user://last_opened", FileAccess.WRITE) var f = FileAccess.open("user://last_opened", FileAccess.WRITE)
if f != null: if f != null:
f.store_pascal_string(last_shader_savepath) f.store_pascal_string(shader_path)
f.flush() f.flush()
func remember_last_opened_file(): func remember_last_opened_file():
var f = FileAccess.open("user://last_opened", FileAccess.READ) var f = FileAccess.open("user://last_opened", FileAccess.READ)
if f != null: if f != null:
last_shader_savepath = f.get_pascal_string() shader_path = f.get_pascal_string()

View file

@ -45,8 +45,11 @@ func inject_step_uniform(shader_code: String) -> Shader:
return shader return shader
func update(overwrite_image_path: String = "") -> Array: # returns error messages (strings) func update(overwrite_image_path: String = "") -> Array: # returns error messages (strings)
var original_shader_code = Filesystem.shader_code # read from disk
if original_shader_code == "":
return ["No shader loaded!"]
# inject STEP uniform & get number of steps # inject STEP uniform & get number of steps
var shader: Shader = inject_step_uniform(Filesystem.shader_code) var shader: Shader = inject_step_uniform(original_shader_code)
var steps: int = ShaderDirectiveParser.parse_steps_directive(shader.code) var steps: int = ShaderDirectiveParser.parse_steps_directive(shader.code)
# validate shader # validate shader
if not validate_shader_compilation(shader): if not validate_shader_compilation(shader):

View file

@ -4,8 +4,6 @@ const BATCH_MODE_SUPPORTED_EXTS = [
".bmp", ".dds", ".exr", ".hdr", ".jpeg", ".jpg", ".ktx", ".png", ".svg", ".webp" ".bmp", ".dds", ".exr", ".hdr", ".jpeg", ".jpg", ".ktx", ".png", ".svg", ".webp"
] ]
@onready var editor_window = %EditorWindow
@onready var ui_container = %UserInterfaceContainer
@onready var app_name = ProjectSettings.get_setting("application/config/name") @onready var app_name = ProjectSettings.get_setting("application/config/name")
func _ready(): func _ready():
@ -139,22 +137,12 @@ func cli_dump_shaderlib():
func prepare_gui(): func prepare_gui():
update_title() update_title()
# position windows
get_window().position = Vector2i(
editor_window.position.x + editor_window.size.x + 50,
editor_window.position.y)
get_window().min_size = Vector2i(400, 400)
editor_window.min_size = Vector2i(560, 400)
editor_window.show()
# Load last opened file # Load last opened file
Filesystem.remember_last_opened_file() Filesystem.remember_last_opened_file()
if Filesystem.last_shader_savepath != "": %MainUI._on_apply_shader_button_pressed()
ui_container.get_node("Editor")._on_open_shader_dialog_file_selected(Filesystem.last_shader_savepath)
func update_title(current_file: String = ""): func update_title(current_file: String = ""):
if current_file == "": if current_file == "":
get_window().title = app_name + " - Viewer" get_window().title = app_name + " - Viewer"
editor_window.title = app_name + " - Editor"
else: else:
get_window().title = current_file + " - " + app_name + " - Viewer" get_window().title = current_file + " - " + app_name + " - Viewer"
editor_window.title = current_file + " - " + app_name + " - Editor"

99
src/MainUI.gd Normal file
View file

@ -0,0 +1,99 @@
extends Control
@onready var open_shader_dialog = %OpenShaderDialog
@onready var save_image_dialog = %SaveImageDialog
@onready var open_shader_button = %OpenShaderButton
@onready var save_image_button = %SaveImageButton
@onready var fit_image_button = %FitImageButton
@onready var apply_shader_button = %ApplyShaderButton
@onready var status_indicator = %StatusIndicator
@onready var error_msg_dialog = %ErrorMessageDialog
@onready var main = get_tree().root.get_node("Main")
@onready var compositor = %Compositor
@onready var camera = %Camera
var status_okay_texture: CompressedTexture2D = preload("uid://m1omb6g45vst")
var status_error_texture: CompressedTexture2D = preload("uid://04iv1gogpuhu")
enum Status {OKAY, ERROR, UNKNOWN = -1}
#
func _input(event):
if event.is_action_pressed("apply_shader"):
_on_apply_shader_button_pressed()
elif event.is_action_pressed("save_shader"):
accept_event() # Event is now handled.
#
func set_buttons_disabled(disabled: bool):
for b in [open_shader_button, save_image_button, fit_image_button, apply_shader_button, status_indicator]:
b.disabled = disabled
func _on_open_shader_button_pressed():
set_buttons_disabled(true)
open_shader_dialog.show()
func _on_fit_image_button_pressed():
camera.fit_image()
func _on_apply_shader_button_pressed():
set_buttons_disabled(true)
var errors = await compositor.update()
set_buttons_disabled(false)
if len(errors) > 0:
update_status(Status.ERROR, "\n".join(errors))
else:
update_status(Status.OKAY)
status_indicator.disabled = true
func _on_save_image_button_pressed():
if Filesystem.result != null:
set_buttons_disabled(true)
save_image_dialog.current_path = Filesystem.last_image_savepath
save_image_dialog.show()
#
func _on_open_shader_dialog_file_selected(path: String):
Filesystem.shader_path = path
main.update_title(path.split("/")[-1])
self._on_apply_shader_button_pressed()
func _on_open_shader_dialog_canceled() -> void:
set_buttons_disabled(false)
func _on_open_shader_dialog_confirmed() -> void:
set_buttons_disabled(false)
func _on_save_image_dialog_file_selected(path):
Filesystem.save_result(path)
func _on_save_image_dialog_canceled() -> void:
set_buttons_disabled(false)
func _on_save_image_dialog_confirmed() -> void:
set_buttons_disabled(false)
#
func update_status(status: Status, msg: String = ""):
error_msg_dialog.dialog_text = msg
error_msg_dialog.reset_size()
if status == Status.OKAY:
status_indicator.texture_normal = status_okay_texture
elif status == Status.ERROR:
status_indicator.texture_normal = status_error_texture
else:
status_indicator.texture_normal = null
if msg == "":
status_indicator.disabled = true
else:
status_indicator.disabled = false
func _on_status_indicator_pressed() -> void:
error_msg_dialog.show()

View file

@ -1,4 +0,0 @@
extends Label
func _ready():
text = ProjectSettings.get_setting("application/config/version")

View file

@ -1,4 +0,0 @@
extends Window
func _on_close_requested() -> void:
get_tree().quit()

8
src/VersionLabel.gd Normal file
View file

@ -0,0 +1,8 @@
extends Label
func _ready():
text = ProjectSettings.get_setting("application/config/name") \
+ " " \
+ ProjectSettings.get_setting("application/config/version") \
+ " | Godot " \
+ Engine.get_version_info()["string"]

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

View file

@ -3,15 +3,15 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://04iv1gogpuhu" uid="uid://04iv1gogpuhu"
path="res://.godot/imported/error.svg-75fe5f417585e01e99de8885f1f45c3b.ctex" path="res://.godot/imported/error.svg-28fb29635cf59d39cabf7052619f602f.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://assets/error.svg" source_file="res://src/assets/error.svg"
dest_files=["res://.godot/imported/error.svg-75fe5f417585e01e99de8885f1f45c3b.ctex"] dest_files=["res://.godot/imported/error.svg-28fb29635cf59d39cabf7052619f602f.ctex"]
[params] [params]

View file

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 3 KiB

Before After
Before After

View file

@ -3,15 +3,15 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://kqwc4avs2xdp" uid="uid://kqwc4avs2xdp"
path="res://.godot/imported/icon.png-b6a7fb2db36edd3d95dc42f1dc8c1c5d.ctex" path="res://.godot/imported/icon.png-d8298ab6eda392a806be6bb7eec65b9c.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://assets/icon.png" source_file="res://src/assets/icon.png"
dest_files=["res://.godot/imported/icon.png-b6a7fb2db36edd3d95dc42f1dc8c1c5d.ctex"] dest_files=["res://.godot/imported/icon.png-d8298ab6eda392a806be6bb7eec65b9c.ctex"]
[params] [params]

View file

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Before After
Before After

View file

@ -3,15 +3,15 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://m1omb6g45vst" uid="uid://m1omb6g45vst"
path="res://.godot/imported/okay.svg-7f6df15523471a86f27b6817e9528a4e.ctex" path="res://.godot/imported/okay.svg-de66a022ef37753b085371b7c60aefd1.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://assets/okay.svg" source_file="res://src/assets/okay.svg"
dest_files=["res://.godot/imported/okay.svg-7f6df15523471a86f27b6817e9528a4e.ctex"] dest_files=["res://.godot/imported/okay.svg-de66a022ef37753b085371b7c60aefd1.ctex"]
[params] [params]

174
src/scenes/main.tscn Normal file
View file

@ -0,0 +1,174 @@
[gd_scene load_steps=12 format=3 uid="uid://bjah7k4bxo044"]
[ext_resource type="Script" path="res://src/Main.gd" id="1_64y3g"]
[ext_resource type="Script" path="res://src/ImageCompositor.gd" id="2_4ykh7"]
[ext_resource type="Shader" path="res://src/shader/ivd_outline.gdshader" id="3_0fllm"]
[ext_resource type="Script" path="res://src/ImageViewportDisplay.gd" id="4_pbpx2"]
[ext_resource type="Script" path="res://src/Camera.gd" id="5_hkdq6"]
[ext_resource type="Theme" uid="uid://cwqlns34rj3vx" path="res://src/theme.tres" id="6_rjp5f"]
[ext_resource type="Script" path="res://src/MainUI.gd" id="7_5puhk"]
[ext_resource type="Script" path="res://src/VersionLabel.gd" id="8_kod8x"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_y2ea0"]
shader = ExtResource("3_0fllm")
shader_parameter/zoom_level = Vector2(1, 1)
[sub_resource type="ViewportTexture" id="ViewportTexture_lct1c"]
viewport_path = NodePath("Compositor")
[sub_resource type="LabelSettings" id="LabelSettings_6o860"]
font_size = 12
font_color = Color(1, 1, 1, 0.509804)
shadow_color = Color(0, 0, 0, 1)
[node name="Main" type="Node2D"]
script = ExtResource("1_64y3g")
[node name="Compositor" type="SubViewport" parent="."]
unique_name_in_owner = true
script = ExtResource("2_4ykh7")
[node name="ImageViewportDisplay" type="Sprite2D" parent="."]
unique_name_in_owner = true
material = SubResource("ShaderMaterial_y2ea0")
texture = SubResource("ViewportTexture_lct1c")
script = ExtResource("4_pbpx2")
[node name="Camera" type="Camera2D" parent="."]
unique_name_in_owner = true
offset = Vector2(0, -64)
script = ExtResource("5_hkdq6")
[node name="CanvasLayer" type="CanvasLayer" parent="."]
[node name="MainUI" type="Control" parent="CanvasLayer"]
unique_name_in_owner = true
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
pivot_offset = Vector2(320, 320)
theme = ExtResource("6_rjp5f")
script = ExtResource("7_5puhk")
[node name="OpenShaderDialog" type="FileDialog" parent="CanvasLayer/MainUI"]
unique_name_in_owner = true
auto_translate_mode = 1
title = "Load Shader"
size = Vector2i(521, 175)
ok_button_text = "Open"
mode_overrides_title = false
file_mode = 0
access = 2
filters = PackedStringArray("*.gdshader")
use_native_dialog = true
[node name="SaveImageDialog" type="FileDialog" parent="CanvasLayer/MainUI"]
unique_name_in_owner = true
auto_translate_mode = 1
title = "Export Image"
size = Vector2i(661, 175)
ok_button_text = "Save"
mode_overrides_title = false
access = 2
filters = PackedStringArray("*.png")
use_native_dialog = true
[node name="ErrorMessageDialog" type="AcceptDialog" parent="CanvasLayer/MainUI"]
unique_name_in_owner = true
auto_translate_mode = 1
title = "Status"
initial_position = 2
size = Vector2i(256, 128)
popup_window = true
ok_button_text = "Close"
[node name="OpenShaderButton" type="Button" parent="CanvasLayer/MainUI"]
unique_name_in_owner = true
layout_mode = 1
offset_left = 16.0
offset_top = 16.0
offset_right = 128.0
offset_bottom = 48.0
text = "Open Shader"
[node name="SaveImageButton" type="Button" parent="CanvasLayer/MainUI"]
unique_name_in_owner = true
layout_mode = 1
offset_left = 144.0
offset_top = 16.0
offset_right = 216.0
offset_bottom = 48.0
disabled = true
text = "Export"
[node name="FitImageButton" type="Button" parent="CanvasLayer/MainUI"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -176.0
offset_top = 16.0
offset_right = -128.0
offset_bottom = 48.0
grow_horizontal = 0
text = "Fit"
[node name="ApplyShaderButton" type="Button" parent="CanvasLayer/MainUI"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -112.0
offset_top = 16.0
offset_right = -16.0
offset_bottom = 48.0
grow_horizontal = 0
text = "Apply (F5)"
[node name="StatusIndicator" type="TextureButton" parent="CanvasLayer/MainUI"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -220.0
offset_top = 21.0
offset_right = -196.0
offset_bottom = 45.0
grow_horizontal = 0
disabled = true
ignore_texture_size = true
stretch_mode = 0
[node name="VersionLabel" type="Label" parent="CanvasLayer/MainUI"]
layout_mode = 1
anchors_preset = 12
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 16.0
offset_top = -24.0
offset_right = -16.0
grow_horizontal = 2
grow_vertical = 0
label_settings = SubResource("LabelSettings_6o860")
horizontal_alignment = 1
vertical_alignment = 1
script = ExtResource("8_kod8x")
[connection signal="canceled" from="CanvasLayer/MainUI/OpenShaderDialog" to="CanvasLayer/MainUI" method="_on_open_shader_dialog_canceled"]
[connection signal="confirmed" from="CanvasLayer/MainUI/OpenShaderDialog" to="CanvasLayer/MainUI" method="_on_open_shader_dialog_confirmed"]
[connection signal="file_selected" from="CanvasLayer/MainUI/OpenShaderDialog" to="CanvasLayer/MainUI" method="_on_open_shader_dialog_file_selected"]
[connection signal="canceled" from="CanvasLayer/MainUI/SaveImageDialog" to="CanvasLayer/MainUI" method="_on_save_image_dialog_canceled"]
[connection signal="confirmed" from="CanvasLayer/MainUI/SaveImageDialog" to="CanvasLayer/MainUI" method="_on_save_image_dialog_confirmed"]
[connection signal="file_selected" from="CanvasLayer/MainUI/SaveImageDialog" to="CanvasLayer/MainUI" method="_on_save_image_dialog_file_selected"]
[connection signal="pressed" from="CanvasLayer/MainUI/OpenShaderButton" to="CanvasLayer/MainUI" method="_on_open_shader_button_pressed"]
[connection signal="pressed" from="CanvasLayer/MainUI/SaveImageButton" to="CanvasLayer/MainUI" method="_on_save_image_button_pressed"]
[connection signal="pressed" from="CanvasLayer/MainUI/FitImageButton" to="CanvasLayer/MainUI" method="_on_fit_image_button_pressed"]
[connection signal="pressed" from="CanvasLayer/MainUI/ApplyShaderButton" to="CanvasLayer/MainUI" method="_on_apply_shader_button_pressed"]
[connection signal="pressed" from="CanvasLayer/MainUI/StatusIndicator" to="CanvasLayer/MainUI" method="_on_status_indicator_pressed"]

View file

@ -1,7 +0,0 @@
shader_type canvas_item;
//!load /path/to/your/image
void fragment() {
// Called for every pixel the material is visible on.
}

57
src/theme.tres Normal file
View file

@ -0,0 +1,57 @@
[gd_resource type="Theme" load_steps=4 format=3 uid="uid://cwqlns34rj3vx"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bm5o2"]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
bg_color = Color(0.1, 0.1, 0.1, 0.3)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color(1, 1, 1, 0.27451)
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
corner_detail = 5
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_l0k8a"]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
bg_color = Color(0.225, 0.225, 0.225, 0.6)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color(1, 1, 1, 0.784314)
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
corner_detail = 5
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1dkyv"]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
bg_color = Color(0.1, 0.1, 0.1, 0.6)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color(1, 1, 1, 0.509804)
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
corner_detail = 5
[resource]
Button/styles/disabled = SubResource("StyleBoxFlat_bm5o2")
Button/styles/hover = SubResource("StyleBoxFlat_l0k8a")
Button/styles/normal = SubResource("StyleBoxFlat_1dkyv")