Update source code

- [Console.tscn, src/console.gd, src/console_info.gd, src/console_expressionscript.gd] Add (mostly uncommented) debugging console
- [example/test.gd] Optimize example presentation controller
- [src/loader.gd, src/pmana.gd] Move click overlay into Loader
- [src/pmana.gd] Add check against invalid controller scripts
- [src/pmana.gd] Create seperate pmana.shutdown() function to handle shutdowns during presentations
- [src/preader.gd] Add checks against empty manifest variable
- [src/preader.gd] Fix bug in get_authors()
- Change/optimize various other smaller things
This commit is contained in:
JeremyStar™ 2024-01-14 21:52:38 +01:00
parent 6f9bea644c
commit e158173104
13 changed files with 866 additions and 15 deletions

2
.gitignore vendored
View file

@ -1,2 +1,2 @@
# Godot 4+ specific ignores
.godot/
bin/

97
Console.tscn Normal file
View file

@ -0,0 +1,97 @@
[gd_scene load_steps=10 format=3 uid="uid://btyi16dkvbly1"]
[ext_resource type="Script" path="res://src/console.gd" id="1_viv5y"]
[ext_resource type="FontFile" uid="uid://bl4vgye7bg8kf" path="res://docs/static/assets/fonts/FiraCode-Regular.ttf" id="2_ebuk0"]
[ext_resource type="FontFile" uid="uid://b5qsng8gpvos3" path="res://docs/static/assets/fonts/FiraCode-Bold.ttf" id="3_102to"]
[ext_resource type="FontFile" uid="uid://c5jkbqx10tj8e" path="res://docs/static/assets/fonts/FiraCode-Medium.ttf" id="4_jqfsc"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_u40jo"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_wj4aw"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ime8b"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_hjtld"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_xa0gg"]
[node name="Console" type="ColorRect"]
offset_right = 767.0
offset_bottom = 426.0
script = ExtResource("1_viv5y")
[node name="Bar" type="ColorRect" parent="."]
layout_mode = 0
offset_left = 2.5
offset_top = 2.5
offset_right = 764.5
offset_bottom = 27.5
color = Color(0.839216, 0.0196078, 0.196078, 1)
[node name="X1" type="Line2D" parent="Bar"]
points = PackedVector2Array(760, 2, 739, 23)
width = 2.0
[node name="X2" type="Line2D" parent="Bar"]
points = PackedVector2Array(760, 23, 739, 2)
width = 2.0
[node name="CloseButton" type="Button" parent="Bar"]
layout_mode = 0
offset_left = 737.0
offset_right = 762.0
offset_bottom = 25.0
theme_override_styles/normal = SubResource("StyleBoxEmpty_u40jo")
theme_override_styles/hover = SubResource("StyleBoxEmpty_wj4aw")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_ime8b")
theme_override_styles/disabled = SubResource("StyleBoxEmpty_hjtld")
theme_override_styles/focus = SubResource("StyleBoxEmpty_xa0gg")
[node name="DragButton" type="Button" parent="Bar"]
layout_mode = 0
offset_right = 737.0
offset_bottom = 25.0
theme_override_styles/normal = SubResource("StyleBoxEmpty_u40jo")
theme_override_styles/hover = SubResource("StyleBoxEmpty_wj4aw")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_ime8b")
theme_override_styles/disabled = SubResource("StyleBoxEmpty_hjtld")
theme_override_styles/focus = SubResource("StyleBoxEmpty_xa0gg")
[node name="Shell" type="ColorRect" parent="."]
layout_mode = 0
offset_left = 2.5
offset_top = 27.5
offset_right = 764.5
offset_bottom = 423.5
color = Color(0, 0, 0, 1)
[node name="Output" type="RichTextLabel" parent="Shell"]
layout_mode = 0
offset_right = 762.0
offset_bottom = 361.0
theme_override_fonts/normal_font = ExtResource("2_ebuk0")
theme_override_fonts/bold_font = ExtResource("3_102to")
theme_override_fonts/italics_font = ExtResource("2_ebuk0")
theme_override_fonts/bold_italics_font = ExtResource("3_102to")
theme_override_fonts/mono_font = ExtResource("2_ebuk0")
theme_override_font_sizes/normal_font_size = 18
theme_override_font_sizes/bold_font_size = 18
theme_override_font_sizes/italics_font_size = 18
theme_override_font_sizes/bold_italics_font_size = 18
theme_override_font_sizes/mono_font_size = 18
bbcode_enabled = true
scroll_following = true
[node name="Input" type="TextEdit" parent="Shell"]
layout_mode = 0
offset_top = 361.0
offset_right = 762.0
offset_bottom = 396.0
theme_override_colors/background_color = Color(0, 0, 0, 1)
theme_override_colors/font_color = Color(1, 1, 1, 1)
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="text_changed" from="Shell/Input" to="." method="input_changed"]

View file

@ -0,0 +1,60 @@
---
sidebar_position: 3
---
# Debugging console
Presencode includes a debugging console that you can use to debug your presentation code, Presencode itself... or to have fun destroying Presencode from the inside.
## Accessing the debug console
To make the debug console visible, press the `^` key on your keyboard.
To get started, type `help` into the input box.
## Commands
You can use these commands inside the debugging console:
<table>
<tr>
<th>Command</th>
<th>Description</th>
<th>Arguments</th>
</tr>
<tr>
<td>clear</td>
<td>Clears the console output</td>
<td></td>
</tr>
<tr>
<td>exit</td>
<td>Starts a fresh console session</td>
<td></td>
</tr>
<tr>
<td>shutdown</td>
<td>Shuts Presencode down</td>
<td>[exitcode: int = 0]</td>
</tr>
<tr>
<td>help</td>
<td>Displays information about certain topics</td>
<td>[topic: String = "INDEX"]</td>
</tr>
<tr>
<td>config</td>
<td>Reads and writes to the in-memory configuration</td>
<td>[get &lt;key: String&gt;|set &lt;key: String&gt; &lt;value: Variant&gt;|list]</td>
</tr>
<tr>
<td>pmana</td>
<td>Controls the Presentation Manager</td>
<td>[register &lt;version: int&gt; &lt;slides: int&gt; &lt;animations: bool&gt; &lt;quit_last_slide: bool&gt; &lt;controller: NodePath&lt;String&gt;&gt;|unregister|change_slide &lt;slide: int&gt; [no_animations: bool = false]|clear_viewport|hide_log|show_log]</td>
</tr>
<tr>
<td>preader</td>
<td>Controls the Presentation Reader</td>
<td>&lt;get &lt;topic|authors|ratio|resolution&gt;&gt;</td>
</tr>
<tr>
<td>arbitrary</td>
<td>Execute arbitrary GDScript expressions</td>
<td>&lt;gdscript expression&gt;</td>
</tr>
</table>

View file

@ -78,6 +78,7 @@ func presentation_start(viewport_: Control) -> void:
## without calling this function.
func presentation_end() -> void:
logger.diag("presentation_end() called")
queue_free()
# Change the current slide to another one
func change_slide(new_slide: int) -> void:

View file

@ -70,6 +70,12 @@ content_scale_switch={
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":71,"physical_keycode":0,"key_label":0,"unicode":103,"echo":false,"script":null)
]
}
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)
]
}
[rendering]

View file

@ -46,7 +46,5 @@ func clicked() -> void:
pmana.change_slide(pmana.current_slide+1, true)
func _process(_delta: float) -> void:
# Move button to top
get_tree().root.move_child(self, get_tree().root.get_child_count(true))
# Scale clickoverlay to window size
size = DisplayServer.window_get_size()

457
src/console.gd Normal file
View file

@ -0,0 +1,457 @@
##############################################################################
### 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/console.gd (Debug console) ###
### ###
### This source file controls the debug console, which can be enabled by ###
### pressing the ^ key. ###
##############################################################################
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")
var expressionscript: Node = Node.new()
# Console information
@onready var info: ConsoleInfo = ConsoleInfo.new()
# States
var dragging: bool = false
var drag_area: Vector2 = Vector2(0, 0)
var cursor_origin: Vector2 = Vector2(0, 0)
func _ready() -> void:
logger.info("Initializing debug console")
position = Vector2(30, 30)
calculate_drag_area()
# Setup window dragging
$Bar/DragButton.connect("button_down", func():
dragging = true
cursor_origin = get_tree().root.get_viewport().get_mouse_position()
)
$Bar/DragButton.connect("button_up", func():
dragging = false
cursor_origin = Vector2(0,0)
)
expressionscript.name = "ExpressionScript"
expressionscript.set_script(ResourceLoader.load("res://src/console_expressionscript.gd"))
expressionscript.logger = get_node("/root/logger")
expressionscript.misc = get_node("/root/misc")
expressionscript.preader = get_node("/root/preader")
expressionscript.pmana = get_node("/root/pmana")
expressionscript.console = self
expressionscript.loader = get_node("/root/Presencode")
add_child(expressionscript)
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
# Prevent console from going out of scope
var position_new: Vector2 = check_violation()
if position != position_new:
position = position_new
# Set new cursor_origin
cursor_origin = cursor_position
# 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:
if input.text.contains("\n"):
input.text = input.text.replace("\n", "")
var input2: String = input.text
input.text = ""
input.editable = false
await process_command(input2.split(" ", false))
input.editable = true
# Process commands
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
match(command[0]):
"clear":
match(command.size()):
_: append_output(info.get_error_string(info.ConsoleError.TOO_MANY_ARGUMENTS))
output.text = ""
"exit":
match(command.size()):
_: append_output(info.get_error_string(info.ConsoleError.TOO_MANY_ARGUMENTS))
await process_command(PackedStringArray(["clear"]))
append_output("""[color=#d60532]Welcome to the Presencode debug console!
To get started, enter \"help\". To close the console, press the X button.[color=white]""")
"shutdown":
append_output("Shutting down...")
var exitcode: int = 0
match(command.size()):
1: exitcode = 0
2: exitcode = int(command[1])
_: append_output(info.get_error_string(info.ConsoleError.TOO_MANY_ARGUMENTS))
pmana.shutdown(exitcode)
"help":
match(command.size()):
1: append_output(info.get_help_topic(info.HelpTopic.INDEX))
2:
match(command[1]):
"index": append_output(info.get_help_topic(info.HelpTopic.INDEX))
"clear": append_output(info.get_help_topic(info.HelpTopic.CLEAR))
"exit": append_output(info.get_help_topic(info.HelpTopic.EXIT))
"shutdown": append_output(info.get_help_topic(info.HelpTopic.SHUTDOWN))
"help": append_output(info.get_help_topic(info.HelpTopic.HELP))
"config": append_output(info.get_help_topic(info.HelpTopic.CONFIG))
"pmana": append_output(info.get_help_topic(info.HelpTopic.PMANA))
"preader": append_output(info.get_help_topic(info.HelpTopic.PREADER))
"arbitrary": append_output(info.get_help_topic(info.HelpTopic.ARBITRARY))
_: append_output(info.get_error_string(info.ConsoleError.INVALID_HELP_TOPIC))
_: append_output(info.get_error_string(info.ConsoleError.TOO_MANY_ARGUMENTS))
"config":
match(command.size()):
1: append_output(info.get_error_string(ConsoleInfo.ConsoleError.TOO_FEW_ARGUMENTS))
_:
match(command[1]):
"list":
match(command.size()):
2:
append_output("""These configuration keys can be used:
Key | Type | Description
LOGGER_ENABLED | bool | Toggles the logger
LOGGER_DIAGNOSTIC | bool | Toggles diagnostic log output
LOGGER_COLORED | bool | Toggles colored log output
LOGGER_HARDFAIL | bool | Toggles hardfailing errors (don't set this to false)
LOGGER_LOGSTRING | String | The logging template, variables: runtime, time, file, function, line, color, type, message
PMANA_ALLOW_FULLSCREEN | bool | Toggles window mode switching
MISC_SHUTDOWN_INVISIBLE | bool | Toggles if the window should be made invisible on shutdown, displays white texture if disabled""")
_: append_output(info.get_error_string(ConsoleInfo.ConsoleError.TOO_MANY_ARGUMENTS))
"set":
match(command.size()):
2: append_output(info.get_error_string(ConsoleInfo.ConsoleError.TOO_FEW_ARGUMENTS))
3: append_output(info.get_error_string(ConsoleInfo.ConsoleError.TOO_FEW_ARGUMENTS))
_:
match(command[2]):
"LOGGER_ENABLED":
match(get_boolean(" ".join(get_last(command, 2)))):
BooleanState.TRUE:
logger.config_enabled = true
append_output("Set \"" + str(command[2]) + "\" to [b]true[/b].")
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:
logger.config_diagnostic = true
append_output("Set \"" + str(command[2]) + "\" to [b]true[/b]")
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:
logger.config_colored = true
append_output("Set \"" + str(command[2]) + "\" to [b]true[/b]")
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:
logger.config_hardfail = true
append_output("Set \"" + str(command[2]) + "\" to [b]true[/b]")
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)))
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:
pmana.config_allow_fullscreen = true
append_output("Set \"" + str(command[2]) + "\" to [b]true[/b]")
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:
misc.config_shutdown_invisible = true
append_output("Set \"" + str(command[2]) + "\" to [b]true[/b]")
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"}))
_: append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_CONFIG_KEY, {"key": command[2]}))
"get":
match(command.size()):
2: append_output(info.get_error_string(ConsoleInfo.ConsoleError.TOO_FEW_ARGUMENTS))
3:
match(command[2]):
"LOGGER_ENABLED": append_output("[b]" + command[2] + "[/b] is set to [b]" + str(logger.config_enabled) + "[/b]")
"LOGGER_DIAGNOSTIC": append_output("[b]" + command[2] + "[/b] is set to [b]" + str(logger.config_diagnostic) + "[/b]")
"LOGGER_COLORED": append_output("[b]" + command[2] + "[/b] is set to [b]" + str(logger.config_colored) + "[/b]")
"LOGGER_HARDFAIL": append_output("[b]" + command[2] + "[/b] is set to [b]" + str(logger.config_hardfail) + "[/b]")
"LOGGER_LOGSTRING": append_output("\"" + command[2] + "[/b] is set to [b]\"" + logger.config_logstring + "\"[/b]")
"PMANA_ALLOW_FULLSCREEN": append_output("[b]" + command[2] + "[/b] is set to [b]" + str(pmana.config_allow_fullscreen) + "[/b]")
"MISC_SHUTDOWN_INVISIBLE": append_output("[b]" + command[2] + "[/b] is set to [b]" + str(misc.config_shutdown_invisible) + "[/b]")
_: append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_CONFIG_KEY, {"key": command[2]}))
_: append_output(info.get_error_string(ConsoleInfo.ConsoleError.TOO_MANY_ARGUMENTS))
_: append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_ARGUMENT))
"pmana":
match(command.size()):
1: append_output(info.get_error_string(ConsoleInfo.ConsoleError.TOO_FEW_ARGUMENTS))
_:
match(command[1]):
"register":
match(command.size()):
1: append_output(info.get_error_string(ConsoleInfo.ConsoleError.TOO_FEW_ARGUMENTS))
2: append_output(info.get_error_string(ConsoleInfo.ConsoleError.TOO_FEW_ARGUMENTS))
3: append_output(info.get_error_string(ConsoleInfo.ConsoleError.TOO_FEW_ARGUMENTS))
4: append_output(info.get_error_string(ConsoleInfo.ConsoleError.TOO_FEW_ARGUMENTS))
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 animations_bool: bool = false
var quit_last_slide: BooleanState = get_boolean(command[5])
var quit_last_slide_bool: bool = false
var controller: NodePath = NodePath(command[6])
if version.x != 0:
append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_TYPE, {"expected_type": "int"}))
return
if slides.x != 0:
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
_:
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
_:
append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_TYPE, {"expected_type": "bool"}))
return
if get_node(controller) == null or typeof(get_node(controller)) != TYPE_OBJECT:
append_output(info.get_error_string(ConsoleInfo.ConsoleError.NOT_AN_OBJECT))
return
if get_node(controller).get_script() == null:
append_output(info.get_error_string(ConsoleInfo.ConsoleError.NO_SCRIPT_ATTACHED))
return
@warning_ignore("narrowing_conversion")
pmana.register(version.y, slides.y, animations_bool, quit_last_slide_bool, controller)
append_output("A new presentation controller has been registered. Look into the Presencode log output if nothing happens.")
_: append_output(info.get_error_string(ConsoleInfo.ConsoleError.TOO_MANY_ARGUMENTS))
"unregister":
if !pmana.registered: append_output(info.get_error_string(ConsoleInfo.ConsoleError.NO_CONTROLLER_REGISTERED))
else:
await pmana.unregister()
append_output("The current presentation controller has been unregistered.")
"change_slide":
match(command.size()):
3:
if !pmana.registered: append_output(info.get_error_string(ConsoleInfo.ConsoleError.NO_CONTROLLER_REGISTERED))
else:
var slide: Vector2i = 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"))
4:
if !pmana.registered: append_output(info.get_error_string(ConsoleInfo.ConsoleError.NO_CONTROLLER_REGISTERED))
else:
var slide: Vector2i = get_int_direct(command[2])
match(slide.x):
0:
var no_animations: BooleanState = get_boolean(command[3])
match(no_animations):
BooleanState.TRUE:
pmana.change_slide(slide.y, true)
append_output("Switched to slide [b]" + str(slide.y) + "[/b] without animations")
BooleanState.FALSE:
pmana.change_slide(slide.y, false)
append_output("Switched to slide [b]" + str(slide.y) + "[/b]")
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.get_error_string(ConsoleInfo.ConsoleError.TOO_MANY_ARGUMENTS))
"clear_viewport":
if !pmana.registered: append_output(info.get_error_string(ConsoleInfo.ConsoleError.NO_CONTROLLER_REGISTERED))
else:
pmana.clear_viewport()
append_output("The viewport has been cleared.")
"hide_log":
if !pmana.registered: append_output(info.get_error_string(ConsoleInfo.ConsoleError.NO_CONTROLLER_REGISTERED))
else:
pmana.hide_log()
append_output("The log output is now [b]invisible[/b].")
"show_log":
if !pmana.registered: append_output(info.get_error_string(ConsoleInfo.ConsoleError.NO_CONTROLLER_REGISTERED))
else:
pmana.show_log()
append_output("The log output is now [b]visible[/b].")
_: append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_ARGUMENT))
"preader":
match(command.size()):
1: append_output(info.get_error_string(ConsoleInfo.ConsoleError.TOO_FEW_ARGUMENTS))
_:
match(command[1]):
"get":
match(command.size()):
2: append_output(info.get_error_string(ConsoleInfo.ConsoleError.TOO_FEW_ARGUMENTS))
3:
match(command[2]):
"topic":
if preader.get_topic() == "": append_output(info.get_error_string(ConsoleInfo.ConsoleError.NO_PRESENTATION_OPEN))
else: append_output("The presentation topic is [b]" + preader.get_topic() + "[/b]")
"authors":
if preader.get_authors() == "": append_output(info.get_error_string(ConsoleInfo.ConsoleError.NO_PRESENTATION_OPEN))
else: append_output("The presentation author is/authors are: [b]" + preader.get_authors() + "[/b]")
"ratio":
if preader.get_ratio() == "": append_output(info.get_error_string(ConsoleInfo.ConsoleError.NO_PRESENTATION_OPEN))
else: append_output("The presentation ratio is [b]" + preader.get_ratio() + "[/b]")
"resolution":
if preader.get_ratio_resolution() == Vector2i(0, 0): append_output(info.get_error_string(ConsoleInfo.ConsoleError.NO_PRESENTATION_OPEN))
else:
var resolution: Vector2i = preader.get_ratio_resolution()
append_output("The presentation resolution is [b]" + str(resolution.x) + "x" + str(resolution.y) + "[/b].")
_: append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_ARGUMENT))
_: append_output(info.get_error_string(ConsoleInfo.ConsoleError.TOO_MANY_ARGUMENTS))
_: append_output(info.get_error_string(ConsoleInfo.ConsoleError.INVALID_ARGUMENT))
"arbitrary":
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: 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()}))
else:
var returned = expression.execute([], expressionscript)
if expression.has_execute_failed():
append_output(info.get_error_string(ConsoleInfo.ConsoleError.EXPRESSION_EXECUTION_FAILED, {"error": expression.get_error_text(), "returned": returned}))
else:
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

@ -0,0 +1,33 @@
##############################################################################
### 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/console_expressionscript.gd (Debug console expression playground) ###
### ###
### This file will be used as a script for executing arbitrary expressions ###
### by the debugging console. ###
##############################################################################
extends Node
# @GlobalScope
var logger: Node = null
var misc: Node = null
var preader: Node = null
var pmana: Node = null
# SceneTree
var console: Node = null
var loader: Node = null

156
src/console_info.gd Normal file
View file

@ -0,0 +1,156 @@
##############################################################################
### 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/console_info.gd (Debug console information) ###
### ###
### This source file holds console information such as help topics or ###
### console errors. ###
##############################################################################
extends Node
class_name ConsoleInfo
enum ConsoleError {
OK,
UNIMPLEMENTED,
INVALID_COMMAND,
TOO_MANY_ARGUMENTS,
TOO_FEW_ARGUMENTS,
INVALID_ARGUMENT,
INVALID_TYPE,
NOT_AN_OBJECT,
NO_SCRIPT_ATTACHED,
INVALID_HELP_TOPIC,
INVALID_CONFIG_KEY,
NO_PRESENTATION_OPEN,
CONTROLLER_ALREADY_REGISTERED,
NO_CONTROLLER_REGISTERED,
EXPRESSION_PARSING_FAILED,
EXPRESSION_EXECUTION_FAILED
}
enum HelpTopic {
INDEX,
CLEAR,
EXIT,
SHUTDOWN,
HELP,
CONFIG,
PMANA,
PREADER,
ARBITRARY
}
var error_color: String = "[color=red]"
var internal_error_color: String = "[b][color=red]"
func _init() -> void:
logger.diag("ConsoleInfo initialized")
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"]) + "]"
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))
func get_help_topic(topic: HelpTopic) -> String:
match(topic):
HelpTopic.INDEX:
return """Command | Description
clear | Clears the console output
exit | Starts a fresh session
shutdown | Shuts Presencode down
help | Displays information about certain topics
config | Reads and writes to the in-memory configuration
pmana | Controls the Presentation Manager
preader | Controls the Presentation Reader
arbitrary | Execute arbitrary GDScript expressions
To view more information about one topic, type \"help [topic]\"."""
HelpTopic.CLEAR:
return """clear
Clears the console output."""
HelpTopic.EXIT:
return """exit
Clears the console output and starts a fresh session."""
HelpTopic.SHUTDOWN:
return """shutdown [exitcode: int = 0]
Shuts Presencode down, accepts a exitcode ranging from 0-255."""
HelpTopic.HELP:
return """help [topic: String = "INDEX"]
Displays useful information about commands."""
HelpTopic.CONFIG:
return """config [get <key: String>|set <key: String> <value: Variant>|list]
Returns, lists or overwrites Presencode's configuration. Lives in memory and cannot be saved to persistent storage."""
HelpTopic.PMANA:
return """pmana [register <version: int> <slides: int> <animations: bool> <quit_last_slide: bool> <controller: NodePath<String>>|unregister|change_slide <slide: int> [no_animations: bool = false]|clear_viewport|hide_log|show_log]
Calls functions belonging to the Presentation Manager."""
HelpTopic.PREADER:
return """preader <get <topic|authors|ratio|resolution>>
Calls functions belonging to the Presentation Reader"""
HelpTopic.ARBITRARY:
return """arbitrary <expression>
Executes arbitrary GDScript expressions.
-> EXPERIMENTAL"""
_:
return generate_internal_error("Invalid HelpTopic \"" + str(topic) + "\"")

View file

@ -124,6 +124,9 @@ ONLY VIEW PRESENCODE PRESENTATIONS IF YOU TRUST THE
###################################################
""")
await get_tree().create_timer(5).timeout
logger.info("Injecting console")
var console: Control = 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
# Open presentation archive/directory
var path: String = "".join(OS.get_cmdline_user_args())
@ -142,6 +145,7 @@ ONLY VIEW PRESENCODE PRESENTATIONS IF YOU TRUST THE
preader.read_manifest()
preader.read_entrypoint()
# Update window properties
console.calculate_drag_area(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())

View file

@ -53,7 +53,7 @@ func get_origin(n: int = 0) -> Dictionary:
# Shutdown Presencode safely
func shutdown(exitcode: int = 0) -> void:
logger.info("Shutting down")
logger.info("Shutting down (code " + str(exitcode) + ")")
get_tree().paused = true
# Display white texture
var npr: NinePatchRect = NinePatchRect.new()

View file

@ -51,6 +51,9 @@ func register(version: int, slides_: int, animations_: bool, quit_last_slide_: b
# Check entrypoint version
if version != entrypoint_version:
await logger.error("Presentation entrypoint version does not match Presencode's entrypoint version")
# Check controller nodepath
if get_node(controller_) == null:
await logger.error("The presentation controller could not be located.")
# Set controller information
slides = slides_
animations = animations_
@ -75,17 +78,18 @@ func register(version: int, slides_: int, animations_: bool, quit_last_slide_: b
for function in lacking_functions:
logger.warn("- " + function)
logger.warn("Consult the documentation or take a look at the example presentation if you need help.")
# Create clickoverlay
clickoverlay = Button.new()
clickoverlay.name = "ClickOverlay"
clickoverlay.set_script(ResourceLoader.load("res://src/clickoverlay.gd"))
get_tree().root.add_child(clickoverlay)
await misc.shutdown(1)
# Create 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
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()
@ -103,10 +107,11 @@ func unregister() -> void:
animation_active = false
config_allow_fullscreen = true
# Remove clickoverlay
get_tree().root.remove_child(clickoverlay)
get_node("/root/Presencode/").remove_child(clickoverlay)
clickoverlay = null
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:
@ -126,8 +131,7 @@ func change_slide(slide: int, no_animations: bool = false) -> void:
# Quit after last slide?
if quit_last_slide:
logger.info("Ending presentation")
await controller.presentation_end()
await misc.shutdown(0)
await shutdown()
return
else:
logger.warn("Over max slides by " + str(slide-slides))
@ -138,8 +142,7 @@ 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 controller.presentation_end()
await misc.shutdown(0)
await shutdown()
return
# Slide is not over max slides, continue slide change
# Play switch away animation
@ -186,6 +189,12 @@ func show_log() -> void:
return
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:
# Fullscreen key combo

View file

@ -131,6 +131,7 @@ func read_entrypoint() -> misc.Error:
# Check if manifest is loaded in memory
if manifest == {}:
logger.error("Manifest not loaded in memory, please call read_manifest() first")
return misc.Error.PREADER_NO_MANIFEST
# Create empty script variable
var script: Script
if directorypath == "":
@ -169,21 +170,50 @@ func get_entrypoint() -> Node:
# Return presentation topic
func get_topic() -> String:
if !is_open:
logger.error("No presentation is currently opened")
return ""
# Check if manifest is loaded in memory
if manifest == {}:
logger.error("Manifest not loaded in memory, please call read_manifest() first")
return ""
return manifest["topic"]
# Return presentation authors
func get_authors() -> String:
if !is_open:
logger.error("No presentation is currently opened")
return ""
# Check if manifest is loaded in memory
if manifest == {}:
logger.error("Manifest not loaded in memory, please call read_manifest() first")
return ""
var authors: String = ""
for author in manifest["authors"]:
authors = authors + ", " + author
if authors == "": authors = author
else: authors = authors + ", " + author
return authors
# Return display ratio
func get_ratio() -> String:
if !is_open:
logger.error("No presentation is currently opened")
return ""
# Check if manifest is loaded in memory
if manifest == {}:
logger.error("Manifest not loaded in memory, please call read_manifest() first")
return ""
return manifest["ratio"]
# Return display resolution from display ratio
func get_ratio_resolution() -> Vector2i:
if !is_open:
logger.error("No presentation is currently opened")
return Vector2i(0, 0)
# Check if manifest is loaded in memory
if manifest == {}:
logger.error("Manifest not loaded in memory, please call read_manifest() first")
return Vector2i(0, 0)
match(manifest["ratio"]):
"16:9": return Vector2i(1920, 1080)
"4:3": return Vector2i(1920, 1440)