diff --git a/Console.tscn b/Console.tscn index 96cb19c..8198605 100644 --- a/Console.tscn +++ b/Console.tscn @@ -93,5 +93,5 @@ theme_override_fonts/font = ExtResource("4_jqfsc") theme_override_font_sizes/font_size = 18 placeholder_text = "Enter a command here" -[connection signal="pressed" from="Bar/CloseButton" to="." method="close_console"] +[connection signal="pressed" from="Bar/CloseButton" to="." method="toggle_console"] [connection signal="text_changed" from="Shell/Input" to="." method="input_changed"] diff --git a/Loader.tscn b/Loader.tscn index 360ce55..2e7ab2f 100644 --- a/Loader.tscn +++ b/Loader.tscn @@ -8,43 +8,3 @@ offset_bottom = 1440.0 size_flags_horizontal = 4 color = Color(0, 0, 0, 1) script = ExtResource("1_dpffy") - -[node name="WindowSizeSupport" type="Control" parent="."] -anchors_preset = 0 -offset_right = 40.0 -offset_bottom = 40.0 - -[node name="EndPixel" type="ColorRect" parent="WindowSizeSupport"] -layout_mode = 0 -offset_left = 1920.0 -offset_top = 1080.0 -offset_right = 1921.0 -offset_bottom = 1081.0 - -[node name="1920+" type="ColorRect" parent="WindowSizeSupport"] -layout_mode = 0 -offset_right = 9999.0 -offset_bottom = 30.0 - -[node name="1920" type="ColorRect" parent="WindowSizeSupport"] -layout_mode = 0 -offset_right = 1920.0 -offset_bottom = 30.0 -color = Color(0, 0, 1, 1) - -[node name="1441" type="ColorRect" parent="WindowSizeSupport"] -layout_mode = 0 -offset_right = 30.0 -offset_bottom = 9999.0 - -[node name="1440" type="ColorRect" parent="WindowSizeSupport"] -layout_mode = 0 -offset_right = 30.0 -offset_bottom = 1440.0 -color = Color(1, 1, 0, 1) - -[node name="1080" type="ColorRect" parent="WindowSizeSupport"] -layout_mode = 0 -offset_right = 30.0 -offset_bottom = 1080.0 -color = Color(1, 0, 1, 1) diff --git a/docs/docs/reference/api/loader.md b/docs/docs/reference/api/loader.md new file mode 100644 index 0000000..d360b12 --- /dev/null +++ b/docs/docs/reference/api/loader.md @@ -0,0 +1,32 @@ +--- +sidebar_position: 1 +--- + +# Loader +Initializes Presencode and holds the viewport while presenting. + +## Enums +### `VersionType` +- values + - `RELEASE` + - `RELEASECANDIDATE` + - `BETA` + - `ALPHA` +## Constants +### `version_release` +- type `int` +- description `Contains the current release number (e.g. 1)` +### `version_type` +- type `VersionType` +- description `Contains the current release type (e.g. VersionType.BETA)` +### `version_typerelease` +- type `int` +- description `Contains the current release type version (e.g. 3)` +### `format_version` +- return type `String` +- description `Replaces the variables %release%, %type_raw%, %type%, %type_short%, %type_technical% and %typerelease%.` +- arguments + - `format` + - type `string` + - mandatory `yes` + - description `The string that you want to format` diff --git a/docs/docs/reference/api/logger.md b/docs/docs/reference/api/logger.md index 68e2f0e..d3b3156 100644 --- a/docs/docs/reference/api/logger.md +++ b/docs/docs/reference/api/logger.md @@ -1,5 +1,5 @@ --- -sidebar_position: 1 +sidebar_position: 2 --- # Logger diff --git a/docs/docs/reference/api/misc.md b/docs/docs/reference/api/misc.md index 1031f69..2d182c6 100644 --- a/docs/docs/reference/api/misc.md +++ b/docs/docs/reference/api/misc.md @@ -1,27 +1,45 @@ --- -sidebar_position: 2 +sidebar_position: 3 --- # Miscellaneous (misc) Miscellaneous functions that do not fit into other scripts. +## Enums +### `BooleanState` +- values + - `TRUE` + - `FALSE` + - `INVALID` ## Variables ### `config_shutdown_invisible` - type `bool` - description `Hides the main window on shutdown if true, displays a white texture on shutdown if false` ## Functions -### `shutdown` -- return type `void` -- description `Registers a new presentation controller` +### `get_bool` +- return type `BooleanState` +- description `Retrieves a bool from a String` - arguments - - `exitcode` - - type `int` - - mandatory `no` - - description `DUDE DO YOU DON'T KNOW WHAT A EXITCODE IS?!` -### `get_temporary_dir` -- return type `String` -- description `Return a temporary directory` -- arguments `none` + - `string` + - type `String` + - mandatory `yes` + - description `The string that you want to convert into a bool` +### `get_int` +- return type `Vector2i` +- description `Retrieves the next valid integer from an Array. Returns Vector2i(0, NUMBER) if successful, Vector2i(1, 0) if not` +- arguments + - `array` + - type `Array` + - mandatory `yes` + - description `The array that you want to get an integer out of` +### `get_int_direct` +- return type `Vector2i` +- description `Converts a String into an int. Returns Vector2i(0, NUMBER) if successful, Vector2i(1, 0) if not` +- arguments + - `string` + - type `String` + - mandatory `yes` + - description `The string that you want to convert to an integer` ### `get_center` - return type `Vector2i` - description `Calculates the center of a child inside its parent (Vector2i edition)` @@ -46,18 +64,28 @@ Miscellaneous functions that do not fit into other scripts. - type `Vector2` - mandatory `yes` - description `The size of the child object` -### `clear_viewport` -- return type `void` -- description `Clears the presentation viewport` -- arguments `none` -### `hide_log` -- return type `void` -- description `Hides log output` -- arguments `none` -### `show_log` -- return type `void` -- description `Unhides log output` +### `get_shortened_array` +- return type `Array` +- description `Removes n items from an array (starting from the beginning)` +- arguments + - `array` + - type `Array` + - mandatory `yes` + - description `The array that you want to shorten` + - `skip` + - type `int` + - mandatory `yes` + - description `The amount of items that you want to remove from the beginning.` + - warning `This function automatically adds 1 to the skip variable. To only skip one element, use 0 (for example)` +### `get_temporary_dir` +- return type `String` +- description `Return a temporary directory` - arguments `none` ### `shutdown` - return type `void` -- description `Ends the presentation and shuts Presencode down` \ No newline at end of file +- description `Registers a new presentation controller` +- arguments + - `exitcode` + - type `int` + - mandatory `no` + - description `DUDE DO YOU DON'T KNOW WHAT A EXITCODE IS?!` diff --git a/docs/docs/reference/api/pmana.md b/docs/docs/reference/api/pmana.md index e621203..81caddf 100644 --- a/docs/docs/reference/api/pmana.md +++ b/docs/docs/reference/api/pmana.md @@ -1,5 +1,5 @@ --- -sidebar_position: 3 +sidebar_position: 4 --- # Presentation Manager (pmana) diff --git a/docs/docs/reference/api/preader.md b/docs/docs/reference/api/preader.md index 9d2c330..32d7ff6 100644 --- a/docs/docs/reference/api/preader.md +++ b/docs/docs/reference/api/preader.md @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 5 --- # Presentation Reader (preader) diff --git a/export_presets.cfg b/export_presets.cfg index e3e64d2..b50698f 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -18,7 +18,7 @@ encrypt_directory=false custom_template/debug="" custom_template/release="" -debug/export_console_wrapper=1 +debug/export_console_wrapper=2 binary_format/embed_pck=true texture_format/bptc=true texture_format/s3tc=true @@ -81,7 +81,7 @@ encrypt_directory=false custom_template/debug="" custom_template/release="" -debug/export_console_wrapper=1 +debug/export_console_wrapper=2 binary_format/embed_pck=true texture_format/bptc=true texture_format/s3tc=true diff --git a/project.godot b/project.godot index 808da6a..fbec78b 100644 --- a/project.godot +++ b/project.godot @@ -26,6 +26,7 @@ misc="*res://src/misc.gd" preader="*res://src/preader.gd" pmana="*res://src/pmana.gd" ui_engine="*res://src/ui_engine.gd" +processor="*res://src/processor.gd" [display] @@ -73,8 +74,8 @@ content_scale_switch={ } console={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":0,"key_label":96,"unicode":0,"echo":false,"script":null) -, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":96,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":0,"key_label":4194334,"unicode":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194334,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) ] } editor_switchres={ diff --git a/src/clickoverlay.gd b/src/clickoverlay.gd index 47d6ba4..936d76a 100644 --- a/src/clickoverlay.gd +++ b/src/clickoverlay.gd @@ -23,9 +23,10 @@ ############################################################################## extends Button -# Setup clickoverlay +# Initialize clickoverlay func _ready() -> void: - # Set properties + logger.diag("Initializing ClickOverlay") + # Set button properties mouse_filter = Control.MOUSE_FILTER_STOP position = Vector2i(0, 0) # Hide button @@ -34,17 +35,13 @@ func _ready() -> void: add_theme_stylebox_override("pressed", StyleBoxEmpty.new()) add_theme_stylebox_override("disabled", StyleBoxEmpty.new()) add_theme_stylebox_override("focus", StyleBoxEmpty.new()) - # Connect "pressed" signal to function "clicked" + # Connect pressed signal connect("pressed", Callable(self, "clicked")) # Simple fix for a bug await get_tree().process_frame clicked() +# Clicked event func clicked() -> void: - # Increase current_slide by one logger.info("Navigating one slide forwards (click)") pmana.change_slide(pmana.current_slide+1, true) - -func _process(_delta: float) -> void: - # Scale clickoverlay to window size - size = DisplayServer.window_get_size() diff --git a/src/console.gd b/src/console.gd index 7528f8d..2c96f5b 100644 --- a/src/console.gd +++ b/src/console.gd @@ -23,13 +23,6 @@ ############################################################################## extends ColorRect -# Enums -enum BooleanState { - TRUE, - FALSE, - INVALID -} - # Nodes @onready var input: TextEdit = get_node("Shell/Input") @onready var output: RichTextLabel = get_node("Shell/Output") @@ -43,20 +36,24 @@ var dragging: bool = false var drag_area: Vector2 = Vector2(0, 0) var cursor_origin: Vector2 = Vector2(0, 0) +# Initialize debug console func _ready() -> void: logger.info("Initializing debug console") + # Set properties visible = false position = Vector2(30, 30) - calculate_drag_area() + # Calculate console drag area + processor.update_dragging_area(processor.DragNode.CONSOLE) # Setup window dragging - $Bar/DragButton.connect("button_down", func(): + $Bar/DragButton.connect("button_down", func() -> void: dragging = true cursor_origin = get_tree().root.get_viewport().get_mouse_position() ) - $Bar/DragButton.connect("button_up", func(): + $Bar/DragButton.connect("button_up", func() -> void: dragging = false cursor_origin = Vector2(0,0) ) + # Setup ExpressionScript (in which arbitrary expressions are run) expressionscript.name = "ExpressionScript" expressionscript.set_script(ResourceLoader.load("res://src/console_expressionscript.gd")) expressionscript.logger = get_node("/root/logger") @@ -67,60 +64,18 @@ func _ready() -> void: expressionscript.console = self expressionscript.loader = get_node("/root/Presencode") add_child(expressionscript) + # Reset console session await process_command(PackedStringArray(["exit"])) logger.info("Debug console initialized") -func _process(_delta: float) -> void: - # Move to top - get_tree().root.move_child(self, get_tree().root.get_child_count(true)) - # Visibility toggle key - if Input.is_action_just_pressed("console"): - logger.info("Toggling console visibility") - visible = !visible - if dragging: - # Get new cursor position - var cursor_position = get_tree().root.get_viewport().get_mouse_position() - # If nothing changed, don't do anything - if cursor_origin != cursor_position: - # Calculate cursor offset - var cursor_offset: Vector2 = Vector2(0, 0) - cursor_offset.x = cursor_origin.x-cursor_position.x - cursor_offset.y = cursor_origin.y-cursor_position.y - # Change console position - position.x = position.x-cursor_offset.x - position.y = position.y-cursor_offset.y - # Set new cursor_origin - cursor_origin = cursor_position - # Prevent console from going out of scope - var position_new: Vector2 = check_violation() - if position != position_new: - position = position_new - -# Helper function, prevents console from going out of scope -func check_violation() -> Vector2: - var new_position: Vector2 = position - if position.x <= 0: - new_position.x = 0 - elif position.x >= drag_area.x: - new_position.x = drag_area.x - if position.y <= 0: - new_position.y = 0 - elif position.y >= drag_area.y: - new_position.y = drag_area.y - return new_position - -# Calculates a new drag_area -func calculate_drag_area(area: Vector2i = DisplayServer.window_get_size()) -> void: - drag_area.x = area.x-size.x - drag_area.y = area.y-size.y - -# Close console on close button press -func close_console() -> void: - logger.info("Closing console") - visible = false +# Toggle console visibility +func toggle_console() -> void: + logger.info("Toggling console visibility") + visible = !visible # Input changed func input_changed() -> void: + # Check for newline/enter if input.text.contains("\n"): input.text = input.text.replace("\n", "") var input2: String = input.text @@ -128,13 +83,30 @@ func input_changed() -> void: input.editable = false await process_command(input2.split(" ", false)) input.editable = true + return + # Highlight wrong/correct arbitrary gdscript expressions + if input.text.begins_with("arbitrary ") and input.text != "arbitrary ": + var expression_raw: String = " ".join(PackedStringArray(misc.get_shortened_array(input.text.split(" "), 0))) + var expression: Expression = Expression.new() + if expression.parse(expression_raw) == Error.OK: input.add_theme_color_override("font_color", Color.LIGHT_GREEN) + else: input.add_theme_color_override("font_color", Color.LIGHT_CORAL) + else: input.add_theme_color_override("font_color", Color8(255, 255, 255, 255)) + +# Write to console output +func append_output(text: String, newline: bool = true) -> void: + if newline: output.text = output.text + text + "\n" + else: output.text = output.text + text # Process commands +## This function is completely undocumented, looking a bit through the function and +## reading the append_output() calls should give you a rough understanding though. func process_command(command: PackedStringArray) -> void: logger.diag("Processing command [" + " ".join(command) + "]") await get_tree().process_frame - append_output("[color=gray]$ " + " ".join(command) + "[color=white]") - if command.size() == 0: return + if command.size() == 0: + append_output("[color=gray]$ *nothing*[color=white]\nI don't think that entering nothing does anything. Try running [b]help[/b].") + return + else: append_output("[color=gray]$ " + " ".join(command) + "[color=white]") match(command[0]): "clear": match(command.size()): @@ -154,7 +126,7 @@ To get started, enter \"help\". To close the console, press the X button.[color= 1: exitcode = 0 2: exitcode = int(command[1]) _: append_output(info.get_error_string(ConsoleInfo.ConsoleError.TOO_MANY_ARGUMENTS)) - pmana.shutdown(exitcode) + misc.shutdown(exitcode) "help": match(command.size()): 1: append_output(info.get_help_topic(ConsoleInfo.HelpTopic.INDEX)) @@ -215,59 +187,59 @@ MISC_SHUTDOWN_INVISIBLE | bool | Toggles if the window should be made invisibl _: match(command[2]): "LOGGER_ENABLED": - match(get_boolean(" ".join(get_last(command, 2)))): - BooleanState.TRUE: + match(misc.get_bool(" ".join(misc.get_shortened_array(command, 2)))): + misc.BooleanState.TRUE: logger.config_enabled = true append_output("Set \"" + str(command[2]) + "\" to [b]true[/b].") - BooleanState.FALSE: + misc.BooleanState.FALSE: logger.config_enabled = false append_output("Set \"" + str(command[2]) + "\" to [b]false[/b]") _: append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_TYPE, {"expected_type": "bool"})) "LOGGER_DIAGNOSTIC": - match(get_boolean(" ".join(get_last(command, 2)))): - BooleanState.TRUE: + match(misc.get_bool(" ".join(misc.get_shortened_array(command, 2)))): + misc.BooleanState.TRUE: logger.config_diagnostic = true append_output("Set \"" + str(command[2]) + "\" to [b]true[/b]") - BooleanState.FALSE: + misc.BooleanState.FALSE: logger.config_diagnostic = false append_output("Set \"" + str(command[2]) + "\" to [b]false[/b]") _: append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_TYPE, {"expected_type": "bool"})) "LOGGER_COLORED": - match(get_boolean(" ".join(get_last(command, 2)))): - BooleanState.TRUE: + match(misc.get_bool(" ".join(misc.get_shortened_array(command, 2)))): + misc.BooleanState.TRUE: logger.config_colored = true append_output("Set \"" + str(command[2]) + "\" to [b]true[/b]") - BooleanState.FALSE: + misc.BooleanState.FALSE: logger.config_colored = false append_output("Set \"" + str(command[2]) + "\" to [b]false[/b]") _: append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_TYPE, {"expected_type": "bool"})) "LOGGER_HARDFAIL": - match(get_boolean(" ".join(get_last(command, 2)))): - BooleanState.TRUE: + match(misc.get_bool(" ".join(misc.get_shortened_array(command, 2)))): + misc.BooleanState.TRUE: logger.config_hardfail = true append_output("Set \"" + str(command[2]) + "\" to [b]true[/b]") - BooleanState.FALSE: + misc.BooleanState.FALSE: logger.config_hardfail = false append_output("Set \"" + str(command[2]) + "\" to [b]false[/b]") _: append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_TYPE, {"expected_type": "bool"})) "LOGGER_LOGSTRING": - logger.config_logstring = " ".join(PackedStringArray(get_last(command, 2))) + logger.config_logstring = " ".join(PackedStringArray(misc.get_shortened_array(command, 2))) append_output("Set \"" + str(command[2]) + "\" to [b]\"" + logger.config_logstring + "\"[/b]") "PMANA_ALLOW_FULLSCREEN": - match(get_boolean(" ".join(get_last(command, 2)))): - BooleanState.TRUE: + match(misc.get_bool(" ".join(misc.get_shortened_array(command, 2)))): + misc.BooleanState.TRUE: pmana.config_allow_fullscreen = true append_output("Set \"" + str(command[2]) + "\" to [b]true[/b]") - BooleanState.FALSE: + misc.BooleanState.FALSE: pmana.config_allow_fullscreen = false append_output("Set \"" + str(command[2]) + "\" to [b]false[/b]") _: append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_TYPE, {"expected_type": "bool"})) "MISC_SHUTDOWN_INVISIBLE": - match(get_boolean(" ".join(get_last(command, 2)))): - BooleanState.TRUE: + match(misc.get_bool(" ".join(misc.get_shortened_array(command, 2)))): + misc.BooleanState.TRUE: misc.config_shutdown_invisible = true append_output("Set \"" + str(command[2]) + "\" to [b]true[/b]") - BooleanState.FALSE: + misc.BooleanState.FALSE: misc.config_shutdown_invisible = false append_output("Set \"" + str(command[2]) + "\" to [b]false[/b]") _: append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_TYPE, {"expected_type": "bool"})) @@ -301,11 +273,11 @@ MISC_SHUTDOWN_INVISIBLE | bool | Toggles if the window should be made invisibl 5: append_output(info.get_error_string(ConsoleInfo.ConsoleError.TOO_FEW_ARGUMENTS)) 6: append_output(info.get_error_string(ConsoleInfo.ConsoleError.TOO_FEW_ARGUMENTS)) 7: - var version: Vector2 = get_int_direct(command[2]) - var slides: Vector2 = get_int_direct(command[3]) - var animations: BooleanState = get_boolean(command[4]) + var version: Vector2 = misc.get_int_direct(command[2]) + var slides: Vector2 = misc.get_int_direct(command[3]) + var animations: misc.BooleanState = misc.get_bool(command[4]) var animations_bool: bool = false - var quit_last_slide: BooleanState = get_boolean(command[5]) + var quit_last_slide: misc.BooleanState = misc.get_bool(command[5]) var quit_last_slide_bool: bool = false var controller: NodePath = NodePath(command[6]) if version.x != 0: @@ -315,14 +287,14 @@ MISC_SHUTDOWN_INVISIBLE | bool | Toggles if the window should be made invisibl append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_TYPE, {"expected_type": "int"})) return match(animations): - BooleanState.TRUE: animations_bool = true - BooleanState.FALSE: animations_bool = false + misc.BooleanState.TRUE: animations_bool = true + misc.BooleanState.FALSE: animations_bool = false _: append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_TYPE, {"expected_type": "bool"})) return match(quit_last_slide): - BooleanState.TRUE: quit_last_slide_bool = true - BooleanState.FALSE: quit_last_slide_bool = false + misc.BooleanState.TRUE: quit_last_slide_bool = true + misc.BooleanState.FALSE: quit_last_slide_bool = false _: append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_TYPE, {"expected_type": "bool"})) return @@ -346,31 +318,31 @@ MISC_SHUTDOWN_INVISIBLE | bool | Toggles if the window should be made invisibl 3: if !pmana.registered: append_output(info.get_error_string(ConsoleInfo.ConsoleError.NO_CONTROLLER_REGISTERED)) else: - var slide: Vector2i = get_int_direct(command[2]) + var slide: Vector2i = misc.get_int_direct(command[2]) match(slide.x): 0: pmana.change_slide(slide.y) append_output("Switched to slide [b]" + str(slide.y) + "[/b]") 1: append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_TYPE, {"expected_type": "int"})) - _: append_output(info.generate_internal_error("Invalid get_int() status number")) + _: append_output(info.generate_internal_error("Invalid misc.get_int() status number")) 4: if !pmana.registered: append_output(info.get_error_string(ConsoleInfo.ConsoleError.NO_CONTROLLER_REGISTERED)) else: - var slide: Vector2i = get_int_direct(command[2]) + var slide: Vector2i = misc.get_int_direct(command[2]) match(slide.x): 0: - var no_animations: BooleanState = get_boolean(command[3]) + var no_animations: misc.BooleanState = misc.get_bool(command[3]) match(no_animations): - BooleanState.TRUE: + misc.BooleanState.TRUE: pmana.change_slide(slide.y, true) append_output("Switched to slide [b]" + str(slide.y) + "[/b] without animations") - BooleanState.FALSE: + misc.BooleanState.FALSE: pmana.change_slide(slide.y, false) append_output("Switched to slide [b]" + str(slide.y) + "[/b]") - BooleanState.INVALID: + misc.BooleanState.INVALID: append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_TYPE, {"expected_type": "bool"})) 1: append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_TYPE, {"expected_type": "int"})) - _: append_output(info.generate_internal_error("Invalid get_int() status number")) + _: append_output(info.generate_internal_error("Invalid misc.get_int() status number")) _: append_output(info.get_error_string(ConsoleInfo.ConsoleError.TOO_MANY_ARGUMENTS)) "get_slide": if !pmana.registered: append_output(info.get_error_string(ConsoleInfo.ConsoleError.NO_CONTROLLER_REGISTERED)) @@ -423,7 +395,7 @@ MISC_SHUTDOWN_INVISIBLE | bool | Toggles if the window should be made invisibl match(command.size()): 1: append_output(info.get_error_string(ConsoleInfo.ConsoleError.TOO_FEW_ARGUMENTS)) _: - var expression_raw: String = " ".join(PackedStringArray(get_last(command, 0))) + var expression_raw: String = " ".join(PackedStringArray(misc.get_shortened_array(command, 0))) var expression: Expression = Expression.new() if expression.parse(expression_raw) != Error.OK: append_output(info.get_error_string(ConsoleInfo.ConsoleError.EXPRESSION_PARSING_FAILED, {"error": expression.get_error_text()})) @@ -435,48 +407,3 @@ MISC_SHUTDOWN_INVISIBLE | bool | Toggles if the window should be made invisibl append_output("""Executed arbitrary expression successfully. Returned: """ + str(returned)) _: append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_COMMAND)) - -# Helper function. I'm bad at explaining and naming things, just look at the code... please. -func get_last(array: Array, n: int) -> Array: - # Makes it easier - n = n+1 - # Create new array - var new_array: Array = [] - if n >= array.size(): - logger.error("n is bigger than array.size()") - return [] - for value in array: - if n == 0: # Append to new_array - new_array.append(value) - else: # Count down - n = n-1 - return new_array - -# Helper function. Tries to get a bool from an Array. -func get_boolean(string: String) -> BooleanState: - match(string): - "true": return BooleanState.TRUE - "false": return BooleanState.FALSE - _: return BooleanState.INVALID - -# Helper function. Tries to get a int from an Array. -## Returns Vector2i(0, NUMBER) if successful -## Returns Vector2i(1, 0) if not successful -func get_int(array: Array) -> Vector2i: - for value in array: - if str(value).is_valid_int(): return Vector2i(0, int(value)) - return Vector2i(1, 0) - -# Helper function. Tries to get a int from a String. -## Returns Vector2i(0, NUMBER) if successful -## Returns Vector2i(1, 0) if not successful -func get_int_direct(string: String) -> Vector2i: - if string.is_valid_int(): return Vector2i(0, int(string)) - else: return Vector2i(1, 0) - -# Helper function. Appends text to output.text -func append_output(text: String, newline: bool = true) -> void: - if newline: - output.text = output.text + text + "\n" - else: - output.text = output.text + text diff --git a/src/console_info.gd b/src/console_info.gd index f566e74..5af7f46 100644 --- a/src/console_info.gd +++ b/src/console_info.gd @@ -24,6 +24,7 @@ extends Node class_name ConsoleInfo +# Enums enum ConsoleError { OK, UNIMPLEMENTED, @@ -55,57 +56,38 @@ enum HelpTopic { ARBITRARY } +# Variables var error_color: String = "[color=red]" var internal_error_color: String = "[b][color=red]" -func _init() -> void: - logger.diag("ConsoleInfo initialized") - +# Generates an internal error func generate_internal_error(error: String) -> String: var origin = misc.get_origin() logger.warn("Debug console (" + origin["file"] + ":" + origin["function"] + ":" + str(origin["line"]) + ") experienced an internal error: " + error) return internal_error_color + "INTERNAL ERROR (" + error + ") [" + origin["file"] + ":" + origin["function"] + ":" + str(origin["line"]) + "]" +# Converts ConsoleError into a String func get_error_string(error: ConsoleError, context: Dictionary = {}) -> String: match(error): - ConsoleError.OK: - return generate_internal_error("ConsoleError.OK is not a valid error") - ConsoleError.UNIMPLEMENTED: - return error_color + "Function unimplemented, aborting." - ConsoleError.INVALID_COMMAND: - return error_color + "Invalid command. Execute \"help\" for a list of all available commands." - ConsoleError.TOO_MANY_ARGUMENTS: - return error_color + "Too many arguments." - ConsoleError.TOO_FEW_ARGUMENTS: - return error_color + "Too few arguments." - ConsoleError.INVALID_ARGUMENT: - return error_color + "Invalid argument." - ConsoleError.INVALID_TYPE: - return error_color + "Invalid type. Command expected type " + str(context["expected_type"]) + "." - ConsoleError.NOT_AN_OBJECT: - return error_color + "Invalid type. Command expected a valid NodePath." - ConsoleError.NO_SCRIPT_ATTACHED: - return error_color + "Invalid object. Command expected a script-attached object." - ConsoleError.INVALID_HELP_TOPIC: - return error_color + "Invalid help topic." - ConsoleError.INVALID_CONFIG_KEY: - return error_color + "Invalid config key \"" + str(context["key"]) + "\". Execute \"config list\" for a list of all config keys." - ConsoleError.NO_PRESENTATION_OPEN: - return error_color + "No presentation is currently open." - ConsoleError.CONTROLLER_ALREADY_REGISTERED: - return error_color + "A presentation controller has been registered already." - ConsoleError.NO_CONTROLLER_REGISTERED: - return error_color + "No presentation controller has been registered yet." - ConsoleError.EXPRESSION_PARSING_FAILED: - return error_color + """Could not parse arbitrary expression successfully. -Error: """ + str(context["error"]) - ConsoleError.EXPRESSION_EXECUTION_FAILED: - return error_color + """Could not execute arbitrary expression successfully. -Error: """ + str(context["error"]) + """ -Returned: """ + str(context["returned"]) - _: - return generate_internal_error("Invalid ConsoleError \"" + str(error) + "\", context: " + str(context)) + ConsoleError.OK: return generate_internal_error("ConsoleError.OK is not a valid error") + ConsoleError.UNIMPLEMENTED: return error_color + "Function unimplemented, aborting." + ConsoleError.INVALID_COMMAND: return error_color + "Invalid command. Execute \"help\" for a list of all available commands." + ConsoleError.TOO_MANY_ARGUMENTS: return error_color + "Too many arguments." + ConsoleError.TOO_FEW_ARGUMENTS: return error_color + "Too few arguments." + ConsoleError.INVALID_ARGUMENT: return error_color + "Invalid argument." + ConsoleError.INVALID_TYPE: return error_color + "Invalid type. Command expected type " + str(context["expected_type"]) + "." + ConsoleError.NOT_AN_OBJECT: return error_color + "Invalid type. Command expected a valid NodePath." + ConsoleError.NO_SCRIPT_ATTACHED: return error_color + "Invalid object. Command expected a script-attached object." + ConsoleError.INVALID_HELP_TOPIC: return error_color + "Invalid help topic." + ConsoleError.INVALID_CONFIG_KEY: return error_color + "Invalid config key \"" + str(context["key"]) + "\". Execute \"config list\" for a list of all config keys." + ConsoleError.NO_PRESENTATION_OPEN: return error_color + "No presentation is currently open." + ConsoleError.CONTROLLER_ALREADY_REGISTERED: return error_color + "A presentation controller has been registered already." + ConsoleError.NO_CONTROLLER_REGISTERED: return error_color + "No presentation controller has been registered yet." + ConsoleError.EXPRESSION_PARSING_FAILED: return error_color + "Could not parse arbitrary expression successfully.\nError: " + str(context["error"]) + ConsoleError.EXPRESSION_EXECUTION_FAILED: return error_color + "Could not execute arbitrary expression successfully.\nError: " + str(context["error"]) + "\nReturned: " + str(context["returned"]) + _: return generate_internal_error("Invalid ConsoleError \"" + str(error) + "\", context: " + str(context)) +# Converts HelpTopic into a String func get_help_topic(topic: HelpTopic) -> String: match(topic): HelpTopic.INDEX: @@ -158,5 +140,4 @@ Calls functions belonging to the Presentation Reader""" Executes arbitrary GDScript expressions. -> EXPERIMENTAL""" - _: - return generate_internal_error("Invalid HelpTopic \"" + str(topic) + "\"") + _: return generate_internal_error("Invalid HelpTopic \"" + str(topic) + "\"") diff --git a/src/loader.gd b/src/loader.gd index 3ab5456..eeaf519 100644 --- a/src/loader.gd +++ b/src/loader.gd @@ -23,21 +23,26 @@ ############################################################################## extends Control -# Loader configuration -## Window size support -### This is/was used for configuring scaling -var config_window_size_support: bool = false -## Skip malicious scripts warning if running as debug build -var config_skipwarning: bool = true -## Slow init -### I don't know why I put this here lol -var config_slow_init: bool = false +# Enums +enum VersionType { + RELEASE, + RELEASECANDIDATE, + BETA, + ALPHA +} + +# Constants +const version_release: int = 1 +const version_type: VersionType = VersionType.ALPHA +const version_typerelease: int = 2 # Nodes var logrtl: RichTextLabel = null var console: Control = null func _ready() -> void: + #while true: await get_tree().create_timer(0.05).timeout + misc.set_main_window_visibility(false) logger.info("Updating loader scene") # Rename loader scene name = "Presencode" @@ -62,7 +67,7 @@ func _ready() -> void: logrtl.add_theme_font_size_override("normal_font_size", 14) logrtl.add_theme_font_size_override("bold_font_size", 14) ## Connect to logger - logger.connect("log_event", Callable(self, "append_log")) + logger.connect("log_event", func(_type: logger.Types, _message: String, logstring: String) -> void: logrtl.text = get_node("/root/Presencode/Log").text + logstring + "\n") ## Add to SceneTree add_child(logrtl) ## Remove VScrollBar @@ -74,14 +79,6 @@ func _ready() -> void: vsbar.add_theme_stylebox_override("grabber", StyleBoxEmpty.new()) vsbar.add_theme_stylebox_override("grabber_highlight", StyleBoxEmpty.new()) vsbar.add_theme_stylebox_override("grabber_pressed", StyleBoxEmpty.new()) - ## Window size support - if config_window_size_support: - $WindowSizeSupport.modulate = Color8(255, 255, 255, 255) - else: - $WindowSizeSupport.modulate = Color8(255, 255, 255, 0) - ## Update process_mode - logger.process_mode = Node.PROCESS_MODE_ALWAYS - misc.process_mode = Node.PROCESS_MODE_ALWAYS ## Initialize Presencode initialize() @@ -98,24 +95,77 @@ func initialize() -> void: Copyright (c) 2024 JeremyStarTM & Contributers Licensed under the GNU General Public License version 3 """) - if config_slow_init: await get_tree().create_timer(randf_range(0.2, 0.6)).timeout # Check for platform misc.check_platform() # Create temporary directory DirAccess.make_dir_recursive_absolute(misc.get_temporary_dir()) + # Load debug console logger.info("Injecting console") console = ResourceLoader.load("res://Console.tscn").instantiate() get_tree().root.add_child.call_deferred(console) - if config_slow_init: await get_tree().create_timer(randf_range(0.1, 0.15)).timeout - # Check for presentation path in commandline arguments - # Open presentation archive/directory - var path: String = " ".join(OS.get_cmdline_user_args()) - if OS.get_cmdline_user_args().size() == 0: + # Initialize processor + processor.initialize.call_deferred() + # Process arguments + var path: String = parse_arguments() + # Update window properties + misc.set_main_window_visibility(true) + # Check path variable + if path == "": ui_engine.initialize(self) else: - await print_warning() await load_presentation(path) +# Process commandline arguments +func parse_arguments() -> String: + var path: String = "" + var next_argument_value: String = "" + for arg in OS.get_cmdline_user_args(): + match(next_argument_value): + "logger_enabled": + match(misc.get_bool(arg)): + misc.BooleanState.TRUE: logger.config_enabled = true + misc.BooleanState.FALSE: logger.config_enabled = false + _: logger.error("Value \"" + arg + "\" does not match \"true\" or \"false\"") + "logger_diagnostic": + match(misc.get_bool(arg)): + misc.BooleanState.TRUE: logger.config_diagnostic = true + misc.BooleanState.FALSE: logger.config_diagnostic = false + _: logger.error("Value \"" + arg + "\" does not match \"true\" or \"false\"") + "logger_colored": + match(misc.get_bool(arg)): + misc.BooleanState.TRUE: logger.config_colored = true + misc.BooleanState.FALSE: logger.config_colored = false + _: logger.error("Value \"" + arg + "\" does not match \"true\" or \"false\"") + "logger_hardfail": + match(misc.get_bool(arg)): + misc.BooleanState.TRUE: logger.config_hardfail = true + misc.BooleanState.FALSE: logger.config_hardfail = false + _: logger.error("Value \"" + arg + "\" does not match \"true\" or \"false\"") + "logger_logstring": logger.config_logstring = arg + "misc_shutdown_invisible": + match(misc.get_bool(arg)): + misc.BooleanState.TRUE: misc.config_shutdown_invisible = true + misc.BooleanState.FALSE: misc.config_shutdown_invisible = false + _: logger.error("Value \"" + arg + "\" does not match \"true\" or \"false\"") + if next_argument_value != "": + next_argument_value = "" + continue + match(arg): + "--help": continue + "--logger-enabled": next_argument_value = "logger_enabled" + "--logger-diagnostic": next_argument_value = "logger_diagnostic" + "--logger-colored": next_argument_value = "logger_colored" + "--logger-hardfail": next_argument_value = "logger_hardfail" + "--logger-logstring": next_argument_value = "logger_logstring" + "--shutdown-invisible": next_argument_value = "misc_shutdown_invisible" + _: + # doesn't match any of the above, append to path instead + ## this causes an interesting side effect, which is best explained by an example: + ## C:/Users/Example User/Documents/Important --shutdown-invisible false Documents/Example --logger-colored false Presentation.pcpa + if path == "": path = arg + else: path = path + " " + arg + return path + # Load a presentation from commandline arguments func load_presentation(path: String) -> void: if FileAccess.file_exists(path) and path.ends_with(".zip") or path.ends_with(".pcpa"): # .pcpa = presencode presentation archive @@ -126,81 +176,52 @@ func load_presentation(path: String) -> void: preader.open_presentation(path, false) else: await logger.error("Presentation file/directory \"" + path + "\" not found") - if config_slow_init: await get_tree().create_timer(randf_range(0.6, 1)).timeout + return # Read manifest & entrypoint files preader.read_manifest() preader.read_entrypoint() # Update window properties - console.calculate_drag_area(preader.get_ratio_resolution()) + processor.update_dragging_area(processor.DragNode.CONSOLE, preader.get_ratio_resolution()) DisplayServer.window_set_title("Presencode » Presenting \"" + preader.get_topic() + "\" by \"" + preader.get_authors() + "\"") DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) DisplayServer.window_set_size(preader.get_ratio_resolution()) DisplayServer.window_set_min_size(preader.get_ratio_resolution()) - if config_slow_init: await get_tree().create_timer(randf_range(0.1, 0.3)).timeout - if config_window_size_support: $WindowSizeSupport/EndPixel.position = Vector2(preader.get_ratio_resolution().x-1, preader.get_ratio_resolution().y-1) get_tree().root.content_scale_size = preader.get_ratio_resolution() get_tree().root.content_scale_mode = Window.CONTENT_SCALE_MODE_VIEWPORT get_tree().root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_KEEP get_tree().root.content_scale_factor = 1.0 await get_tree().process_frame DisplayServer.window_set_position(misc.get_center(DisplayServer.screen_get_size(), DisplayServer.window_get_size())) - if config_slow_init: await get_tree().create_timer(randf_range(1, 1.5)).timeout # Add entrypoint to SceneTree get_tree().root.add_child(preader.get_entrypoint()) -# seeeeeeeeeeeeelf explanitory -func _process(_delta: float) -> void: - # Move to top - get_tree().root.move_child(self, get_tree().root.get_child_count(true)-1) - # Change sizes - size = DisplayServer.window_get_size() - if typeof(logrtl) == TYPE_OBJECT: - logrtl.size = DisplayServer.window_get_size() - if config_window_size_support: move_child($WindowSizeSupport, get_child_count(true)) - # Fullscreen key combo - if pmana.config_allow_fullscreen and Input.is_action_just_pressed("fullscreen"): - if DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_WINDOWED: - # window is windowed, set to fullscreen mode - logger.info("Switched to fullscreen mode") - DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN) - else: - # window is in fullscreen (or something else) mode, set to windowed mode - logger.info("Switched to windowed mode") - DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) - # Content scale switch combo - if Input.is_action_just_pressed("content_scale_switch"): - match(get_tree().root.content_scale_stretch): - Window.CONTENT_SCALE_STRETCH_FRACTIONAL: - logger.info("Switching to fractional scaling") - get_tree().root.content_scale_stretch = Window.CONTENT_SCALE_STRETCH_INTEGER - Window.CONTENT_SCALE_STRETCH_INTEGER: - logger.info("Switching to integer scaling") - get_tree().root.content_scale_stretch = Window.CONTENT_SCALE_STRETCH_FRACTIONAL - _: - logger.error("Invalid content_scale_stretch value") - -# Append log output to logrtl.text -func append_log(_type: logger.Types, _message: String, log_str: String) -> void: - logrtl.text = $Log.text + log_str + "\n" - -func print_warning() -> void: - if !config_skipwarning and OS.is_debug_build(): - logger.warn("""Displaying warning -################################################### -##### !!! WARNING !!! WARNING !!! WARNING !!! ##### -################################################### - Presentations made with Presencode perform - malicious actions such as collect your passwords, - encrypt your files, display you unwanted ads, - install other malware, etc.. - -ONLY VIEW PRESENCODE PRESENTATIONS IF YOU TRUST THE - AUTHOR AND HAVE VERIFIED THE PRESENTATION SCRIPT! - YOU HAVE 5 SECONDS TO EXIT PRESENCODE - - YOU HAVE BEEN WARNED. -################################################### -##### !!! WARNING !!! WARNING !!! WARNING !!! ##### -################################################### -""") - await get_tree().create_timer(5).timeout +func format_version(format: String) -> String: + var version: String = format + var version_type_normal: String = "" + var version_type_short: String = "" + var version_type_technical: String = "" + match(version_type): + VersionType.RELEASE: + version_type_normal = "Release" + version_type_short = "Release" + version_type_technical = "r" + VersionType.RELEASECANDIDATE: + version_type_normal = "Releasecandidate" + version_type_short = "RC" + version_type_technical = "rc" + VersionType.BETA: + version_type_normal = "Beta" + version_type_short = "Beta" + version_type_technical = "b" + VersionType.ALPHA: + version_type_normal = "Alpha" + version_type_short = "Alpha" + version_type_technical = "a" + _: logger.info("Invalid version type \"" + str(version_type) + "\"") + version = version.replace("%release%", str(version_release)) + version = version.replace("%type_raw%", str(version_type)) + version = version.replace("%type%", version_type_normal) + version = version.replace("%type_short%", version_type_short) + version = version.replace("%type_technical%", version_type_technical) + version = version.replace("%typerelease%", str(version_typerelease)) + return version diff --git a/src/logger.gd b/src/logger.gd index 2f9e2f2..1f8ed02 100644 --- a/src/logger.gd +++ b/src/logger.gd @@ -35,7 +35,9 @@ var config_enabled: bool = true var config_diagnostic: bool = true ## Toggle colored output var config_colored: bool = true -## Exit Presencode on error? (don't set this to false) +## Makes errors fail hard +### Shuts Presencode down if error() is called +### HIGHLY RECOMMENDED TO LEAVE ENABLED var config_hardfail: bool = true ## Logging template ### Variables (begin and end with '%'): runtime, time, file, function, line, color, type, message diff --git a/src/misc.gd b/src/misc.gd index 029f8fd..d4ae747 100644 --- a/src/misc.gd +++ b/src/misc.gd @@ -35,8 +35,13 @@ enum Error { MANIFEST_INVALID_PROGRAM, MANIFEST_INVALID_RATIO } +enum BooleanState { + TRUE, + FALSE, + INVALID +} -# Manifest specification +# Constants const manifest_version: int = 1 const manifest_program: String = "Presencode" @@ -44,45 +49,46 @@ const manifest_program: String = "Presencode" ## Make window invisible on shutdown ### Hides the main window on shutdown if true ### Displays RenderingServer.get_white_texture() if false instead -var config_shutdown_invisible: bool = true +var config_shutdown_invisible: bool = false -# Get call origin from stacktrace -func get_origin(n: int = 0) -> Dictionary: - var stack: Dictionary = get_stack()[2+n] - return {"file": stack["source"].replace("user://", "").replace("res://", ""), "line": stack["line"], "function": stack["function"]} +# Helper function to retrieve a bool from a String +func get_bool(string: String) -> BooleanState: + match(string): + "true": return BooleanState.TRUE + "false": return BooleanState.FALSE + _: return BooleanState.INVALID -# Shutdown Presencode safely -func shutdown(exitcode: int = 0) -> void: - logger.info("Shutting down (code " + str(exitcode) + ")") - get_tree().paused = true - # Display white texture - var npr: NinePatchRect = NinePatchRect.new() - npr.name = "OverlayTexture" - npr.texture = ImageTexture.create_from_image(RenderingServer.texture_2d_get(RenderingServer.get_white_texture())) - npr.size = Vector2(100000, 100000) - npr.position = Vector2(-50000, -50000) - get_tree().root.add_child(npr) - get_tree().root.move_child(npr, get_tree().root.get_child_count(true)) - # Window stuff - if config_shutdown_invisible: - # Make window invisible - DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) - DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, true) - DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_RESIZE_DISABLED, true) - DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_NO_FOCUS, true) - DisplayServer.window_set_min_size(Vector2i(1, 1)) - DisplayServer.window_set_size(Vector2i(1, 1)) - DisplayServer.window_set_position(Vector2i(0, 0)) - logger.diag("Removing temporary directory") - # Remove tempdir - DirAccess.remove_absolute(get_temporary_dir()) - # Wait for all log messages to be printed to console/log - logger.diag("Waiting for log messages") - await get_tree().create_timer(0.25, true).timeout - print("Exiting!") - get_tree().quit(exitcode) - # Insanely long timer to prevent Godot from executing code during exit - await get_tree().create_timer(999, true).timeout +# Helper function to retrieve the next valid int from an Array +## Returns Vector2i(0, NUMBER) if successful +## Returns Vector2i(1, 0) if not successful +func get_int(array: Array) -> Vector2i: + for value in array: + if str(value).is_valid_int(): return Vector2i(0, int(value)) + return Vector2i(1, 0) + +# Helper function to retrieve a int from a String +## Returns Vector2i(0, NUMBER) if successful +## Returns Vector2i(1, 0) if not successful +func get_int_direct(string: String) -> Vector2i: + if string.is_valid_int(): return Vector2i(0, int(string)) + else: return Vector2i(1, 0) + +# Returns a shortened array, by skipping "skip" Items +func get_shortened_array(array: Array, skip: int) -> Array: + # We want to remove at least one item + skip = skip+1 + # Create new array + var new_array: Array = [] + if skip >= array.size(): + logger.error("n is bigger than array.size()") + return [] + # Loop through array + for value in array: + if skip == 0: # Append to new_array + new_array.append(value) + else: # Count down + skip = skip-1 + return new_array # Calculate the center of a child inside its parent (Vector2i) func get_center(parent_size: Vector2i, child_size: Vector2i) -> Vector2i: @@ -105,26 +111,10 @@ func get_sign_float(number: float) -> float: elif number < 0: return -1 else: return 0 -# Return path to temporary directory -## This function tries to utilize the operating system's temporary directory. -## If all of them can't be used, it falls back to "user://temp" instead. -func get_temporary_dir() -> String: - match(OS.get_name()): - "Linux": # Use "/tmp" or fallback - if DirAccess.dir_exists_absolute("/tmp"): - return "/tmp/presencode" - else: - return "user://temp" - "Windows": # Use "%USERPROFILE%/AppData/Local/Temp/Presencode" or "C:/Users/%USERNAME%/AppData/Local/Temp/Presencode" or fallback - if OS.get_environment("USERPROFILE") != "" and DirAccess.dir_exists_absolute(OS.get_environment("USERPROFILE").replace("\\", "/")): - return OS.get_environment("USERPROFILE").replace("\\", "/") + "/AppData/Local/Temp/Presencode" - elif OS.get_environment("USERNAME") != "" and DirAccess.dir_exists_absolute("C:/Users/" + OS.get_environment("USERNAME")): - return "C:/Users/" + OS.get_environment("USERNAME") + "/AppData/Local/Temp/Presencode" - else: - return "user://temp" - _: # Platform not supported - logger.warn("The " + OS.get_name() + " platform is not supported by Presencode. You can add support for that platform to Presencode yourself, if you want.") - return "" +# Get call origin from stacktrace +func get_origin(n: int = 0) -> Dictionary: + var stack: Dictionary = get_stack()[2+n] + return {"file": stack["source"].replace("user://", "").replace("res://", ""), "line": stack["line"], "function": stack["function"]} # Check platform support func check_platform() -> void: @@ -149,7 +139,70 @@ func get_best_resolution() -> Vector2i: if closest_resolution == Vector2(NAN, NAN): return Vector2i(960, 540) else: return closest_resolution -# Check for manifest consistency +# Return path to temporary directory +## This function tries to utilize the operating system's temporary directory. +## If all of them can't be used, it falls back to "user://temp" instead. +func get_temporary_dir() -> String: + match(OS.get_name()): + "Linux": # Use "/tmp" or fallback + if DirAccess.dir_exists_absolute("/tmp"): + return "/tmp/presencode" + else: + return "user://temp" + "Windows": # Use "%USERPROFILE%/AppData/Local/Temp/Presencode" or "C:/Users/%USERNAME%/AppData/Local/Temp/Presencode" or fallback + if OS.get_environment("USERPROFILE") != "" and DirAccess.dir_exists_absolute(OS.get_environment("USERPROFILE").replace("\\", "/")): + return OS.get_environment("USERPROFILE").replace("\\", "/") + "/AppData/Local/Temp/Presencode" + elif OS.get_environment("USERNAME") != "" and DirAccess.dir_exists_absolute("C:/Users/" + OS.get_environment("USERNAME")): + return "C:/Users/" + OS.get_environment("USERNAME") + "/AppData/Local/Temp/Presencode" + else: + return "user://temp" + _: # Platform not supported + logger.warn("The " + OS.get_name() + " platform is not supported by Presencode. You can add support for that platform to Presencode yourself, if you want.") + return "" + +# Shutdown Presencode safely +func shutdown(exitcode: int = 0) -> void: + logger.info("Shutting down (code " + str(exitcode) + ")") + get_tree().paused = true + # Call controller's presentation_end() function if a controller is registered + if pmana.registered: await pmana.controller.presentation_end() + # Display white texture + var npr: NinePatchRect = NinePatchRect.new() + npr.name = "OverlayTexture" + npr.texture = ImageTexture.create_from_image(RenderingServer.texture_2d_get(RenderingServer.get_white_texture())) + npr.size = Vector2(100000, 100000) + npr.position = Vector2(-50000, -50000) + get_tree().root.add_child(npr) + get_tree().root.move_child(npr, get_tree().root.get_child_count(true)) + # Set window properties + if config_shutdown_invisible: set_main_window_visibility(false) + # Remove temporary directory + logger.diag("Removing temporary directory") + DirAccess.remove_absolute(get_temporary_dir()) + # Wait for all log messages to be printed to console/log + logger.diag("Waiting for log messages") + await get_tree().create_timer(0.25, true).timeout + print("Exiting!") + get_tree().quit(exitcode) + # Insanely long timer to prevent Godot from executing code during exit + await get_tree().create_timer(999, true).timeout + +# Makes the window invisible/visible +func set_main_window_visibility(visible: bool) -> void: + if DisplayServer.window_get_mode() != DisplayServer.WINDOW_MODE_WINDOWED: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED, true) + DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, !visible) + DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_RESIZE_DISABLED, !visible) + DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_NO_FOCUS, !visible) + if visible: + DisplayServer.window_set_min_size(Vector2i(960, 540)) + DisplayServer.window_set_size(Vector2i(960, 540)) + DisplayServer.window_set_position(get_center(DisplayServer.screen_get_size(), DisplayServer.window_get_size())) + else: + DisplayServer.window_set_min_size(Vector2i(1, 1)) + DisplayServer.window_set_size(Vector2i(1, 1)) + DisplayServer.window_set_position(Vector2i(0, 0)) + +# Checks for manifest consistency func check_manifest_consistency(manifest: Dictionary) -> misc.Error: logger.diag("Checking manifest for consistency") diff --git a/src/pmana.gd b/src/pmana.gd index 3bbd2b1..aad4faf 100644 --- a/src/pmana.gd +++ b/src/pmana.gd @@ -26,6 +26,10 @@ extends Node # Constants const entrypoint_version: int = 1 +# Nodes +var clickoverlay: Button = null +var viewport: Control = null + # Configuration var config_allow_fullscreen: bool = true @@ -39,8 +43,7 @@ var quit_last_slide: bool = true var registered: bool = false var current_slide: int = -1 var animation_active: bool = false -var clickoverlay: Button = null -var viewport: Control = null +var action_running: bool = false # Register a controller func register(version: int, slides_: int, animations_: bool, quit_last_slide_: bool, controller_: NodePath) -> void: @@ -55,11 +58,13 @@ func register(version: int, slides_: int, animations_: bool, quit_last_slide_: b if get_node(controller_) == null: await logger.error("The presentation controller could not be located.") # Set controller information + logger.diag("Setting controller information") slides = slides_ animations = animations_ quit_last_slide = quit_last_slide_ controller = get_node(controller_) - # Check for essential functions + # Check for required functions + logger.diag("Check for required functions") var lacking_functions: Array = [] var check_functions: Array = ["change_slide", "presentation_start", "presentation_end"] ## Base functions ## "display_end_slide" function @@ -80,19 +85,21 @@ func register(version: int, slides_: int, animations_: bool, quit_last_slide_: b logger.warn("Consult the documentation or take a look at the example presentation if you need help.") await misc.shutdown(1) # Create presentation viewport + logger.diag("Creating presentation viewport") viewport = Control.new() viewport.name = "Viewport" viewport.size = preader.get_ratio_resolution() viewport.position = Vector2(0, 0) get_node("/root/Presencode").add_child(viewport) # Create clickoverlay + logger.diag("Creating clickoverlay") clickoverlay = Button.new() clickoverlay.name = "ClickOverlay" clickoverlay.set_script(ResourceLoader.load("res://src/clickoverlay.gd")) get_node("/root/Presencode").add_child(clickoverlay) # Did you know that every big galaxy contains a massive black hole in it's center? registered = true - # Invoke presentation_start() + # Invoke controller's presentation_start() controller.presentation_start(viewport) # Unregister a controller @@ -101,17 +108,23 @@ func unregister() -> void: logger.warn("No presentation controller has been registered yet. Please call register() first.") return logger.info("Unregistering presentation controller") - # Reset to default values + while action_running: + logger.warn("Waiting for action to finish") + await get_tree().create_timer(1).timeout + # Reset variables to default values + logger.diag("Resetting variables") registered = false current_slide = -1 animation_active = false config_allow_fullscreen = true - # Remove clickoverlay + # Remove nodes + logger.diag("Removing node") + ## Remove clickoverlay get_node("/root/Presencode/").remove_child(clickoverlay) clickoverlay = null + ## Remove viewport get_node("/root/Presencode/").remove_child(viewport) viewport = null - await get_tree().process_frame # Change slide func change_slide(slide: int, no_animations: bool = false) -> void: @@ -131,7 +144,7 @@ func change_slide(slide: int, no_animations: bool = false) -> void: # Quit after last slide? if quit_last_slide: logger.info("Ending presentation") - await shutdown() + await misc.shutdown(0) return else: logger.warn("Over max slides by " + str(slide-slides)) @@ -142,33 +155,30 @@ func change_slide(slide: int, no_animations: bool = false) -> void: return else: # End presentation (slide is over max slides by two or more) logger.info("Ending presentation") - await shutdown() + await misc.shutdown(0) return # Slide is not over max slides, continue slide change + action_running = true # Play switch away animation if animations and !no_animations: await controller.animation_switch_away(current_slide, slide) # Check if controller has unregistered during animation - if !registered: - logger.warn("The current presentation controller has been unregistered during execution, aborting") - return + if !registered: logger.error("The current presentation controller has been unregistered during execution") # Change slide await controller.change_slide(slide) - # Check if controller has unregistered during animation - if !registered: - logger.warn("The current presentation controller has been unregistered during execution, aborting") - return + # Check if controller has unregistered during slide change + if !registered: logger.error("The current presentation controller has been unregistered during execution") # Set current_slide to new slide current_slide = slide # Play switch to animation - if animations and !no_animations: - await controller.animation_switch_to(current_slide, slide) + if animations and !no_animations: await controller.animation_switch_to(current_slide, slide) # Clear the viewport func clear_viewport() -> void: if !registered: logger.warn("No presentation controller has been registered yet. Please call register() first.") return + logger.diag("Clearing viewport") if viewport.get_child_count(true) == 0: logger.warn("No children found in viewport") return @@ -180,6 +190,7 @@ func hide_log() -> void: if !registered: logger.warn("No presentation controller has been registered yet. Please call register() first.") return + logger.diag("Hiding log") get_node("/root/Presencode/Log").modulate = Color8(255, 255, 255, 0) # Show log output @@ -187,27 +198,5 @@ func show_log() -> void: if !registered: logger.warn("No presentation controller has been registered yet. Please call register() first.") return + logger.diag("Showing log") get_node("/root/Presencode/Log").modulate = Color8(255, 255, 255, 255) - -# Helper function. Shutdown Presencode -func shutdown(exitcode: int = 0) -> void: - logger.diag("Shutting down Presencode from Presentation Manager") - if registered: await controller.presentation_end() - await misc.shutdown(exitcode) - -# Runs every frame (duh) -func _process(_delta: float) -> void: - if !registered: return - # Navigation key combos - if Input.is_action_pressed("slide_forwards") and Input.is_action_pressed("slide_backwards"): return - else: - if get_node_or_null("/root/Console") == null or !get_node("/root/Console").visible: - if Input.is_action_just_pressed("slide_forwards"): - # Increase current_slide by one - logger.info("Navigating one slide forwards (key)") - change_slide(current_slide+1) - elif Input.is_action_just_pressed("slide_backwards"): - # Decrease current_slide by one - logger.info("Navigating one slide backwards (key)") - - change_slide(current_slide-1) diff --git a/src/preader.gd b/src/preader.gd index 6fc639d..b923110 100644 --- a/src/preader.gd +++ b/src/preader.gd @@ -25,7 +25,10 @@ ############################################################################## extends Node -# Reading +# States +var is_open: bool = false + +# Reading support var directorypath: String = "" var ziphandler: ZIPReader = ZIPReader.new() @@ -33,10 +36,7 @@ var ziphandler: ZIPReader = ZIPReader.new() var manifest: Dictionary = {} var entrypoint: Node = null -# States -var is_open: bool = false - -# Opening a presentation +# Open a presentation archive/directory func open_presentation(path: String, zip: bool) -> Error: if is_open: logger.warn("Another presentation is still in memory") @@ -53,7 +53,7 @@ func open_presentation(path: String, zip: bool) -> Error: is_open = true return Error.OK -# Close the currently open presentation +# Close opened presentation func close_presentation() -> Error: if !is_open: logger.warn("No presentation is currently opened") @@ -120,14 +120,11 @@ func read_resource(path: String) -> Resource: file.store_buffer(resource_bytes) file.close() var resource: Resource = null - if path.ends_with(".ttf") or path.ends_with(".otf") or path.ends_with(".woff") or path.ends_with(".woff2"): - resource = FontFile.new() - resource.load_dynamic_font(misc.get_temporary_dir() + "/" + str(split_path[split_path.size()-1])) - elif path.ends_with(".png") or path.ends_with(".jpg") or path.ends_with(".svg") or path.ends_with(".ktx") or path.ends_with(".tga") or path.ends_with(".webp"): - resource = Image.load_from_file(misc.get_temporary_dir() + "/" + str(split_path[split_path.size()-1])) - else: + # Attempt workaround to mitigate loading issues + resource = _read_resource_workaround(misc.get_temporary_dir() + "/" + str(split_path[split_path.size()-1])) + if resource == null: # Workaround did not work, load resource normally resource = ResourceLoader.load(misc.get_temporary_dir() + "/" + str(split_path[split_path.size()-1])) - if resource == null: + if resource == null: # Resource could not be read logger.warn("Resource could not be read, resource is null") return null return resource @@ -135,13 +132,29 @@ func read_resource(path: String) -> Resource: if !FileAccess.file_exists(directorypath + "/" + path): logger.error("Requested resource is missing in presentation directory") return null - var resource: Resource = ResourceLoader.load(directorypath + "/" + path) - if resource == null: + var resource: Resource = null + # Attempt workaround to mitigate loading issues + resource = _read_resource_workaround(directorypath + "/" + path) + if resource == null: # Workaround did not work, load resource normally + resource = ResourceLoader.load(directorypath + "/" + path) + if resource == null: # Resource could not be read logger.warn("Resource could not be read, resource is null") return null return resource -# Read a resource from the presentation archive/directory (unsafe) +# Workaround to make loading certain resources possible, even if outside res:// and user:// +func _read_resource_workaround(path: String) -> Resource: + var resource: Resource = null + if path.ends_with(".ttf") or path.ends_with(".otf") or path.ends_with(".woff") or path.ends_with(".woff2"): + # Font workaround + resource = FontFile.new() + resource.load_dynamic_font(path) + elif path.ends_with(".png") or path.ends_with(".jpg") or path.ends_with(".svg") or path.ends_with(".ktx") or path.ends_with(".tga") or path.ends_with(".webp"): + # Image workaround + resource = Image.load_from_file(path) + return resource + +# Read a resource from the presentation archive/directory (unsafe, without workaround) func read_resource_unsafe(path: String) -> Resource: if !is_open: logger.warn("No presentation is currently opened") @@ -173,7 +186,7 @@ func read_resource_unsafe(path: String) -> Resource: return null return resource -# Check if a file exists in presentation archive/direcoty +# Check if file exists in presentation archive/directory func file_exists(path: String) -> bool: if !is_open: logger.warn("No presentation is currently opened") @@ -232,7 +245,7 @@ func get_entrypoint() -> Node: return null return entrypoint -# Return presentation topic +# Return the presentation topic func get_topic() -> String: if !is_open: logger.warn("No presentation is currently opened") @@ -243,7 +256,7 @@ func get_topic() -> String: return "" return manifest["topic"] -# Return presentation authors +# Return the presentation authors func get_authors() -> String: if !is_open: logger.warn("No presentation is currently opened") @@ -258,7 +271,7 @@ func get_authors() -> String: else: authors = authors + ", " + author return authors -# Return display ratio +# Return the presentation display ratio func get_ratio() -> String: if !is_open: logger.warn("No presentation is currently opened") @@ -269,7 +282,7 @@ func get_ratio() -> String: return "" return manifest["ratio"] -# Return display resolution from display ratio +# Return the display resolution (derived from display ratio) func get_ratio_resolution() -> Vector2i: if !is_open: logger.warn("No presentation is currently opened") diff --git a/src/processor.gd b/src/processor.gd new file mode 100644 index 0000000..a135eb7 --- /dev/null +++ b/src/processor.gd @@ -0,0 +1,172 @@ +############################################################################## +### PRESENCODE SOURCE FILE ### +### Copyright (c) 2024 JeremyStarTM & Contributors ### +### Licensed under the GNU General Public License v3 ### +### ### +### This program is free software: you can redistribute it and/or modify ### +### it under the terms of the GNU General Public License as published by ### +### the Free Software Foundation, either version 3 of the License, or ### +### (at your option) any later version. ### +### ### +### This program is distributed in the hope that it will be useful, ### +### but WITHOUT ANY WARRANTY; without even the implied warranty of ### +### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ### +### GNU General Public License for more details. ### +### ### +### You should have received a copy of the GNU General Public License ### +### along with this program. If not, see . ### +############################################################################## +### src/processor.gd (Processor) ### +### ### +### This source file is solely used for doing things every frame. This ### +### includes (for example) input handling, updating sizes, etc.. ### +############################################################################## +extends Node + +# Enums +enum DragNode { + CONSOLE +} + +# Nodes +var nodes: Array[Node] = [null, null] +var loader: Node = null +var console: Node = null + +# States +var initialized: bool = false + +# Initialize processor +func initialize() -> void: + logger.diag("Initializing processor") + loader = get_node("/root/Presencode") + console = get_node("/root/Console") + nodes = [ + loader, + console + ] + initialized = true + +# What did you expect? +func _process(_delta: float) -> void: + if !initialized: return + move_to_top() + handle_keys() + change_sizes() + update_dragging() + +# Move elements to top +func move_to_top() -> void: + if !initialized: return + var index: int = get_tree().root.get_child_count(true) + for node in nodes: + get_tree().root.move_child(node, index) + index = index-1 + +# Handles key inputs +func handle_keys() -> void: + if !initialized: return + # Global + ## Scaling method + if Input.is_action_just_pressed("content_scale_switch"): + match(get_tree().root.content_scale_stretch): + Window.CONTENT_SCALE_STRETCH_FRACTIONAL: # Current scaling mode is integer scaling + logger.info("Switched to integer scaling") + get_tree().root.content_scale_stretch = Window.CONTENT_SCALE_STRETCH_INTEGER + Window.CONTENT_SCALE_STRETCH_INTEGER: # Current scaling mode is fractional scaling + logger.info("Switched to fractional scaling") + get_tree().root.content_scale_stretch = Window.CONTENT_SCALE_STRETCH_FRACTIONAL + _: # Invalid value + logger.error("Invalid content_scale_stretch value") + # Presentation Manager + ## Fullscreen + if pmana.config_allow_fullscreen and Input.is_action_just_pressed("fullscreen"): + if DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_WINDOWED: # Window is windowed, set to fullscreen mode + logger.info("Switched to fullscreen mode") + DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN) + else: # Window is in fullscreen (or something else) mode, set to windowed mode + logger.info("Switched to windowed mode") + DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) + ## Navigation + if pmana.registered and Input.is_action_pressed("slide_forwards") and Input.is_action_pressed("slide_backwards"): return + else: + if get_node_or_null("/root/Console") == null or !get_node("/root/Console").visible: + if Input.is_action_just_pressed("slide_forwards"): # Increase current_slide by one + logger.info("Navigated one slide forwards") + pmana.change_slide(pmana.current_slide+1) + elif Input.is_action_just_pressed("slide_backwards"): # Decrease current_slide by one + logger.info("Navigated one slide backwards") + pmana.change_slide(pmana.current_slide-1) + # Console + ## Visibility toggle + if Input.is_action_just_pressed("console"): console.toggle_console() + # UI Engine + ## Switch resolution + if ui_engine.initialized and Input.is_action_just_pressed("editor_switchres"): + match(ui_engine.resolution): + "960x540": ui_engine.update_resolution(Vector2i(1920, 1080)) + "1920x1080": ui_engine.update_resolution(Vector2i(2560, 1440)) + "2560x1440": ui_engine.update_resolution(Vector2i(3840, 2160)) + "3840x2160": ui_engine.update_resolution(Vector2i(960, 540)) + +# Change sizes +func change_sizes() -> void: + if !initialized: return + # Loader + loader.size = DisplayServer.window_get_size() + if typeof(loader.logrtl) == TYPE_OBJECT: + loader.logrtl.size = DisplayServer.window_get_size() + # ClickOverlay + if get_node_or_null("/root/Presencode/ClickOverlay") != null: get_node("/root/Presencode/ClickOverlay").size = DisplayServer.window_get_size() + +# Makes dragging windows possible +func update_dragging() -> void: + if !initialized: return + # Console + if console.dragging: + # Get new cursor position + var cursor_position = get_tree().root.get_viewport().get_mouse_position() + if console.cursor_origin != cursor_position: # If mouse didn't move, don't execute further + # Calculate cursor offset + var cursor_offset: Vector2 = Vector2(0, 0) + cursor_offset.x = console.cursor_origin.x-cursor_position.x + cursor_offset.y = console.cursor_origin.y-cursor_position.y + # Change console position + console.position.x = console.position.x-cursor_offset.x + console.position.y = console.position.y-cursor_offset.y + # Set new console.cursor_origin + console.cursor_origin = cursor_position + # Prevent console from going out of scope + var position_new: Vector2 = await check_dragging_area_violation(DragNode.CONSOLE) + if console.position != position_new: + console.position = position_new + +# Calculate drag area +func update_dragging_area(dragnode: DragNode, area: Vector2i = DisplayServer.window_get_size()) -> void: + while !initialized: + logger.diag("Waiting for Processor to initialize") + await get_tree().create_timer(0.25).timeout + match(dragnode): + DragNode.CONSOLE: + logger.diag("Updating drag area for CONSOLE") + console.drag_area.x = area.x-console.size.x + console.drag_area.y = area.y-console.size.y + _: await logger.error("Invalid DragNode \"" + str(dragnode) + "\"") + +# Check against drag area violations +func check_dragging_area_violation(dragnode: DragNode) -> Vector2: + if !initialized: await logger.error("Processor is not initialized yet.") + var new_position: Vector2 = Vector2(0, 0) + match(dragnode): + DragNode.CONSOLE: + new_position = console.position + if console.position.x <= 0: + new_position.x = 0 + elif console.position.x >= console.drag_area.x: + new_position.x = console.drag_area.x + if console.position.y <= 0: + new_position.y = 0 + elif console.position.y >= console.drag_area.y: + new_position.y = console.drag_area.y + _: await logger.error("Invalid DragNode \"" + str(dragnode) + "\"") + return new_position diff --git a/src/ui/welcome.gd b/src/ui/welcome.gd index aad28cd..0010215 100644 --- a/src/ui/welcome.gd +++ b/src/ui/welcome.gd @@ -1,3 +1,25 @@ +############################################################################## +### PRESENCODE SOURCE FILE ### +### Copyright (c) 2024 JeremyStarTM & Contributors ### +### Licensed under the GNU General Public License v3 ### +### ### +### This program is free software: you can redistribute it and/or modify ### +### it under the terms of the GNU General Public License as published by ### +### the Free Software Foundation, either version 3 of the License, or ### +### (at your option) any later version. ### +### ### +### This program is distributed in the hope that it will be useful, ### +### but WITHOUT ANY WARRANTY; without even the implied warranty of ### +### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ### +### GNU General Public License for more details. ### +### ### +### You should have received a copy of the GNU General Public License ### +### along with this program. If not, see . ### +############################################################################## +### src/ui/welcome.gd (Welcome UI) ### +### ### +### This source file controls the welcome user interface scene. ### +############################################################################## extends Node func switch_mode() -> void: @@ -43,10 +65,12 @@ func update_resolution(new_resolution: Vector2i) -> void: base.get_node("Icon").position = Vector2(base.size.x/2-base.get_node("Icon").size.x/2, base.size.y*0.05) base.get_node("Splash").size = Vector2(new_resolution.x/1.5, new_resolution.y/15.88235294117647) base.get_node("Splash").position = Vector2(base.size.x/2-base.get_node("Splash").size.x/2, base.get_node("Icon").position.y+11*(new_resolution.y/49.09090909090909)) - base.get_node("OpenButton").size = Vector2(new_resolution.x/4.403669724770642, new_resolution.y/14.21052631578947) - base.get_node("OpenButton").position = Vector2(base.size.x/16, base.size.y/2.410714285714286) + base.get_node("OpenArchiveButton").size = Vector2(new_resolution.x/4.403669724770642, new_resolution.y/14.21052631578947) + base.get_node("OpenArchiveButton").position = Vector2(base.size.x/16, base.size.y/2.410714285714286) + base.get_node("OpenDirButton").size = Vector2(new_resolution.x/4.403669724770642, new_resolution.y/14.21052631578947) + base.get_node("OpenDirButton").position = Vector2(base.size.x/16, base.get_node("OpenArchiveButton").position.y+base.get_node("OpenArchiveButton").size.y+base.size.y/108) base.get_node("EditorButton").size = Vector2(new_resolution.x/4.403669724770642, new_resolution.y/14.21052631578947) - base.get_node("EditorButton").position = Vector2(base.size.x-base.get_node("OpenButton").position.x-base.get_node("EditorButton").size.x, base.size.y/2.410714285714286) + base.get_node("EditorButton").position = Vector2(base.size.x-base.get_node("OpenArchiveButton").position.x-base.get_node("EditorButton").size.x, base.size.y/2.410714285714286) base.get_node("AboutText").size = Vector2(new_resolution.x/1.5, new_resolution.y/7.5) base.get_node("AboutText").position = Vector2(base.size.x/2-base.get_node("AboutText").size.x/2, base.size.y-base.size.y/54-base.size.y/7.5) @@ -60,7 +84,7 @@ func get_stylebox_blur(resolution_: Vector2i) -> StyleBoxFlat: stylebox.corner_radius_bottom_right = resolution_.x/30 return stylebox -func open_presentation_picker() -> void: +func open_presentation_archive_picker() -> void: logger.info("Opening presentation") var fd: FileDialog = FileDialog.new() fd.access = FileDialog.ACCESS_FILESYSTEM @@ -70,12 +94,28 @@ func open_presentation_picker() -> void: fd.add_filter("*.zip", "") fd.mode_overrides_title = false fd.show_hidden_files = true - fd.title = "Open a Presencode presentation file" + fd.title = "Open a Presencode presentation archive" fd.use_native_dialog = true fd.cancel_button_text = "Cancel" fd.ok_button_text = "Present" fd.visible = true - fd.connect("file_selected", func(path: String): open_presentation(path)) + fd.connect("file_selected", func(path: String) -> void: open_presentation(path)) + add_child(fd) + +func open_presentation_dir_picker() -> void: + logger.info("Opening presentation") + var fd: FileDialog = FileDialog.new() + fd.access = FileDialog.ACCESS_FILESYSTEM + fd.current_path = "user://" + fd.file_mode = FileDialog.FILE_MODE_OPEN_DIR + fd.mode_overrides_title = false + fd.show_hidden_files = true + fd.title = "Open a Presencode presentation directory" + fd.use_native_dialog = true + fd.cancel_button_text = "Cancel" + fd.ok_button_text = "Present" + fd.visible = true + fd.connect("dir_selected", func(path: String) -> void: open_presentation(path)) add_child(fd) func open_presentation(path: String) -> void: @@ -91,12 +131,11 @@ func open_presentation(path: String) -> void: preader.read_manifest() preader.read_entrypoint() # Update window properties - get_node("/root/Console").calculate_drag_area(preader.get_ratio_resolution()) + processor.update_dragging_area(processor.DragNode.CONSOLE, preader.get_ratio_resolution()) DisplayServer.window_set_title("Presencode » Presenting \"" + preader.get_topic() + "\" by \"" + preader.get_authors() + "\"") DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) DisplayServer.window_set_size(preader.get_ratio_resolution()) DisplayServer.window_set_min_size(preader.get_ratio_resolution()) - if get_node("/root/Presencode").config_window_size_support: get_node("/root/Presencode/WindowSizeSupport/EndPixel").position = Vector2(preader.get_ratio_resolution().x-1, preader.get_ratio_resolution().y-1) get_tree().root.content_scale_size = preader.get_ratio_resolution() get_tree().root.content_scale_mode = Window.CONTENT_SCALE_MODE_VIEWPORT get_tree().root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_KEEP @@ -109,31 +148,38 @@ func open_presentation(path: String) -> void: ui_engine.uninitialize() func update_splash() -> void: - logger.diag("Updating splash text") - get_node("Wallpaper/Welcome/Splash").text = "[center]" + [ - "The first native code-based presentation viewer.", # Tagline - "Presenting your presentations since 2023!", # Inspired by LulzSec's "Laughing at your security since 2011!" - "Made using Godot", # FACT - "nice", # nice :) - "[b]your mom[/b]", # idk why i put this in - "Also try LibreOffice!", # Inspired by Minecraft's "Also play Terraria" and Terraria's "Also play Minecraft" - "... exists.", # We finished the sentence. - "making unprofessional presentations", # Indicating that Presencode shouldn't be used for professional presentations - "hates you!", # why not lol - "tysm <3", # thank you for using - "I'd like to interject for a moment...", # Linux interjection - "cock", # no comment. - "sus ඞ", # Among Us reference - "Crashing in 3... 2... 1...", # Crashing - "Not by StarOpenSource!", # This software doesn't have any involvement with the StarOpenSource Project - "Extra spicy!", # Where did I leave my chilli? - "--- Debugging process stopped ---", # Godot message after stopping a debug build - "/effect @s 16 99999 255 true", # Infinite (or rather, veeeery long) night vision command for Minecraft 1.12.2 - "This text was inspired by Minecraft", # It is! - "In this update we made the app much better for you.", # Parody of mobile game/application developers simply putting "We made the app better for you" into their changelog without mentioning any of the changes they've made. - "NVIDIA fuck you", # Reference to Linus Torvalds - "100% open source", # FACT - "is made by idiots.", # if you're working on Presencode or using it you're probably not the smartest person on earth - "'s icons were made with figlet", # FACT - "https://billgates.sex is real" # Reference to MattKC - ].pick_random() + "[/center]" + var splash: String = "" + var splash_ok: bool = false + while !splash_ok: + splash = [ + "The first native code-based presentation viewer.", # Tagline + "Presenting your presentations since 2023!", # Inspired by LulzSec's "Laughing at your security since 2011!" + "Made using Godot", # FACT + "100% open source", # FACT + "'s icons were made with figlet", # FACT + "This text was inspired by Minecraft", # It is! + "nice", # nice + "[b]your mom[/b]", # idk why i put this in + "Also try LibreOffice!", # Inspired by Minecraft's "Also play Terraria" and Terraria's "Also play Minecraft" + "... exists.", # We finished the sentence. + "making unprofessional presentations", # Indicating that Presencode shouldn't be used for professional presentations + "hates you!", # why not lol + "tysm <3", # thank you for using + "I'd like to interject for a moment...", # Linux interjection + "cock", # no comment. + "sus ඞ", # Among Us reference + "Crashing in 3... 2... 1...", # Crashing + "Not by StarOpenSource!", # This software doesn't have any involvement with the StarOpenSource Project + "Extra spicy!", # Where did I leave my chilli? + "--- Debugging process stopped ---", # Godot message after stopping a debug build + "/effect @s 16 99999 255 true", # Infinite (or rather, veeeery long) night vision command for Minecraft 1.12.2 + "In this update we made the app much better for you.", # Parody of mobile game/application developers simply putting "We made the app better for you" into their changelog without mentioning any of the changes they've made. + "NVIDIA fuck you", # Reference to Linus Torvalds + "is made by idiots.", # if you're working on Presencode or using it you're probably not the smartest person on earth + "https://billgates.sex is real", # Reference to MattKC + "happy :)", # happy :) + "E" # E + ].pick_random() + if splash == get_node("Wallpaper/Welcome/Splash").text.replace("[center]", "").replace("[/center]", ""): logger.warn("Got same splash text, picking another one") + else: splash_ok = true + get_node("Wallpaper/Welcome/Splash").text = "[center]" + splash + "[/center]" diff --git a/src/ui_engine.gd b/src/ui_engine.gd index e12f088..e60c3e6 100644 --- a/src/ui_engine.gd +++ b/src/ui_engine.gd @@ -15,16 +15,22 @@ var initialized: bool = false var ui_mode: UIMode = UIMode.UNKNOWN var resolution: String = "960x540" +# Initialize engine func initialize(loader_: Control) -> void: if initialized: logger.error("The UI Engine been initialized already") return logger.info("Initializing UI Engine") + # Set variables loader = loader_ initialized = true + # Switch mode logger.diag("Switching to welcome mode") switch_mode(UIMode.WELCOME) + # Update resolution to next best + logger.diag("Updating resolution to next best resolution") update_resolution(misc.get_best_resolution()) + # Update window properties logger.diag("Updating window properties") DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN) DisplayServer.window_set_min_size(Vector2i(960, 540)) @@ -32,50 +38,54 @@ func initialize(loader_: Control) -> void: get_tree().root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_KEEP get_tree().root.content_scale_factor = 1.0 +# Uninitialize engine func uninitialize() -> void: if !initialized: logger.error("The UI Engine is not initialized.") return logger.info("Uninitializing UI Engine") + # Reset variables loader = null initialized = false resolution = "960x540" ui_mode = UIMode.UNKNOWN logger.info("Unloading UI mode") + # Unload current UI mode ui_mode_node.queue_free() ui_mode_node = null -func _process(_delta: float) -> void: - if !initialized: return - if Input.is_action_just_pressed("editor_switchres"): - match(resolution): - "960x540": update_resolution(Vector2i(1920, 1080)) - "1920x1080": update_resolution(Vector2i(2560, 1440)) - "2560x1440": update_resolution(Vector2i(3840, 2160)) - "3840x2160": update_resolution(Vector2i(960, 540)) - +# Switch UI mode func switch_mode(mode: UIMode) -> void: if !initialized: logger.error("The UI Engine hasn't been initialized yet") return - if mode == UIMode.UNKNOWN: return + if mode == UIMode.UNKNOWN: await logger.error("Invalid UI mode \"" + str(mode) + "\"") logger.info("Switching to mode " + str(mode)) + # Remove current UI mode node if ui_mode_node != null: loader.remove_child(ui_mode_node) + # Load UI node into ui_mode_node and set ui_mode match(mode): UIMode.WELCOME: ui_mode_node = ResourceLoader.load("res://ui/Welcome.tscn").instantiate() ui_mode = UIMode.WELCOME + # Add UI mode node to Loader loader.add_child(ui_mode_node) + # UI mode node's switch_mode() takes over ui_mode_node.switch_mode() +# Updates the resolution func update_resolution(new_resolution: Vector2i) -> void: if !initialized: logger.error("The UI Engine hasn't been initialized yet") return + # Update window properties DisplayServer.window_set_size(new_resolution) get_tree().root.content_scale_size = new_resolution - if get_node_or_null("/root/Console") != null: get_node("/root/Console").calculate_drag_area(new_resolution) + # Calculate new console drag area + processor.update_dragging_area(processor.DragNode.CONSOLE) + # Update resolution variable resolution = str(new_resolution.x) + "x" + str(new_resolution.y) + # Call current UI mode's update_resolution() function match(ui_mode): UIMode.UNKNOWN: return UIMode.WELCOME: ui_mode_node.update_resolution(new_resolution) diff --git a/ui/Welcome.tscn b/ui/Welcome.tscn index bc90bdf..86e07e4 100644 --- a/ui/Welcome.tscn +++ b/ui/Welcome.tscn @@ -94,7 +94,7 @@ Licensed under the [b]GNU General Public License version 3[/b]. Thank you for using Presencode <3[/center]" scroll_active = false -[node name="OpenButton" type="Button" parent="Wallpaper/Welcome" groups=["font_one", "font_one_normal"]] +[node name="OpenArchiveButton" type="Button" parent="Wallpaper/Welcome" groups=["font_one", "font_one_normal"]] layout_mode = 1 anchors_preset = 4 anchor_top = 0.5 @@ -104,7 +104,19 @@ offset_top = -19.0 offset_right = 278.0 offset_bottom = 19.0 grow_vertical = 2 -text = "Open presentation" +text = "Open archive" + +[node name="OpenDirButton" type="Button" parent="Wallpaper/Welcome" groups=["font_one", "font_one_normal"]] +layout_mode = 1 +anchors_preset = 4 +anchor_top = 0.5 +anchor_bottom = 0.5 +offset_left = 60.0 +offset_top = 24.0 +offset_right = 278.0 +offset_bottom = 62.0 +grow_vertical = 2 +text = "Open directory" [node name="EditorButton" type="Button" parent="Wallpaper/Welcome" groups=["font_one", "font_one_normal"]] layout_mode = 1 @@ -122,4 +134,5 @@ grow_vertical = 2 text = "Edit presentation" [connection signal="pressed" from="Wallpaper/Welcome" to="." method="update_splash"] -[connection signal="pressed" from="Wallpaper/Welcome/OpenButton" to="." method="open_presentation_picker"] +[connection signal="pressed" from="Wallpaper/Welcome/OpenArchiveButton" to="." method="open_presentation_archive_picker"] +[connection signal="pressed" from="Wallpaper/Welcome/OpenDirButton" to="." method="open_presentation_dir_picker"]