Partial source code rewrite

- [src/*] Update comments
- [src/*] Improve code quality
- [export_presets.cfg] All exports use a console wrapper now
- [src/console.gd] Add highlighting for "arbitrary" command
- [src/console.gd] Add special message if nothing has been typed in
- [src/misc.gd, src/console.gd] Move BooleanState, get_last() (now called get_shortened_array), get_boolean(), get_int() and get_int_direct() into misc
- [src/console_info.gd] Remove useless _init() function
- [src/console_info.gd] Make get_error_string() function more compact
- [Loader.tscn, src/loader.gd] Remove window size support and slow initialization functionality
- [src/loader.gd] Remove warning on startup (will be reimplemented at some point, most likely graphically)
- [src/loader.gd] Replaced append_log() function with a lambda
- [src/loader.gd] Implement argument parser with configuration support
- [src/loader.gd] Add Presencode version information as constants and format_version() function
- [src/misc.gd] Reorganize functions
- [src/pmana.gd] Add checks to avoid slide change collisions
- [src/pmana.gd] Replace logger.warn() calls to "await logger.error()" calls
- [src/pmana.gd, src/misc.gd, src/console.gd] Remove pmana.shutdown() function in favour of misc.shutdown() which now does the same
- [src/preader.gd] Fix resource loading issues for presentation directories
- [src/preader.gd] Move resource loading workaround into seperate function _read_resource_workaround()
- [src/processor.gd, src/console.gd, src/loader.gd, src/pmana.gd, src/ui_engine.gd] Move all _process() functions into seperate file
- [src/processor.gd, src/console.gd, src/loader.gd, src/pmana.gd, src/ui_engine.gd, src/ui/welcome.gd] Move everything dragging related (except variables) into processor.gd
- [ui/Welcome.tscn, src/ui/welcome.gd] Add support for presentation directories
- [src/ui/welcome.gd] Update splash text
This commit is contained in:
JeremyStar™ 2024-01-19 20:45:37 +01:00
parent 19d3e8a0d0
commit b5acd36479
21 changed files with 767 additions and 522 deletions

View file

@ -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"]

View file

@ -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)

View file

@ -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`

View file

@ -1,5 +1,5 @@
---
sidebar_position: 1
sidebar_position: 2
---
# Logger

View file

@ -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`
- description `Registers a new presentation controller`
- arguments
- `exitcode`
- type `int`
- mandatory `no`
- description `DUDE DO YOU DON'T KNOW WHAT A EXITCODE IS?!`

View file

@ -1,5 +1,5 @@
---
sidebar_position: 3
sidebar_position: 4
---
# Presentation Manager (pmana)

View file

@ -1,5 +1,5 @@
---
sidebar_position: 4
sidebar_position: 5
---
# Presentation Reader (preader)

View file

@ -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

View file

@ -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={

View file

@ -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()

View file

@ -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"):
# Toggle console visibility
func toggle_console() -> void:
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
# 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

View file

@ -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) + "\"")

View file

@ -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

View file

@ -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

View file

@ -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")

View file

@ -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)

View file

@ -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")

172
src/processor.gd Normal file
View file

@ -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 <https://www.gnu.org/licenses/>. ###
##############################################################################
### 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

View file

@ -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 <https://www.gnu.org/licenses/>. ###
##############################################################################
### 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,12 +148,17 @@ 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]" + [
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
"nice", # nice :)
"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.
@ -129,11 +173,13 @@ func update_splash() -> void:
"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]"
"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]"

View file

@ -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)

View file

@ -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"]