Sets up initial project structure
Initializes the project with core files including: - Editor configuration (.editorconfig, .gitattributes, .gitignore, .vscode/settings.json) - Log.gd addon for enhanced debugging - Loggie addon for advanced logging - Project assets folder
BIN
addons/loggie/assets/icon.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
34
addons/loggie/assets/icon.png.import
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://2fr6et0qni2y"
|
||||
path="res://.godot/imported/icon.png-57313fc4d67f18c33a83a3388ad36531.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/loggie/assets/icon.png"
|
||||
dest_files=["res://.godot/imported/icon.png-57313fc4d67f18c33a83a3388ad36531.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
addons/loggie/assets/logo.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
34
addons/loggie/assets/logo.png.import
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://duuj0ibyreygv"
|
||||
path="res://.godot/imported/logo.png-2771abf7e361d7f10d5859133bc43562.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/loggie/assets/logo.png"
|
||||
dest_files=["res://.godot/imported/logo.png-2771abf7e361d7f10d5859133bc43562.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
addons/loggie/assets/theme/checkbox/checkbox_checked.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
|
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bfnp2a0sbhi2x"
|
||||
path="res://.godot/imported/checkbox_checked.png-a77d9d083c4ebd2bf01389a1bba7091b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/loggie/assets/theme/checkbox/checkbox_checked.png"
|
||||
dest_files=["res://.godot/imported/checkbox_checked.png-a77d9d083c4ebd2bf01389a1bba7091b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
|
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dqf5cye21gyw8"
|
||||
path="res://.godot/imported/checkbox_checked_disabled.png-0752d58bb0616edc7e127ccd992cc02f.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/loggie/assets/theme/checkbox/checkbox_checked_disabled.png"
|
||||
dest_files=["res://.godot/imported/checkbox_checked_disabled.png-0752d58bb0616edc7e127ccd992cc02f.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
addons/loggie/assets/theme/checkbox/checkbox_unchecked.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
|
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bib1lwikra5kr"
|
||||
path="res://.godot/imported/checkbox_unchecked.png-4c82347fbe6c6b60f40739ece2ad0dba.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/loggie/assets/theme/checkbox/checkbox_unchecked.png"
|
||||
dest_files=["res://.godot/imported/checkbox_unchecked.png-4c82347fbe6c6b60f40739ece2ad0dba.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
|
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cloe7vx2ej0nf"
|
||||
path="res://.godot/imported/checkbox_unchecked_disabled.png-aa159b17a13862a51b7ba58c26a64745.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/loggie/assets/theme/checkbox/checkbox_unchecked_disabled.png"
|
||||
dest_files=["res://.godot/imported/checkbox_unchecked_disabled.png-aa159b17a13862a51b7ba58c26a64745.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
addons/loggie/assets/theme/fonts/PatrickHandSC-Regular.ttf
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
[remap]
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://btuvtbhws7p8s"
|
||||
path="res://.godot/imported/PatrickHandSC-Regular.ttf-a768b75713759378d46297f0740f6da5.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/loggie/assets/theme/fonts/PatrickHandSC-Regular.ttf"
|
||||
dest_files=["res://.godot/imported/PatrickHandSC-Regular.ttf-a768b75713759378d46297f0740f6da5.fontdata"]
|
||||
|
||||
[params]
|
||||
|
||||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
allow_system_fallback=true
|
||||
force_autohinter=false
|
||||
hinting=1
|
||||
subpixel_positioning=1
|
||||
keep_rounding_remainders=true
|
||||
oversampling=0.0
|
||||
Fallbacks=null
|
||||
fallbacks=[]
|
||||
Compress=null
|
||||
compress=true
|
||||
preload=[]
|
||||
language_support={}
|
||||
script_support={}
|
||||
opentype_features={}
|
||||
BIN
addons/loggie/assets/theme/fonts/coffee_soda.ttf
Normal file
35
addons/loggie/assets/theme/fonts/coffee_soda.ttf.import
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
[remap]
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://e3rpni7mpu0p"
|
||||
path="res://.godot/imported/coffee_soda.ttf-1c45a1f5c42cb2ffa335471f7a4924a3.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/loggie/assets/theme/fonts/coffee_soda.ttf"
|
||||
dest_files=["res://.godot/imported/coffee_soda.ttf-1c45a1f5c42cb2ffa335471f7a4924a3.fontdata"]
|
||||
|
||||
[params]
|
||||
|
||||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
allow_system_fallback=true
|
||||
force_autohinter=false
|
||||
hinting=1
|
||||
subpixel_positioning=1
|
||||
keep_rounding_remainders=true
|
||||
oversampling=0.0
|
||||
Fallbacks=null
|
||||
fallbacks=[]
|
||||
Compress=null
|
||||
compress=true
|
||||
preload=[]
|
||||
language_support={}
|
||||
script_support={}
|
||||
opentype_features={}
|
||||
14
addons/loggie/assets/theme/loggie_border_box.tres
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[gd_resource type="StyleBoxFlat" format=3 uid="uid://ckw36egxdynxc"]
|
||||
|
||||
[resource]
|
||||
draw_center = false
|
||||
border_width_left = 3
|
||||
border_width_top = 3
|
||||
border_width_right = 3
|
||||
border_width_bottom = 3
|
||||
border_color = Color(0.71, 0.52114, 0.1704, 0.772549)
|
||||
border_blend = true
|
||||
corner_radius_top_left = 10
|
||||
corner_radius_top_right = 10
|
||||
corner_radius_bottom_right = 10
|
||||
corner_radius_bottom_left = 10
|
||||
95
addons/loggie/assets/theme/loggie_theme.tres
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
[gd_resource type="Theme" load_steps=10 format=3 uid="uid://bntkg3oi4b314"]
|
||||
|
||||
[ext_resource type="FontFile" uid="uid://btuvtbhws7p8s" path="res://addons/loggie/assets/theme/fonts/PatrickHandSC-Regular.ttf" id="1_ucfms"]
|
||||
[ext_resource type="Texture2D" uid="uid://bfnp2a0sbhi2x" path="res://addons/loggie/assets/theme/checkbox/checkbox_checked.png" id="2_tqabx"]
|
||||
[ext_resource type="Texture2D" uid="uid://dqf5cye21gyw8" path="res://addons/loggie/assets/theme/checkbox/checkbox_checked_disabled.png" id="3_plx1a"]
|
||||
[ext_resource type="Texture2D" uid="uid://bib1lwikra5kr" path="res://addons/loggie/assets/theme/checkbox/checkbox_unchecked.png" id="4_yp55b"]
|
||||
[ext_resource type="Texture2D" uid="uid://cloe7vx2ej0nf" path="res://addons/loggie/assets/theme/checkbox/checkbox_unchecked_disabled.png" id="5_0424s"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_gb03k"]
|
||||
bg_color = Color(0.349691, 0.30101, 0.201282, 1)
|
||||
border_width_left = 4
|
||||
border_width_top = 4
|
||||
border_width_right = 4
|
||||
border_width_bottom = 4
|
||||
border_color = Color(0.361244, 0.323458, 0.213348, 1)
|
||||
corner_radius_top_left = 8
|
||||
corner_radius_top_right = 8
|
||||
corner_radius_bottom_right = 8
|
||||
corner_radius_bottom_left = 8
|
||||
expand_margin_left = 2.0
|
||||
expand_margin_top = 1.0
|
||||
expand_margin_right = 2.0
|
||||
expand_margin_bottom = 1.0
|
||||
anti_aliasing_size = 0.537
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_73pt3"]
|
||||
bg_color = Color(0.767332, 0.598222, 0.330864, 1)
|
||||
border_width_left = 4
|
||||
border_width_top = 4
|
||||
border_width_right = 4
|
||||
border_width_bottom = 4
|
||||
border_color = Color(0.943861, 0.775452, 0.522251, 1)
|
||||
corner_radius_top_left = 8
|
||||
corner_radius_top_right = 8
|
||||
corner_radius_bottom_right = 8
|
||||
corner_radius_bottom_left = 8
|
||||
expand_margin_left = 2.0
|
||||
expand_margin_top = 1.0
|
||||
expand_margin_right = 2.0
|
||||
expand_margin_bottom = 1.0
|
||||
anti_aliasing_size = 0.537
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xxk7l"]
|
||||
bg_color = Color(0.661808, 0.507874, 0.261375, 1)
|
||||
border_width_left = 4
|
||||
border_width_top = 4
|
||||
border_width_right = 4
|
||||
border_width_bottom = 4
|
||||
border_color = Color(0.83, 0.629555, 0.3071, 1)
|
||||
corner_radius_top_left = 8
|
||||
corner_radius_top_right = 8
|
||||
corner_radius_bottom_right = 8
|
||||
corner_radius_bottom_left = 8
|
||||
expand_margin_left = 2.0
|
||||
expand_margin_top = 1.0
|
||||
expand_margin_right = 2.0
|
||||
expand_margin_bottom = 1.0
|
||||
anti_aliasing_size = 0.537
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_i1fiu"]
|
||||
bg_color = Color(0.6, 0.4574, 0.228, 1)
|
||||
border_width_left = 4
|
||||
border_width_top = 4
|
||||
border_width_right = 4
|
||||
border_width_bottom = 4
|
||||
border_color = Color(0.83, 0.629555, 0.3071, 1)
|
||||
corner_radius_top_left = 8
|
||||
corner_radius_top_right = 8
|
||||
corner_radius_bottom_right = 8
|
||||
corner_radius_bottom_left = 8
|
||||
expand_margin_left = 2.0
|
||||
expand_margin_top = 1.0
|
||||
expand_margin_right = 2.0
|
||||
expand_margin_bottom = 1.0
|
||||
anti_aliasing_size = 0.537
|
||||
|
||||
[resource]
|
||||
Button/constants/outline_size = 6
|
||||
Button/font_sizes/font_size = 30
|
||||
Button/fonts/font = ExtResource("1_ucfms")
|
||||
Button/styles/disabled = SubResource("StyleBoxFlat_gb03k")
|
||||
Button/styles/focus = SubResource("StyleBoxFlat_73pt3")
|
||||
Button/styles/hover = SubResource("StyleBoxFlat_xxk7l")
|
||||
Button/styles/normal = SubResource("StyleBoxFlat_i1fiu")
|
||||
CheckBox/icons/checked = ExtResource("2_tqabx")
|
||||
CheckBox/icons/checked_disabled = ExtResource("3_plx1a")
|
||||
CheckBox/icons/radio_checked = ExtResource("2_tqabx")
|
||||
CheckBox/icons/radio_checked_disabled = ExtResource("3_plx1a")
|
||||
CheckBox/icons/radio_unchecked = ExtResource("4_yp55b")
|
||||
CheckBox/icons/radio_unchecked_disabled = ExtResource("5_0424s")
|
||||
CheckBox/icons/unchecked = ExtResource("4_yp55b")
|
||||
CheckBox/icons/unchecked_disabled = ExtResource("5_0424s")
|
||||
Label/colors/font_color = Color(0.980392, 0.843137, 0.619608, 1)
|
||||
Label/constants/outline_size = 3
|
||||
Label/font_sizes/font_size = 21
|
||||
BIN
addons/loggie/assets/updater_bg.png
Normal file
|
After Width: | Height: | Size: 685 KiB |
34
addons/loggie/assets/updater_bg.png.import
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cgh6hd3u8nlpj"
|
||||
path="res://.godot/imported/updater_bg.png-c90bec38cc1681b029907f694c893180.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/loggie/assets/updater_bg.png"
|
||||
dest_files=["res://.godot/imported/updater_bg.png-c90bec38cc1681b029907f694c893180.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
28
addons/loggie/channels/custom_channels/template.gd.example
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
extends LoggieMsgChannel
|
||||
|
||||
func _init() -> void:
|
||||
self.ID = "<your-custom-id>"
|
||||
|
||||
# Example - Customize preprocess flags:
|
||||
# self.preprocess_flags = LoggieEnums.PreprocessStep.APPEND_TIMESTAMPS | LoggieEnums.PreprocessStep.APPEND_DOMAIN_NAME | LoggieEnums.PreprocessStep.APPEND_CLASS_NAME
|
||||
|
||||
func send(msg : LoggieMsg, msg_type : LoggieEnums.MsgType):
|
||||
# Validate that the message is coming from a valid logger if you are going to be requiring access to a [Loggie] instance.
|
||||
var loggie = msg.get_logger() # Access a [Loggie] instance directly from the message.
|
||||
if loggie == null:
|
||||
push_error("Attempt to send a message that's coming from an invalid logger.")
|
||||
return
|
||||
|
||||
# To access the most recently preprocessed version of the message - use:
|
||||
# msg.last_preprocess_result
|
||||
|
||||
# To access a string version of the message (no preprocessing) - use:
|
||||
var msg_text = msg.string()
|
||||
|
||||
# Optionally
|
||||
# We can use `LoggieTools.convert_string_to_format_mode` to apply one of the Loggie format modes to it.
|
||||
# Explore more functions in [LoggieTools] and [LoggieMsg].
|
||||
# var converted = LoggieTools.convert_string_to_format_mode(msg.last_preprocess_result, loggie.settings.msg_format_mode)
|
||||
|
||||
# Do something with the message.
|
||||
print(msg_text)
|
||||
75
addons/loggie/channels/discord.gd
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
class_name DiscordLoggieMsgChannel extends LoggieMsgChannel
|
||||
|
||||
const discord_msg_character_limit = 2000 # The max. amount of characters the content of the message can contain before discord refuses to post it.
|
||||
var debug_domain = "_d_loggie_discord"
|
||||
var debug_enabled = false
|
||||
|
||||
func _init() -> void:
|
||||
self.ID = "discord"
|
||||
self.preprocess_flags = 0 # For this type of channel, this will be applied dynamically by Loggie after it loads LoggieSettings.
|
||||
|
||||
func send(msg : LoggieMsg, msg_type : LoggieEnums.MsgType):
|
||||
# Validate variables.
|
||||
var loggie = msg.get_logger()
|
||||
if loggie == null:
|
||||
push_error("Attempt to send a message that's coming from an invalid logger.")
|
||||
return
|
||||
|
||||
# Wait until loggie is inside tree so that we can use add_child(http) on it without errors.
|
||||
if !loggie.is_inside_tree():
|
||||
loggie.tree_entered.connect(func():
|
||||
send(msg, msg_type)
|
||||
, CONNECT_ONE_SHOT)
|
||||
return
|
||||
|
||||
var webhook_url = loggie.settings.discord_webhook_url_live if loggie.is_in_production() else loggie.settings.discord_webhook_url_dev
|
||||
if webhook_url == null or (webhook_url is String and webhook_url.is_empty()):
|
||||
push_error("Attempt to send a message to the Discord channel with an invalid webhook_url.")
|
||||
return
|
||||
|
||||
var output_text = LoggieTools.convert_string_to_format_mode(msg.last_preprocess_result, LoggieEnums.MsgFormatMode.MARKDOWN)
|
||||
|
||||
# Chunk the given string into chunks of maximum supported size by Discord, so we don't end up hitting the character limit
|
||||
# which would prevent the message from getting posted.
|
||||
var chunks = LoggieTools.chunk_string(output_text, discord_msg_character_limit)
|
||||
if chunks.size() > 1:
|
||||
loggie.debug("Chunking a long (", output_text.length(), "length ) message while sending to Discord into:", chunks.size(), "chunks.")
|
||||
for chunk : String in chunks:
|
||||
call_deferred("send_post_request", loggie, chunk, webhook_url)
|
||||
|
||||
func send_post_request(logger : Variant, output_text : String, webhook_url : String):
|
||||
# Enable debug messages if configured.
|
||||
logger.set_domain_enabled(debug_domain, debug_enabled)
|
||||
|
||||
# Create a new HTTPRequest POST request that will be sent to Discord and add it into the scenetree.
|
||||
var http = HTTPRequest.new()
|
||||
logger.add_child(http)
|
||||
|
||||
# When the request is completed, destroy it.
|
||||
http.request_completed.connect(func(result, response_code, headers, body):
|
||||
var debug_msg = logger.msg("HTTP Request Completed:").color(Color.ORANGE).header().domain(debug_domain).channel("terminal")
|
||||
debug_msg.nl().msg("Result:").color(Color.ORANGE).bold().space().msg(result).nl()
|
||||
debug_msg.msg("Response Code:").color(Color.ORANGE).bold().space().msg(response_code).nl()
|
||||
debug_msg.msg("Headers:").color(Color.ORANGE).bold().space().msg(headers).nl()
|
||||
debug_msg.msg("Body:").color(Color.ORANGE).bold().space().msg(body)
|
||||
debug_msg.debug()
|
||||
|
||||
## Inform the user about a received non-success response code.
|
||||
if response_code < 200 or response_code > 299:
|
||||
logger.msg("Discord responded with a non-success code: ").bold().msg(response_code, " - This is an indicator that something about the message you tried to send to Discord does not comply with their request body standards (e.g. content is too long, invalid format, etc.)").channel("terminal").warn()
|
||||
|
||||
http.queue_free()
|
||||
)
|
||||
|
||||
# Convert the [LoggieMsg]'s contents into markdown and post that to the target webhook url.
|
||||
var json = JSON.stringify({"content": output_text})
|
||||
var header = ["Content-Type: application/json"]
|
||||
|
||||
# Construct debug message.
|
||||
if debug_enabled:
|
||||
var debug_msg_post = logger.msg("Sending POST Request:").color(Color.CORNFLOWER_BLUE).header().channel("terminal").domain(debug_domain).nl()
|
||||
debug_msg_post.msg("JSON stringified (length {size}):".format({"size": output_text.length()})).color(Color.LIGHT_SLATE_GRAY).bold().space().msg(json).color(Color.SLATE_GRAY)
|
||||
debug_msg_post.debug()
|
||||
|
||||
# Send the request.
|
||||
http.request(webhook_url, header, HTTPClient.METHOD_POST, json)
|
||||
1
addons/loggie/channels/discord.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bqmohonxdfp2s
|
||||
65
addons/loggie/channels/slack.gd
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
class_name SlackLoggieMsgChannel extends LoggieMsgChannel
|
||||
|
||||
var debug_domain = "_d_loggie_slack"
|
||||
var debug_enabled = false
|
||||
|
||||
func _init() -> void:
|
||||
self.ID = "slack"
|
||||
self.preprocess_flags = 0 # For this type of channel, this will be applied dynamically by Loggie after it loads LoggieSettings.
|
||||
|
||||
func send(msg : LoggieMsg, msg_type : LoggieEnums.MsgType):
|
||||
# Validate variables.
|
||||
var loggie = msg.get_logger()
|
||||
if loggie == null:
|
||||
push_error("Attempt to send a message that's coming from an invalid logger.")
|
||||
return
|
||||
|
||||
# Wait until loggie is inside tree so that we can use add_child(http) on it without errors.
|
||||
if !loggie.is_inside_tree():
|
||||
loggie.tree_entered.connect(func():
|
||||
send(msg, msg_type)
|
||||
, CONNECT_ONE_SHOT)
|
||||
return
|
||||
|
||||
var webhook = loggie.settings.slack_webhook_url_live if loggie.is_in_production() else loggie.settings.slack_webhook_url_dev
|
||||
if webhook == null or (webhook is String and webhook.is_empty()):
|
||||
push_error("Attempt to send a message to the Slack channel with an invalid webhook.")
|
||||
return
|
||||
|
||||
# Enable debug messages if configured.
|
||||
loggie.set_domain_enabled(debug_domain, debug_enabled)
|
||||
|
||||
# Create a new HTTPRequest POST request that will be sent to Slack and add it into the scenetree.
|
||||
var http = HTTPRequest.new()
|
||||
loggie.add_child(http)
|
||||
|
||||
# When the request is completed, destroy it.
|
||||
http.request_completed.connect(func(result, response_code, headers, body):
|
||||
var debug_msg = loggie.msg("HTTP Request Completed:").color(Color.ORANGE).header().domain(debug_domain)
|
||||
debug_msg.nl().msg("Result:").color(Color.ORANGE).bold().space().msg(result).nl()
|
||||
debug_msg.msg("Response Code:").color(Color.ORANGE).bold().space().msg(response_code).nl()
|
||||
debug_msg.msg("Headers:").color(Color.ORANGE).bold().space().msg(headers).nl()
|
||||
debug_msg.msg("Body:").color(Color.ORANGE).bold().space().msg(body)
|
||||
debug_msg.debug()
|
||||
|
||||
## Inform the user about a received non-success response code.
|
||||
if response_code < 200 or response_code > 299:
|
||||
loggie.msg("Slack responded with a non-success code: ").bold().msg(response_code, " - This is an indicator that something about the message you tried to send to Slack does not comply with their request body standards (e.g. content is too long, invalid format, etc.)").channel("terminal").warn()
|
||||
|
||||
http.queue_free()
|
||||
)
|
||||
|
||||
# Convert the [LoggieMsg]'s contents into markdown and post that to the target webhook url.
|
||||
var md_text = LoggieTools.convert_string_to_format_mode(msg.last_preprocess_result, LoggieEnums.MsgFormatMode.PLAIN)
|
||||
var json = JSON.stringify({"text": md_text})
|
||||
var header = ["Content-Type: application/json"]
|
||||
|
||||
# Construct debug message.
|
||||
if debug_enabled:
|
||||
var debug_msg_post = loggie.msg("Sending POST Request:").color(Color.ORANGE).header().domain(debug_domain).nl()
|
||||
debug_msg_post.msg("Preprocessed message:").color(Color.ORANGE).bold().space().msg(msg.last_preprocess_result).nl()
|
||||
debug_msg_post.msg("JSON stringified:").color(Color.ORANGE).bold().space().msg(json)
|
||||
debug_msg_post.debug()
|
||||
|
||||
# Send the request.
|
||||
http.request(webhook, header, HTTPClient.METHOD_POST, json)
|
||||
1
addons/loggie/channels/slack.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cw3nwdhr5xmgi
|
||||
21
addons/loggie/channels/terminal.gd
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
class_name TerminalLoggieMsgChannel extends LoggieMsgChannel
|
||||
|
||||
func _init() -> void:
|
||||
self.ID = "terminal"
|
||||
self.preprocess_flags = 0 # For this type of channel, this will be applied dynamically by Loggie after it loads LoggieSettings.
|
||||
|
||||
func send(msg : LoggieMsg, msg_type : LoggieEnums.MsgType):
|
||||
var loggie = msg.get_logger()
|
||||
var text = LoggieTools.convert_string_to_format_mode(msg.last_preprocess_result, loggie.settings.msg_format_mode)
|
||||
|
||||
match loggie.settings.msg_format_mode:
|
||||
LoggieEnums.MsgFormatMode.ANSI, LoggieEnums.MsgFormatMode.BBCODE:
|
||||
print_rich(text)
|
||||
LoggieEnums.MsgFormatMode.PLAIN, _:
|
||||
print(text)
|
||||
|
||||
# Dump a non-preprocessed terminal-ready version of the message in additional ways if that has been configured.
|
||||
if msg_type == LoggieEnums.MsgType.ERROR and loggie.settings.print_errors_to_console:
|
||||
push_error(LoggieTools.convert_string_to_format_mode(msg.string(), LoggieEnums.MsgFormatMode.PLAIN))
|
||||
if msg_type == LoggieEnums.MsgType.WARNING and loggie.settings.print_warnings_to_console:
|
||||
push_warning(LoggieTools.convert_string_to_format_mode(msg.string(), LoggieEnums.MsgFormatMode.PLAIN))
|
||||
1
addons/loggie/channels/terminal.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bi8qfxuunsh1b
|
||||
52
addons/loggie/custom_settings.gd.example
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
class_name CustomLoggieSettings extends LoggieSettings
|
||||
|
||||
func load():
|
||||
# Omit settings from here to have them use their default value instead.
|
||||
# Otherwise, directly set the value of the setting to your liking.
|
||||
# Any variable in [LoggieSettings] except `project_settings` is a valid target to alter.
|
||||
# You could also have them loaded here in some custom way, for example, from a .json or .ini file.
|
||||
# See the documentation of the [LoggieSettings] class to see all available options and their descriptions.
|
||||
# This template doesn't contain *all* the available options.
|
||||
|
||||
# Example: Customize Loggie preferences.
|
||||
self.update_check_mode = LoggieEnums.UpdateCheckType.CHECK_AND_SHOW_UPDATER_WINDOW
|
||||
self.msg_format_mode = LoggieEnums.MsgFormatMode.BBCODE
|
||||
self.log_level = LoggieEnums.LogLevel.INFO
|
||||
self.show_loggie_specs = LoggieEnums.ShowLoggieSpecsMode.ESSENTIAL
|
||||
self.show_system_specs = true
|
||||
self.enforce_optimal_settings_in_release_build = true
|
||||
|
||||
# Example: Customize logging settings.
|
||||
self.print_errors_to_console = true
|
||||
self.print_warnings_to_console = true
|
||||
self.nameless_class_name_proxy = LoggieEnums.NamelessClassExtensionNameProxy.BASE_TYPE
|
||||
self.timestamps_use_utc = true
|
||||
|
||||
# Example: Customize formats.
|
||||
self.format_info_msg = "{msg}"
|
||||
self.format_notice_msg = "[b][color=cyan][NOTICE]:[/color][/b] {msg}"
|
||||
self.format_warning_msg = "[b][color=orange][WARN]:[/color][/b] {msg}"
|
||||
self.format_error_msg = "[b][color=red][ERROR]:[/color][/b] {msg}"
|
||||
self.format_debug_msg = "[b][color=pink][DEBUG]:[/color][/b] {msg}"
|
||||
|
||||
# Example: Customize boxes.
|
||||
self.h_separator_symbol = "-"
|
||||
self.box_characters_mode = LoggieEnums.BoxCharactersMode.COMPATIBLE
|
||||
|
||||
self.box_symbols_compatible = {
|
||||
"top_left" : "-",
|
||||
"top_right" : "-",
|
||||
"bottom_left" : "-",
|
||||
"bottom_right" : "-",
|
||||
"h_line" : "-",
|
||||
"v_line" : ":",
|
||||
}
|
||||
|
||||
self.box_symbols_pretty = {
|
||||
"top_left" : "┌",
|
||||
"top_right" : "┐",
|
||||
"bottom_left" : "└",
|
||||
"bottom_right" : "┘",
|
||||
"h_line" : "─",
|
||||
"v_line" : "│",
|
||||
}
|
||||
286
addons/loggie/loggie.gd
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
@tool
|
||||
|
||||
## Loggie is a basic logging utility for those who need common minor improvements and helpers around the basic [method print], [method print_rich]
|
||||
## and other default Godot printing functions. Loggie creates instances of [LoggieMsg], which are a wrapper around a string that needs to manipulated,
|
||||
## then uses them to properly format, arrange and present them in the console and .log files. Loggie uses the default Godot logging mechanism under the hood.
|
||||
extends Node
|
||||
|
||||
## The current version of Loggie.
|
||||
## Needs to be updated manually when changing the version.
|
||||
var version : LoggieVersion = LoggieVersion.new(2,0)
|
||||
|
||||
## Emitted any time Loggie attempts to log a message.
|
||||
## Useful for capturing the messages that pass through Loggie.
|
||||
## [br][param msg] is the message Loggie attempted to log (before any preprocessing).
|
||||
## [br][param preprocessed_content] is what the string content of that message contained after the preprocessing step,
|
||||
## which is what ultimately gets logged.
|
||||
## [br][param result] describes the final result of the attempt to log that message.
|
||||
signal log_attempted(msg : LoggieMsg, preprocessed_content : String, result : LoggieEnums.LogAttemptResult)
|
||||
|
||||
## A reference to the settings of this Loggie. Read more about [LoggieSettings].
|
||||
var settings : LoggieSettings
|
||||
|
||||
## Holds a mapping between all registered domains (string keys) and bool values representing whether
|
||||
## those domains are currently enabled. Enable domains with [method set_domain_enabled].
|
||||
## You can then place [LoggieMsg] messages into a domain by calling [method LoggieMsg.domain].
|
||||
## Messages belonging to a disabled domain will never be outputted.
|
||||
var domains : Dictionary = {}
|
||||
|
||||
## Holds a mapping between script paths and the names of the classes defined in those scripts.
|
||||
var class_names : Dictionary = {}
|
||||
|
||||
## Holds a mapping between channel IDs (string) and the
|
||||
## [LoggieMsgChannel] objects those IDs are representing.
|
||||
var available_channels = {}
|
||||
|
||||
## Stores a reference to a [LoggieVersionManager] that will be used to manage the
|
||||
## version of this instance.
|
||||
var version_manager : LoggieVersionManager = LoggieVersionManager.new()
|
||||
|
||||
func _init() -> void:
|
||||
# Connect the version manager to this logger.
|
||||
version_manager.connect_logger(self)
|
||||
|
||||
# Load and initialize the settings.
|
||||
var uses_original_settings_file = true
|
||||
var default_settings_path = get_script().get_path().get_base_dir().path_join("loggie_settings.gd")
|
||||
var custom_settings_path = get_script().get_path().get_base_dir().path_join("custom_settings.gd")
|
||||
|
||||
if self.settings == null:
|
||||
if custom_settings_path != null and custom_settings_path != "" and ResourceLoader.exists(custom_settings_path):
|
||||
var loaded_successfully = load_settings_from_path(custom_settings_path)
|
||||
if loaded_successfully:
|
||||
uses_original_settings_file = false
|
||||
|
||||
if uses_original_settings_file:
|
||||
var _settings = ResourceLoader.load(default_settings_path)
|
||||
if _settings != null:
|
||||
self.settings = _settings.new()
|
||||
self.settings.load()
|
||||
else:
|
||||
push_error("Loggie loaded neither a custom nor a default settings file. This will break the plugin. Make sure that a valid loggie_settings.gd is in the same directory where loggie.gd is.")
|
||||
return
|
||||
|
||||
# Enforce certain settings if configured to do so.
|
||||
if self.settings.enforce_optimal_settings_in_release_build == true and is_in_production():
|
||||
self.settings.msg_format_mode = LoggieEnums.MsgFormatMode.PLAIN
|
||||
self.settings.box_characters_mode = LoggieEnums.BoxCharactersMode.COMPATIBLE
|
||||
|
||||
# Set the default custom string converter.
|
||||
self.settings.custom_string_converter = LoggieTools.convert_to_string
|
||||
|
||||
# Install all the built-in channels.
|
||||
var terminal_channel : TerminalLoggieMsgChannel = load("res://addons/loggie/channels/terminal.gd").new()
|
||||
terminal_channel.preprocess_flags = self.settings.preprocess_flags_terminal_channel
|
||||
add_channel(terminal_channel)
|
||||
var discord_channel : DiscordLoggieMsgChannel = load("res://addons/loggie/channels/discord.gd").new()
|
||||
discord_channel.preprocess_flags = self.settings.preprocess_flags_discord_channel
|
||||
add_channel(discord_channel)
|
||||
var slack_channel : SlackLoggieMsgChannel = load("res://addons/loggie/channels/slack.gd").new()
|
||||
slack_channel.preprocess_flags = self.settings.preprocess_flags_slack_channel
|
||||
add_channel(slack_channel)
|
||||
|
||||
# Already cache the name of the singleton found at loggie's script path.
|
||||
class_names[self.get_script().resource_path] = LoggieSettings.loggie_singleton_name
|
||||
|
||||
# Prepopulate class data from ProjectSettings to avoid needing to read files.
|
||||
if OS.has_feature("debug"):
|
||||
for class_data: Dictionary in ProjectSettings.get_global_class_list():
|
||||
class_names[class_data.path] = class_data.class
|
||||
|
||||
for autoload_setting: String in ProjectSettings.get_property_list().map(func(prop): return prop.name).filter(func(prop): return prop.begins_with("autoload/") and ProjectSettings.has_setting(prop)):
|
||||
var autoload_class: String = autoload_setting.trim_prefix("autoload/")
|
||||
var class_path: String = ProjectSettings.get_setting(autoload_setting)
|
||||
class_path = class_path.trim_prefix("*")
|
||||
if not class_names.has(class_path):
|
||||
class_names[class_path] = autoload_class
|
||||
|
||||
# And don't proceed further if we're in Editor mode, since we don't need to show loggie boot messages in that case.
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
# Print the Loggie boot messages.
|
||||
if self.settings.show_loggie_specs != LoggieEnums.ShowLoggieSpecsMode.DISABLED:
|
||||
msg("👀 Loggie {version}{isproxy} booted.".format({
|
||||
"version" : self.version_manager.version,
|
||||
"isproxy" : " (proxy for {original})".format({"original": self.version_manager.version.proxy_for}) if self.version_manager.version.proxy_for != null else ""
|
||||
})).color(Color.ORANGE).header().nl().info()
|
||||
var loggie_specs_msg = LoggieSystemSpecsMsg.new().use_logger(self)
|
||||
loggie_specs_msg.add(msg("|\t Using Custom Settings File: ").bold(), !uses_original_settings_file).nl().add("|\t ").hseparator(35).nl()
|
||||
|
||||
match self.settings.show_loggie_specs:
|
||||
LoggieEnums.ShowLoggieSpecsMode.ESSENTIAL:
|
||||
loggie_specs_msg.embed_essential_logger_specs()
|
||||
LoggieEnums.ShowLoggieSpecsMode.ADVANCED:
|
||||
loggie_specs_msg.embed_advanced_logger_specs()
|
||||
|
||||
loggie_specs_msg.preprocessed(false).info()
|
||||
|
||||
if self.settings.show_system_specs:
|
||||
var system_specs_msg = LoggieSystemSpecsMsg.new().use_logger(self)
|
||||
system_specs_msg.embed_specs().preprocessed(false).info()
|
||||
|
||||
## Attempts to instantiate and use a LoggieSettings object from the script at the given [param path].
|
||||
## Returns true if successful, otherwise false and prints an error.
|
||||
func load_settings_from_path(path : String) -> bool:
|
||||
var settings_resource = ResourceLoader.load(path)
|
||||
var settings_instance
|
||||
|
||||
if settings_resource != null:
|
||||
settings_instance = settings_resource.new()
|
||||
|
||||
if (settings_instance is LoggieSettings):
|
||||
self.settings = settings_instance
|
||||
self.settings.load()
|
||||
return true
|
||||
else:
|
||||
push_error("Unable to instantiate a LoggieSettings object from the script at path {path}. Check that loggie.gd -> custom_settings_path is pointing to a valid .gd script that contains the class definition of a class that either extends LoggieSettings, or is LoggieSettings.".format({"path": path}))
|
||||
return false
|
||||
|
||||
## Checks if Loggie is running in production (release) mode of the game.
|
||||
## While it is, every [LoggieMsg] will have plain output.
|
||||
## Uses a sensible default check for most projects, but
|
||||
## you can rewrite this function to your needs if necessary.
|
||||
## TODO: Port this out of Loggie into LoggieSettings so users can override it easier.
|
||||
func is_in_production() -> bool:
|
||||
return OS.has_feature("release")
|
||||
|
||||
## Returns a custom list of channels that messages from the given [param domain_name] will be sent to.
|
||||
## This list can be set through the [method set_domain_enabled] method.
|
||||
## If the list is empty, default channels will be used.
|
||||
func get_domain_custom_target_channels(domain_name : String) -> Array:
|
||||
if domains.has(domain_name):
|
||||
return domains[domain_name].custom_target_channels
|
||||
return []
|
||||
|
||||
## Sets whether the domain with the given name is enabled.
|
||||
## If [param custom_target_channels] is provided, it will be used as the list of channels that messages from the given domain will be sent to.
|
||||
## It can be provided as a string (if only one channel is used), or an array of strings (if multiple channels are used).
|
||||
## Otherwise, the default channels will be used.
|
||||
func set_domain_enabled(domain_name : String, enabled : bool, custom_target_channels : Variant = []) -> void:
|
||||
var pruned_target_channels = []
|
||||
|
||||
if custom_target_channels is String:
|
||||
custom_target_channels = [custom_target_channels]
|
||||
|
||||
# Prune the array to ensure only string content is used.
|
||||
if custom_target_channels is Array:
|
||||
for entry in custom_target_channels:
|
||||
if entry is String or entry is StringName:
|
||||
pruned_target_channels.push_back(entry)
|
||||
else:
|
||||
push_error("Attempt to set a custom target channel for domain {domain_name} with an invalid value: {custom_target_channels}. The value must be a string or an array of strings. Default channels will be used instead.".format({
|
||||
"domain_name": domain_name,
|
||||
"custom_target_channels": custom_target_channels
|
||||
}))
|
||||
|
||||
domains[domain_name] = {"enabled": enabled, "custom_target_channels": pruned_target_channels}
|
||||
|
||||
## Checks whether the domain with the given name is enabled.
|
||||
## The domain name "" (empty string) is the default one for all newly created messages,
|
||||
## and is designed to always be enabled.
|
||||
func is_domain_enabled(domain_name : String) -> bool:
|
||||
if domain_name == "":
|
||||
return true
|
||||
|
||||
if domains.has(domain_name) and domains[domain_name].enabled == true:
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
## Returns an available channel with the given ID (if one exists), otherwise null.
|
||||
func get_channel(channel_id : String) -> LoggieMsgChannel:
|
||||
if available_channels.has(channel_id):
|
||||
return available_channels[channel_id]
|
||||
return null
|
||||
|
||||
## Adds a new channel for sending messages to.
|
||||
## Multiple channels with the same ID can not be added, so make sure your ID
|
||||
## does not clash with one of the existing channels' IDs, which are:
|
||||
## [param terminal], [param discord], [param slack].
|
||||
func add_channel(channel : LoggieMsgChannel):
|
||||
if not available_channels.has(channel.ID):
|
||||
available_channels[channel.ID] = channel
|
||||
else:
|
||||
push_error("Attempt to add a channel with ID {ID} failed, a channel with that ID already exists in Loggie.".format({
|
||||
"ID": channel.ID
|
||||
}))
|
||||
|
||||
## Creates a new [LoggieMsg] out of the given [param msg] and extra arguments (by converting them to strings and concatenating them to the msg).
|
||||
## You may continue to modify the [LoggieMsg] with additional functions from that class, then when you are ready to output it, use methods like:
|
||||
## [method LoggieMsg.info], [method LoggieMsg.warn], etc.
|
||||
func msg(message = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg:
|
||||
var loggieMsg = LoggieMsg.new(message, arg1, arg2, arg3, arg4, arg5)
|
||||
loggieMsg.use_logger(self)
|
||||
return loggieMsg
|
||||
|
||||
## A shortcut method that instantly creates a [LoggieMsg] with the given arguments and outputs it at the info level.
|
||||
## Can be used when you have no intention of customizing a LoggieMsg in any way using helper methods.
|
||||
## For customization, use [method msg] instead.
|
||||
func info(message = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg:
|
||||
return msg(message, arg1, arg2, arg3, arg4, arg5).info()
|
||||
|
||||
## A shortcut method that instantly creates a [LoggieMsg] with the given arguments and outputs it at the warn level.
|
||||
## Can be used when you have no intention of customizing a LoggieMsg in any way using helper methods.
|
||||
## For customization, use [method msg] instead.
|
||||
func warn(message = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg:
|
||||
return msg(message, arg1, arg2, arg3, arg4, arg5).warn()
|
||||
|
||||
## A shortcut method that instantly creates a [LoggieMsg] with the given arguments and outputs it at the error level.
|
||||
## Can be used when you have no intention of customizing a LoggieMsg in any way using helper methods.
|
||||
## For customization, use [method msg] instead.
|
||||
func error(message = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg:
|
||||
return msg(message, arg1, arg2, arg3, arg4, arg5).error()
|
||||
|
||||
## A shortcut method that instantly creates a [LoggieMsg] with the given arguments and outputs it at the debug level.
|
||||
## Can be used when you have no intention of customizing a LoggieMsg in any way using helper methods.
|
||||
## For customization, use [method msg] instead.
|
||||
func debug(message = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg:
|
||||
return msg(message, arg1, arg2, arg3, arg4, arg5).debug()
|
||||
|
||||
## A shortcut method that instantly creates a [LoggieMsg] with the given arguments and outputs it at the notice level.
|
||||
## Can be used when you have no intention of customizing a LoggieMsg in any way using helper methods.
|
||||
## For customization, use [method msg] instead.
|
||||
func notice(message = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg:
|
||||
return msg(message, arg1, arg2, arg3, arg4, arg5).notice()
|
||||
|
||||
## Returns the path to the directory from which within this script is running.
|
||||
func get_directory_path() -> String:
|
||||
return get_script().resource_path.get_base_dir()
|
||||
|
||||
## Returns a [LoggieMsg] that comes inserted with stylized content describing the stack trace obtained via [method get_stack].
|
||||
## This function only works in debug builds, and on the main thread, because it uses [method get_stack].
|
||||
## Read more about why in that function's documentation.
|
||||
func stack() -> LoggieMsg:
|
||||
if !OS.has_feature("debug"):
|
||||
return msg()
|
||||
|
||||
const FALLBACK_TXT_TO_FORMAT = "{index}: {fn_name}:{line} (in {source_path})"
|
||||
var stack = get_stack()
|
||||
var stack_msg = msg()
|
||||
|
||||
var text_to_format = settings.format_stacktrace_entry if is_instance_valid(settings) else FALLBACK_TXT_TO_FORMAT
|
||||
|
||||
stack.reverse()
|
||||
|
||||
for index in stack.size():
|
||||
var file_name = stack[index].source.get_file().get_basename()
|
||||
|
||||
if settings.skipped_filenames_in_stack_trace.has(file_name):
|
||||
continue
|
||||
|
||||
var entry_msg = LoggieMsg.new()
|
||||
entry_msg.add(text_to_format.format({
|
||||
"index": index,
|
||||
"source_path": stack[index].source,
|
||||
"fn_name": stack[index].function,
|
||||
"line": stack[index].line
|
||||
}))
|
||||
|
||||
if index == 0 or index < stack.size():
|
||||
entry_msg.prefix("\n ")
|
||||
entry_msg.endseg()
|
||||
|
||||
stack_msg.add(entry_msg)
|
||||
|
||||
return stack_msg
|
||||
1
addons/loggie/loggie.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dsexgf6nu6265
|
||||
429
addons/loggie/loggie_message.gd
Normal file
|
|
@ -0,0 +1,429 @@
|
|||
@tool
|
||||
|
||||
## LoggieMsg represents a mutable object that holds an array of strings ([member content]) [i](referred to as 'content segments')[/i], and
|
||||
## a bunch of helper methods that make it easy to manipulate these segments and chain together additions and changes to them.
|
||||
## [br][br]For example:
|
||||
## [codeblock]
|
||||
### Prints: "Hello world!" at the INFO debug level.
|
||||
##var msg = LoggieMsg.new("Hello world").color(Color.RED).suffix("!").info()
|
||||
##[/codeblock]
|
||||
## [br] You can also use [method Loggie.msg] to quickly construct a message.
|
||||
## [br] Example of usage:
|
||||
## [codeblock]Loggie.msg("Hello world").color(Color("#ffffff")).suffix("!").info()[/codeblock]
|
||||
class_name LoggieMsg extends RefCounted
|
||||
|
||||
## The full content of this message. By calling various helper methods in this class, this content is further altered.
|
||||
## The content is an array of strings which represents segments of the message which are ultimately appended together
|
||||
## to form the final message. You can start a new segment by calling [method msg] on this class.
|
||||
## You can then output the whole message with methods like [method info], [method debug], etc.
|
||||
var content : Array = [""]
|
||||
|
||||
## The segment of [member content] that is currently being edited.
|
||||
var current_segment_index : int = 0
|
||||
|
||||
## The (key string) domain this message belongs to.
|
||||
## "" is the default domain which is always enabled.
|
||||
## If this message attempts to be outputted, but belongs to a disabled domain, it will not be outputted.
|
||||
## You can change which domains are enabled in Loggie at any time with [Loggie.set_domain_enabled].
|
||||
## This is useful for creating blocks of debugging output that you can simply turn off/on with a boolean when you actually need them.
|
||||
var domain_name : String = ""
|
||||
|
||||
## Stores a reference to the logger that generated this message, from which we need to read settings and other data.
|
||||
## This variable should be set with [method use_logger] before an attempt is made to log this message out.
|
||||
var _logger : Variant
|
||||
|
||||
## Stores an array of IDs of all channels this message should be sent to when being outputted.
|
||||
var used_channels : Array = ["terminal"]
|
||||
|
||||
## Whether this message should be preprocessed and modified during [method output].
|
||||
var preprocess : bool = true
|
||||
|
||||
## Usually, the [LoggieMsgChannel] this message gets outputted on sets the preprocessing steps this message should use.
|
||||
## But sometimes we may want to use a specific set of preprocessing steps on this message,
|
||||
## overriding the channel's set of rules.
|
||||
## In that case, set this variable to the value of the [LoggieEnums.PreprocessStep] flags you want this message to use with
|
||||
## [method preprocess].
|
||||
var custom_preprocess_flags : int = -1
|
||||
|
||||
## Stores the string which was obtained the last time the [method get_preprocessed] was called on this message.
|
||||
## You need to call it at least once for this to have any results.
|
||||
var last_preprocess_result : String = ""
|
||||
|
||||
## Whether this message should append the stack trace during preprocessing.
|
||||
var appends_stack : bool = false
|
||||
|
||||
func _init(message = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> void:
|
||||
var args = [message, arg1, arg2, arg3, arg4, arg5]
|
||||
self.content[0] = LoggieTools.concatenate_args(args)
|
||||
self.set_meta("initial_args", args)
|
||||
|
||||
## Returns a reference to the logger object that created this message.
|
||||
func get_logger() -> Variant:
|
||||
return self._logger
|
||||
|
||||
## Sets this message to use the given [param logger] as the logger from which it will be reading
|
||||
## settings. The given logger should be of class [Loggie] or an extension of it.
|
||||
func use_logger(logger_to_use : Variant) -> LoggieMsg:
|
||||
self._logger = logger_to_use
|
||||
self.used_channels = self._logger.settings.default_channels
|
||||
|
||||
# Now that a logger is connected, we can re-format the first segment
|
||||
# using that Logger's converter function if it has a custom one defined.
|
||||
if self.has_meta("initial_args"):
|
||||
var initial_args = self.get_meta("initial_args")
|
||||
if initial_args is Array:
|
||||
var converter_fn = self._logger.settings.custom_string_converter if is_instance_valid(self._logger) and is_instance_valid(self._logger.settings) else null
|
||||
self.content[0] = LoggieTools.concatenate_args(initial_args, converter_fn)
|
||||
|
||||
return self
|
||||
|
||||
## Sets the list of channels this message should be sent to when outputted.
|
||||
## [param channels] should either be provided as a single channel ID (String), or
|
||||
## as an array of channel IDs (Array of strings).
|
||||
func channel(channels : Variant):
|
||||
if typeof(channels) != TYPE_ARRAY and typeof(channels) != TYPE_PACKED_STRING_ARRAY:
|
||||
channels = [str(channels)]
|
||||
self.used_channels = channels
|
||||
return self
|
||||
|
||||
## Returns a processed version of the content of this message, which has modifications applied to
|
||||
## it based on the requested [param level] and other settings defined by the provided preprocess [param flags].
|
||||
## Available preprocess flags are found in [enum LoggieEnums.PreprocessStep].
|
||||
func get_preprocessed(flags : int, level : LoggieEnums.LogLevel) -> String:
|
||||
var loggie = get_logger()
|
||||
var message = self.string()
|
||||
|
||||
match level:
|
||||
LoggieEnums.LogLevel.ERROR:
|
||||
message = loggie.settings.format_error_msg.format({"msg": message})
|
||||
LoggieEnums.LogLevel.WARN:
|
||||
message = loggie.settings.format_warning_msg.format({"msg": message})
|
||||
LoggieEnums.LogLevel.NOTICE:
|
||||
message = loggie.settings.format_notice_msg.format({"msg": message})
|
||||
LoggieEnums.LogLevel.INFO:
|
||||
message = loggie.settings.format_info_msg.format({"msg": message})
|
||||
LoggieEnums.LogLevel.DEBUG:
|
||||
message = loggie.settings.format_debug_msg.format({"msg": message})
|
||||
|
||||
if (flags & LoggieEnums.PreprocessStep.APPEND_DOMAIN_NAME != 0) and !self.domain_name.is_empty():
|
||||
message = _apply_format_domain(message)
|
||||
|
||||
if (flags & LoggieEnums.PreprocessStep.APPEND_CLASS_NAME != 0) and OS.has_feature("debug"):
|
||||
message = _apply_format_class_name(message)
|
||||
|
||||
if (flags & LoggieEnums.PreprocessStep.APPEND_TIMESTAMPS != 0):
|
||||
message = _apply_format_timestamp(message)
|
||||
|
||||
if self.appends_stack or (loggie.settings.debug_msgs_print_stack_trace and level == LoggieEnums.LogLevel.DEBUG):
|
||||
message = _apply_format_stack(message)
|
||||
|
||||
return message
|
||||
|
||||
## Outputs the given string [param message] at the given output [param level] to the standard output using either [method print_rich] or [method print].
|
||||
## The domain from which the message is considered to be coming can be provided via [param target_domain].
|
||||
## The classification of the message can be provided via [param msg_type], as certain types need extra handling and treatment.
|
||||
## It also does a number of changes to the given [param msg] based on various Loggie settings.
|
||||
## Designed to be called internally. You should consider using [method info], [method error], [method warn], [method notice], [method debug] instead.
|
||||
func output(level : LoggieEnums.LogLevel, msg_type : LoggieEnums.MsgType = LoggieEnums.MsgType.STANDARD) -> void:
|
||||
var loggie = get_logger()
|
||||
var message = self.string()
|
||||
var target_domain = self.domain_name
|
||||
var target_channels = self.used_channels
|
||||
|
||||
if loggie == null:
|
||||
push_error("Attempt to log output with an invalid _logger. Make sure to call LoggieMsg.use_logger to set the appropriate logger before working with the message.")
|
||||
return
|
||||
|
||||
if loggie.settings == null:
|
||||
push_error("Attempt to use a _logger with invalid settings to print: {msg}".format({"msg": message}))
|
||||
return
|
||||
|
||||
# We don't output the message if the settings dictate that messages of that level shouldn't be outputted.
|
||||
if level > loggie.settings.log_level:
|
||||
loggie.log_attempted.emit(self, message, LoggieEnums.LogAttemptResult.LOG_LEVEL_INSUFFICIENT)
|
||||
return
|
||||
|
||||
# We don't output the message if the domain from which it comes is not enabled.
|
||||
if not loggie.is_domain_enabled(target_domain):
|
||||
loggie.log_attempted.emit(self, message, LoggieEnums.LogAttemptResult.DOMAIN_DISABLED)
|
||||
return
|
||||
|
||||
# Send the message on all configured channels.
|
||||
var custom_target_channels = loggie.get_domain_custom_target_channels(target_domain)
|
||||
if custom_target_channels.size() > 0:
|
||||
target_channels = custom_target_channels
|
||||
|
||||
for channel_id : String in target_channels:
|
||||
var channel : LoggieMsgChannel = loggie.get_channel(channel_id)
|
||||
|
||||
if channel == null:
|
||||
loggie.log_attempted.emit(self, message, LoggieEnums.LogAttemptResult.INVALID_CHANNEL)
|
||||
continue
|
||||
|
||||
# Preprocessing Stage:
|
||||
# Apply full preprocessing only if explicitly enabled.
|
||||
# Otherwise, simply concatenate together all the [member content].
|
||||
if self.preprocess:
|
||||
var flags = self.custom_preprocess_flags if self.custom_preprocess_flags != -1 else channel.preprocess_flags
|
||||
self.last_preprocess_result = get_preprocessed(flags, level)
|
||||
else:
|
||||
self.last_preprocess_result = self.string()
|
||||
|
||||
channel.send(self, msg_type)
|
||||
|
||||
# Emit signal deferred so if this is called from a thread, it doesn't cry about it.
|
||||
loggie.call_deferred("emit_signal", "log_attempted", self, message, LoggieEnums.LogAttemptResult.SUCCESS)
|
||||
|
||||
## Outputs this message from Loggie as an Error type message.
|
||||
## The [Loggie.settings.log_level] must be equal to or higher to the ERROR level for this to work.
|
||||
func error() -> LoggieMsg:
|
||||
output(LoggieEnums.LogLevel.ERROR, LoggieEnums.MsgType.ERROR)
|
||||
return self
|
||||
|
||||
## Outputs this message from Loggie as an Warning type message.
|
||||
## The [Loggie.settings.log_level] must be equal to or higher to the WARN level for this to work.
|
||||
func warn() -> LoggieMsg:
|
||||
output(LoggieEnums.LogLevel.WARN, LoggieEnums.MsgType.WARNING)
|
||||
return self
|
||||
|
||||
## Outputs this message from Loggie as an Notice type message.
|
||||
## The [Loggie.settings.log_level] must be equal to or higher to the NOTICE level for this to work.
|
||||
func notice() -> LoggieMsg:
|
||||
output(LoggieEnums.LogLevel.NOTICE)
|
||||
return self
|
||||
|
||||
## Outputs this message from Loggie as an Info type message.
|
||||
## The [Loggie.settings.log_level] must be equal to or higher to the INFO level for this to work.
|
||||
func info() -> LoggieMsg:
|
||||
output(LoggieEnums.LogLevel.INFO)
|
||||
return self
|
||||
|
||||
## Outputs this message from Loggie as a Debug type message.
|
||||
## The [Loggie.settings.log_level] must be equal to or higher to the DEBUG level for this to work.
|
||||
func debug() -> LoggieMsg:
|
||||
output(LoggieEnums.LogLevel.DEBUG, LoggieEnums.MsgType.DEBUG)
|
||||
return self
|
||||
|
||||
## Returns the string content of this message.
|
||||
## If [param segment] is provided, it should be an integer indicating which segment of the message to return.
|
||||
## If its value is -1, all segments are concatenated together and returned.
|
||||
func string(segment : int = -1) -> String:
|
||||
if segment == -1:
|
||||
return "".join(self.content)
|
||||
else:
|
||||
if segment < self.content.size():
|
||||
return self.content[segment]
|
||||
else:
|
||||
push_error("Attempt to access a non-existent segment of a LoggieMsg. Make sure to use a valid segment index.")
|
||||
return ""
|
||||
|
||||
## Converts the current content of this message to an ANSI compatible form.
|
||||
func to_ANSI() -> LoggieMsg:
|
||||
var new_content : Array = []
|
||||
for segment in self.content:
|
||||
new_content.append(LoggieTools.rich_to_ANSI(segment))
|
||||
self.content = new_content
|
||||
return self
|
||||
|
||||
## Strips all the BBCode in the current content of this message.
|
||||
func strip_BBCode() -> LoggieMsg:
|
||||
var new_content : Array = []
|
||||
for segment in self.content:
|
||||
new_content.append(LoggieTools.remove_BBCode(segment))
|
||||
self.content = new_content
|
||||
return self
|
||||
|
||||
## Wraps the content of the current segment of this message in the given color.
|
||||
## The [param color] can be provided as a [Color], a recognized Godot color name (String, e.g. "red"), or a color hex code (String, e.g. "#ff0000").
|
||||
func color(_color : Variant) -> LoggieMsg:
|
||||
if _color is Color:
|
||||
_color = _color.to_html()
|
||||
|
||||
self.content[current_segment_index] = "[color={colorstr}]{msg}[/color]".format({
|
||||
"colorstr": _color,
|
||||
"msg": self.content[current_segment_index]
|
||||
})
|
||||
|
||||
return self
|
||||
|
||||
## Stylizes the current segment of this message to be bold.
|
||||
func bold() -> LoggieMsg:
|
||||
self.content[current_segment_index] = "[b]{msg}[/b]".format({"msg": self.content[current_segment_index]})
|
||||
return self
|
||||
|
||||
## Stylizes the current segment of this message to be italic.
|
||||
func italic() -> LoggieMsg:
|
||||
self.content[current_segment_index] = "[i]{msg}[/i]".format({"msg": self.content[current_segment_index]})
|
||||
return self
|
||||
|
||||
## Stylizes the current segment of this message as a header.
|
||||
func header() -> LoggieMsg:
|
||||
var loggie = get_logger()
|
||||
self.content[current_segment_index] = loggie.settings.format_header.format({"msg": self.content[current_segment_index]})
|
||||
return self
|
||||
|
||||
## Sets whether this message should append the stack trace during preprocessing.
|
||||
## If used in a different thread, it doesn't work, because it relies on [method get_stack] and
|
||||
## that method doesn't work within threads.
|
||||
func stack(enabled : bool = true) -> LoggieMsg:
|
||||
self.appends_stack = enabled
|
||||
return self
|
||||
|
||||
## Constructs a decorative box with the given horizontal padding around the current segment
|
||||
## of this message. Messages containing a box are not going to be preprocessed, so they are best
|
||||
## used only as a special header or decoration.
|
||||
func box(h_padding : int = 4):
|
||||
var loggie = get_logger()
|
||||
var stripped_content = LoggieTools.remove_BBCode(self.content[current_segment_index]).strip_edges(true, true)
|
||||
var content_length = stripped_content.length()
|
||||
var h_fill_length = content_length + (h_padding * 2)
|
||||
var box_character_source = loggie.settings.box_symbols_compatible if loggie.settings.box_characters_mode == LoggieEnums.BoxCharactersMode.COMPATIBLE else loggie.settings.box_symbols_pretty
|
||||
|
||||
var top_row_design = "{top_left_corner}{h_fill}{top_right_corner}".format({
|
||||
"top_left_corner" : box_character_source.top_left,
|
||||
"h_fill" : box_character_source.h_line.repeat(h_fill_length),
|
||||
"top_right_corner" : box_character_source.top_right
|
||||
})
|
||||
|
||||
var middle_row_design = "{vert_line}{padding}{content}{space_fill}{padding}{vert_line}".format({
|
||||
"vert_line" : box_character_source.v_line,
|
||||
"content" : self.content[current_segment_index],
|
||||
"padding" : " ".repeat(h_padding),
|
||||
"space_fill" : " ".repeat(h_fill_length - stripped_content.length() - h_padding*2)
|
||||
})
|
||||
|
||||
var bottom_row_design = "{bottom_left_corner}{h_fill}{bottom_right_corner}".format({
|
||||
"bottom_left_corner" : box_character_source.bottom_left,
|
||||
"h_fill" : box_character_source.h_line.repeat(h_fill_length),
|
||||
"bottom_right_corner" : box_character_source.bottom_right
|
||||
})
|
||||
|
||||
self.content[current_segment_index] = "{top_row}\n{middle_row}\n{bottom_row}\n".format({
|
||||
"top_row" : top_row_design,
|
||||
"middle_row" : middle_row_design,
|
||||
"bottom_row" : bottom_row_design
|
||||
})
|
||||
|
||||
self.preprocessed(false)
|
||||
return self
|
||||
|
||||
## Appends additional content to this message at the end of the current content and its stylings.
|
||||
## This does not create a new message segment, just appends to the current one.
|
||||
func add(message : Variant = null, arg1 : Variant = null, arg2 : Variant = null, arg3 : Variant = null, arg4 : Variant = null, arg5 : Variant = null) -> LoggieMsg:
|
||||
var converter_fn = self._logger.settings.custom_string_converter if is_instance_valid(self._logger) and is_instance_valid(self._logger.settings) else null
|
||||
self.content[current_segment_index] = self.content[current_segment_index] + LoggieTools.concatenate_args([message, arg1, arg2, arg3, arg4, arg5], converter_fn)
|
||||
return self
|
||||
|
||||
## Adds a specified amount of newlines to the end of the current segment of this message.
|
||||
func nl(amount : int = 1) -> LoggieMsg:
|
||||
self.content[current_segment_index] += "\n".repeat(amount)
|
||||
return self
|
||||
|
||||
## Adds a specified amount of spaces to the end of the current segment of this message.
|
||||
func space(amount : int = 1) -> LoggieMsg:
|
||||
self.content[current_segment_index] += " ".repeat(amount)
|
||||
return self
|
||||
|
||||
## Adds a specified amount of tabs to the end of the current segment of this message.
|
||||
func tab(amount : int = 1) -> LoggieMsg:
|
||||
self.content[current_segment_index] += "\t".repeat(amount)
|
||||
return self
|
||||
|
||||
## Sets this message to belong to the domain with the given name.
|
||||
## If it attempts to be outputted, but the domain is disabled, it won't be outputted.
|
||||
func domain(_domain_name : String) -> LoggieMsg:
|
||||
self.domain_name = _domain_name
|
||||
return self
|
||||
|
||||
## Prepends the given prefix string to the start of the message (first segment) with the provided separator.
|
||||
func prefix(str_prefix : String, separator : String = "") -> LoggieMsg:
|
||||
self.content[0] = "{prefix}{separator}{content}".format({
|
||||
"prefix" : str_prefix,
|
||||
"separator" : separator,
|
||||
"content" : self.content[0]
|
||||
})
|
||||
return self
|
||||
|
||||
## Appends the given suffix string to the end of the message (last segment) with the provided separator.
|
||||
func suffix(str_suffix : String, separator : String = "") -> LoggieMsg:
|
||||
self.content[self.content.size() - 1] = "{content}{separator}{suffix}".format({
|
||||
"suffix" : str_suffix,
|
||||
"separator" : separator,
|
||||
"content" : self.content[self.content.size() - 1]
|
||||
})
|
||||
return self
|
||||
|
||||
## Appends a horizontal separator with the given length to the current segment of this message.
|
||||
## If [param alternative_symbol] is provided, it should be a String, and it will be used as the symbol for the separator instead of the default one.
|
||||
func hseparator(size : int = 16, alternative_symbol : Variant = null) -> LoggieMsg:
|
||||
var loggie = get_logger()
|
||||
var symbol = loggie.settings.h_separator_symbol if alternative_symbol == null else str(alternative_symbol)
|
||||
self.content[current_segment_index] = self.content[current_segment_index] + (symbol.repeat(size))
|
||||
return self
|
||||
|
||||
## Ends the current segment of the message and starts a new one.
|
||||
func endseg() -> LoggieMsg:
|
||||
self.content.push_back("")
|
||||
self.current_segment_index = self.content.size() - 1
|
||||
return self
|
||||
|
||||
## Creates a new segment in this message and sets its content to the given message.
|
||||
## Acts as a shortcut for calling [method endseg] + [method add].
|
||||
func msg(message = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg:
|
||||
self.endseg()
|
||||
var converter_fn = self._logger.settings.custom_string_converter if is_instance_valid(self._logger) and is_instance_valid(self._logger.settings) else null
|
||||
self.content[current_segment_index] = LoggieTools.concatenate_args([message, arg1, arg2, arg3, arg4, arg5], converter_fn)
|
||||
return self
|
||||
|
||||
## Sets whether this message should be preprocessed and potentially modified with prefixes and suffixes during [method output].
|
||||
## If turned off, while outputting this message, Loggie will skip the steps where it appends the messaage domain, class name, timestamp, etc.
|
||||
## Whether preprocess is set to true doesn't affect the final conversion from RICH to ANSI or PLAIN, which always happens
|
||||
## under some circumstances that are based on other settings.
|
||||
func preprocessed(shouldPreprocess : bool) -> LoggieMsg:
|
||||
self.preprocess = shouldPreprocess
|
||||
return self
|
||||
|
||||
## Adds this message's configured domain to the start of the given [param message] and returns the modifier version of it.
|
||||
func _apply_format_domain(message : String) -> String:
|
||||
var loggie = get_logger()
|
||||
message = loggie.settings.format_domain_prefix.format({"domain" : self.domain_name, "msg" : message})
|
||||
return message
|
||||
|
||||
## Adds a formatted class name to the given [param message] and returns the modified version of it.
|
||||
func _apply_format_class_name(message : String) -> String:
|
||||
var loggie = get_logger()
|
||||
var stack_frame : Dictionary = LoggieTools.get_current_stack_frame_data()
|
||||
var _class_name : String
|
||||
|
||||
var scriptPath = stack_frame.source
|
||||
if loggie.class_names.has(scriptPath):
|
||||
_class_name = loggie.class_names[scriptPath]
|
||||
else:
|
||||
_class_name = LoggieTools.get_class_name_from_script(scriptPath, loggie.settings.nameless_class_name_proxy)
|
||||
loggie.class_names[scriptPath] = _class_name
|
||||
|
||||
if _class_name != "":
|
||||
message = "[b]({class_name})[/b] {msg}".format({
|
||||
"class_name" : _class_name,
|
||||
"msg" : message
|
||||
})
|
||||
return message
|
||||
|
||||
## Adds a formatted timestamp to the given [param message] and returns the modified version of it.
|
||||
func _apply_format_timestamp(message : String) -> String:
|
||||
var loggie = get_logger()
|
||||
var format_dict : Dictionary = Time.get_datetime_dict_from_system(loggie.settings.timestamps_use_utc)
|
||||
for field in ["month", "day", "hour", "minute", "second"]:
|
||||
format_dict[field] = "%02d" % format_dict[field]
|
||||
message = "{formatted_time} {msg}".format({
|
||||
"formatted_time" : loggie.settings.format_timestamp.format(format_dict),
|
||||
"msg" : message
|
||||
})
|
||||
return message
|
||||
|
||||
## Adds the stack trace to the given [param message] and returns the modified version of it.
|
||||
func _apply_format_stack(message : String) -> String:
|
||||
var loggie = get_logger()
|
||||
var stack_msg = loggie.stack()
|
||||
message = message + stack_msg.string()
|
||||
return message
|
||||
1
addons/loggie/loggie_message.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://d2s1jro33fhet
|
||||
24
addons/loggie/loggie_message_channel.gd
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
@tool
|
||||
|
||||
## A class that describes a channel that can be used to output Loggie Messages.
|
||||
class_name LoggieMsgChannel extends RefCounted
|
||||
|
||||
## The ID of the channel.
|
||||
var ID : String = ""
|
||||
|
||||
## The preprocessing steps a [method LoggieMsg] that's about to be
|
||||
## sent to this channel has to go through. See: [LoggieEnums.PreprocessStep] for
|
||||
## the list of flags that can be used.
|
||||
var preprocess_flags : int = 0
|
||||
|
||||
## Defines what happens when some [LoggieMsg] wants to be sent with this channel.
|
||||
## [br]If you're implementing your own channel, override this function to define
|
||||
## how your channel outputs the message.
|
||||
##
|
||||
## You can access the last known preprocessed version of the message
|
||||
## in [LoggieMsg.last_preprocess_result].
|
||||
##
|
||||
## If your channel requires extra data, the data can be embedded into a message
|
||||
## with [method LoggieMsg.set_meta] and read here with [method LoggieMsg.get_meta].
|
||||
func send(msg : LoggieMsg, type : LoggieEnums.MsgType):
|
||||
pass
|
||||
1
addons/loggie/loggie_message_channel.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://b70b2fx61qqj8
|
||||
521
addons/loggie/loggie_settings.gd
Normal file
|
|
@ -0,0 +1,521 @@
|
|||
@tool
|
||||
|
||||
## Defines a set of variables through which all the relevant settings of Loggie can have their
|
||||
## values set, read and documented. An instance of this class is found in [member Loggie.settings], and that's where Loggie
|
||||
## ultimately reads from when it's asking for the value of a setting. For user convenience, settings are (by default) exported
|
||||
## as custom Godot project settings and are loaded from there into these variables during [method load], however,
|
||||
## you can extend or overwrite this class' [method load] method to define a different way of loading these settings if you prefer.
|
||||
## [i](e.g. loading from a config.ini file, or a .json file, etc.)[/i].[br][br]
|
||||
##
|
||||
## Loggie calls [method load] on this class during its [method _ready] function.
|
||||
class_name LoggieSettings extends Resource
|
||||
|
||||
## The name that will be used for the singleton referring to Loggie.
|
||||
## [br][br][i][b]Note:[/b] You may change this to something you're more used to, such as "log" or "logger".[/i]
|
||||
## When doing so, make sure to either do it while the Plugin is enabled, then disable and re-enable the plugin,
|
||||
## or that you manually clear out the previously created autoload (should be called "Loggie") in Project Settings -> Autoloads.
|
||||
static var loggie_singleton_name = "Loggie"
|
||||
|
||||
# ----------------------------------------------- #
|
||||
#region Project Settings
|
||||
# ----------------------------------------------- #
|
||||
## The dictionary which is used to grab the defaults and other values associated with each setting
|
||||
## relevant to Loggie, particularly important for the default way of loading [LoggieSettings] and
|
||||
## setting up Godot Project Settings related to Loggie.
|
||||
const project_settings = {
|
||||
"update_check_mode" = {
|
||||
"path": "loggie/general/check_for_updates",
|
||||
"default_value" : LoggieEnums.UpdateCheckType.CHECK_AND_SHOW_UPDATER_WINDOW,
|
||||
"type" : TYPE_INT,
|
||||
"hint" : PROPERTY_HINT_ENUM,
|
||||
"hint_string" : "Never:0,Only print notice if available:1,Print notice and auto-install:2,Yes and show updater window:3",
|
||||
"doc" : "Sets which behavior Loggie should use when checking for updates.",
|
||||
},
|
||||
"remove_settings_if_plugin_disabled" = {
|
||||
"path": "loggie/general/remove_settings_if_plugin_disabled",
|
||||
"default_value" : true,
|
||||
"type" : TYPE_BOOL,
|
||||
"hint" : PROPERTY_HINT_NONE,
|
||||
"hint_string" : "",
|
||||
"doc" : "Choose whether you want Loggie project settings to be wiped from ProjectSettings if the Loggie plugin is disabled.",
|
||||
},
|
||||
"msg_format_mode" = {
|
||||
"path": "loggie/general/msg_format_mode",
|
||||
"default_value" : LoggieEnums.MsgFormatMode.BBCODE,
|
||||
"type" : TYPE_INT,
|
||||
"hint" : PROPERTY_HINT_ENUM,
|
||||
"hint_string" : "Plain:0,ANSI:1,BBCode:2,Markdown:3",
|
||||
"doc" : "Choose the format for which loggie should preprocess the output so that it displays correctly on the intended output medium.[br][br]Use BBCode for Godot console.[br]Use ANSI for Powershell, Bash, etc.[br]Use MARKDOWN for Discord.[br]Use PLAIN for log files.",
|
||||
},
|
||||
"log_level" = {
|
||||
"path": "loggie/general/log_level",
|
||||
"default_value" : LoggieEnums.LogLevel.INFO,
|
||||
"type" : TYPE_INT,
|
||||
"hint" : PROPERTY_HINT_ENUM,
|
||||
"hint_string" : "Error:0,Warn:1,Notice:2,Info:3,Debug:4",
|
||||
"doc" : "Choose the level of messages which should be displayed. Loggie displays all messages that are outputted at the currently set level (or any lower level).",
|
||||
},
|
||||
"show_system_specs" = {
|
||||
"path": "loggie/general/show_system_specs",
|
||||
"default_value" : true,
|
||||
"type" : TYPE_BOOL,
|
||||
"hint" : PROPERTY_HINT_NONE,
|
||||
"hint_string" : "",
|
||||
"doc" : "Should Loggie log the system and device specs of the user as soon as it is booted?",
|
||||
},
|
||||
"show_loggie_specs" = {
|
||||
"path": "loggie/general/show_loggie_specs",
|
||||
"default_value" : LoggieEnums.ShowLoggieSpecsMode.ESSENTIAL,
|
||||
"type" : TYPE_INT,
|
||||
"hint" : PROPERTY_HINT_ENUM,
|
||||
"hint_string" : "Disabled:0,Essential:1,Advanced:2",
|
||||
"doc" : "Defines which way Loggie should print its own specs when it is booted.",
|
||||
},
|
||||
"enforce_optimal_settings_in_release_build" = {
|
||||
"path": "loggie/general/enforce_optimal_settings_in_release_build",
|
||||
"default_value" : true,
|
||||
"type" : TYPE_BOOL,
|
||||
"hint" : PROPERTY_HINT_NONE,
|
||||
"hint_string" : "",
|
||||
"doc" : "Should Loggie enforce certain settings to automatically change to optimal values in production/release builds?",
|
||||
},
|
||||
"default_channels" = {
|
||||
"path": "loggie/general/default_channels",
|
||||
"default_value" : ["terminal"],
|
||||
"type" : TYPE_PACKED_STRING_ARRAY,
|
||||
"hint" : PROPERTY_HINT_TYPE_STRING,
|
||||
"hint_string" : "",
|
||||
"doc" : "The channels messages outputted from Loggie will be sent to by default (if not modified with LoggieMsg.channel method).",
|
||||
},
|
||||
"skipped_filenames_in_stack_trace" = {
|
||||
"path": "loggie/general/skipped_filenames_in_stack_trace",
|
||||
"default_value" : ["loggie", "loggie_message"],
|
||||
"type" : TYPE_PACKED_STRING_ARRAY,
|
||||
"hint" : PROPERTY_HINT_TYPE_STRING,
|
||||
"hint_string" : "",
|
||||
"doc" : "The file names, which, when appearing in a stack trace, should be omitted from the output.",
|
||||
},
|
||||
"discord_webhook_url_live" = {
|
||||
"path": "loggie/general/discord/live_webhook",
|
||||
"default_value" : "",
|
||||
"type" : TYPE_STRING,
|
||||
"hint" : PROPERTY_HINT_MULTILINE_TEXT,
|
||||
"hint_string" : "",
|
||||
"doc" : "The endpoint URL for the Discord webhook used when Loggie is running in a production build.",
|
||||
},
|
||||
"discord_webhook_url_dev" = {
|
||||
"path": "loggie/general/discord/dev_webhook",
|
||||
"default_value" : "",
|
||||
"type" : TYPE_STRING,
|
||||
"hint" : PROPERTY_HINT_MULTILINE_TEXT,
|
||||
"hint_string" : "",
|
||||
"doc" : "The endpoint URL for the Discord webhook used when Loggie is not running in a production build.",
|
||||
},
|
||||
"slack_webhook_url_live" = {
|
||||
"path": "loggie/general/slack/live_webhook",
|
||||
"default_value" : "",
|
||||
"type" : TYPE_STRING,
|
||||
"hint" : PROPERTY_HINT_MULTILINE_TEXT,
|
||||
"hint_string" : "",
|
||||
"doc" : "The endpoint URL for the Slack webhook used when Loggie is running in a production build.",
|
||||
},
|
||||
"slack_webhook_url_dev" = {
|
||||
"path": "loggie/general/slack/dev_webhook",
|
||||
"default_value" : "",
|
||||
"type" : TYPE_STRING,
|
||||
"hint" : PROPERTY_HINT_MULTILINE_TEXT,
|
||||
"hint_string" : "",
|
||||
"doc" : "The endpoint URL for the Slack webhook used when Loggie is not running in a production build.",
|
||||
},
|
||||
"timestamps_use_utc" = {
|
||||
"path": "loggie/preprocessing/timestamps_use_utc",
|
||||
"default_value" : true,
|
||||
"type" : TYPE_BOOL,
|
||||
"hint" : PROPERTY_HINT_NONE,
|
||||
"hint_string" : "",
|
||||
"doc" : "If 'Output Timestamps' is true, should those timestamps use the UTC time. If not, local system time is used instead.",
|
||||
},
|
||||
"output_errors_to_console" = {
|
||||
"path": "loggie/preprocessing/terminal/output_errors_also_to_console",
|
||||
"default_value" : true,
|
||||
"type" : TYPE_BOOL,
|
||||
"hint" : PROPERTY_HINT_NONE,
|
||||
"hint_string" : "",
|
||||
"doc" : "If true, errors printed by Loggie will also be visible through an additional print in the main output.",
|
||||
},
|
||||
"output_warnings_to_console" = {
|
||||
"path": "loggie/preprocessing/terminal/output_warnings_also_to_console",
|
||||
"default_value" : true,
|
||||
"type" : TYPE_BOOL,
|
||||
"hint" : PROPERTY_HINT_NONE,
|
||||
"hint_string" : "",
|
||||
"doc" : "If true, warnings printed by Loggie will also be visible through an additional print in the main output.",
|
||||
},
|
||||
"debug_msgs_print_stack_trace" = {
|
||||
"path": "loggie/preprocessing/terminal/debug_msgs_print_stack_trace",
|
||||
"default_value" : false,
|
||||
"type" : TYPE_BOOL,
|
||||
"hint" : PROPERTY_HINT_NONE,
|
||||
"hint_string" : "",
|
||||
"doc" : "If true, 'debug' level messages outputted by Loggie will also print the stack trace.",
|
||||
},
|
||||
"nameless_class_name_proxy" = {
|
||||
"path": "loggie/preprocessing/nameless_class_name_proxy",
|
||||
"default_value" : LoggieEnums.NamelessClassExtensionNameProxy.BASE_TYPE,
|
||||
"type" : TYPE_INT,
|
||||
"hint" : PROPERTY_HINT_ENUM,
|
||||
"hint_string" : "Nothing:0,ScriptName:1,BaseType:2",
|
||||
"doc" : "If 'Derive and Display Class Names From Scripts' is enabled, and a script doesn't have a 'class_name', which text should we use as a substitute?",
|
||||
},
|
||||
"preprocess_flags_terminal_channel" = {
|
||||
"path": "loggie/preprocessing/terminal/preprocess_flags",
|
||||
"default_value" : LoggieEnums.PreprocessStep.APPEND_TIMESTAMPS | LoggieEnums.PreprocessStep.APPEND_DOMAIN_NAME | LoggieEnums.PreprocessStep.APPEND_CLASS_NAME,
|
||||
"type" : TYPE_INT,
|
||||
"hint" : PROPERTY_HINT_FLAGS,
|
||||
"hint_string" : "Append Timestamp:1,Append Domain Name:2,Append Class Name:4",
|
||||
"doc" : "Defines the flags which LoggieMessages sent to the terminal channel will use during preprocessing.",
|
||||
},
|
||||
"preprocess_flags_discord_channel" = {
|
||||
"path": "loggie/preprocessing/discord/preprocess_flags",
|
||||
"default_value" : LoggieEnums.PreprocessStep.APPEND_DOMAIN_NAME | LoggieEnums.PreprocessStep.APPEND_CLASS_NAME,
|
||||
"type" : TYPE_INT,
|
||||
"hint" : PROPERTY_HINT_FLAGS,
|
||||
"hint_string" : "Append Timestamp:1,Append Domain Name:2,Append Class Name:4",
|
||||
"doc" : "Defines the flags which LoggieMessages sent to the Discord channel will use during preprocessing.",
|
||||
},
|
||||
"preprocess_flags_slack_channel" = {
|
||||
"path": "loggie/preprocessing/slack/preprocess_flags",
|
||||
"default_value" : LoggieEnums.PreprocessStep.APPEND_DOMAIN_NAME | LoggieEnums.PreprocessStep.APPEND_CLASS_NAME,
|
||||
"type" : TYPE_INT,
|
||||
"hint" : PROPERTY_HINT_FLAGS,
|
||||
"hint_string" : "Append Timestamp:1,Append Domain Name:2,Append Class Name:4",
|
||||
"doc" : "Defines the flags which LoggieMessages sent to the Slack channel will use during preprocessing.",
|
||||
},
|
||||
"format_timestamp" = {
|
||||
"path": "loggie/formats/timestamp",
|
||||
"default_value" : "[{day}.{month}.{year} {hour}:{minute}:{second}]",
|
||||
"type" : TYPE_STRING,
|
||||
"hint" : PROPERTY_HINT_NONE,
|
||||
"hint_string" : "",
|
||||
"doc" : "The format used for timestamps which are prepended to the message when the appending of timestamps is enabled.",
|
||||
},
|
||||
"format_stacktrace_entry" = {
|
||||
"path": "loggie/formats/stacktrace_entry",
|
||||
"default_value" : "{index}: [color=#ff7085]func[/color] [color=#53b1c3][b]{fn_name}[/b]:{line}[/color] [color=slate_gray][i](in {source_path})[/i][/color]",
|
||||
"type" : TYPE_STRING,
|
||||
"hint" : PROPERTY_HINT_NONE,
|
||||
"hint_string" : "",
|
||||
"doc" : "The format used for stack trace entries when trace logging is enabled.",
|
||||
},
|
||||
"format_debug_msg" = {
|
||||
"path": "loggie/formats/debug_message",
|
||||
"default_value" : "[b][color=pink][DEBUG]:[/color][/b] {msg}",
|
||||
"type" : TYPE_STRING,
|
||||
"hint" : PROPERTY_HINT_NONE,
|
||||
"hint_string" : "",
|
||||
"doc" : "The format used for debug messages.",
|
||||
},
|
||||
"format_info_msg" = {
|
||||
"path": "loggie/formats/info_message",
|
||||
"default_value" : "{msg}",
|
||||
"type" : TYPE_STRING,
|
||||
"hint" : PROPERTY_HINT_NONE,
|
||||
"hint_string" : "",
|
||||
"doc" : "The format used for info messages.",
|
||||
},
|
||||
"format_notice_msg" = {
|
||||
"path": "loggie/formats/notice_message",
|
||||
"default_value" : "[b][color=cyan][NOTICE]:[/color][/b] {msg}",
|
||||
"type" : TYPE_STRING,
|
||||
"hint" : PROPERTY_HINT_NONE,
|
||||
"hint_string" : "",
|
||||
"doc" : "The format used for notice messages.",
|
||||
},
|
||||
"format_warning_msg" = {
|
||||
"path": "loggie/formats/warning_message",
|
||||
"default_value" : "[b][color=orange][WARN]:[/color][/b] {msg}",
|
||||
"type" : TYPE_STRING,
|
||||
"hint" : PROPERTY_HINT_NONE,
|
||||
"hint_string" : "",
|
||||
"doc" : "The format used for warning messages.",
|
||||
},
|
||||
"format_error_msg" = {
|
||||
"path": "loggie/formats/error_message",
|
||||
"default_value" : "[b][color=red][ERROR]:[/color][/b] {msg}",
|
||||
"type" : TYPE_STRING,
|
||||
"hint" : PROPERTY_HINT_NONE,
|
||||
"hint_string" : "",
|
||||
"doc" : "The format used for error messages.",
|
||||
},
|
||||
"format_domain_prefix" = {
|
||||
"path": "loggie/formats/domain_prefix",
|
||||
"default_value" : "[b]({domain})[/b] {msg}",
|
||||
"type" : TYPE_STRING,
|
||||
"hint" : PROPERTY_HINT_NONE,
|
||||
"hint_string" : "",
|
||||
"doc" : "The format used for domain prefixes.",
|
||||
},
|
||||
"format_header" = {
|
||||
"path": "loggie/formats/header",
|
||||
"default_value" : "[b][i]{msg}[/i][/b]",
|
||||
"type" : TYPE_STRING,
|
||||
"hint" : PROPERTY_HINT_NONE,
|
||||
"hint_string" : "",
|
||||
"doc" : "The format used for headers.",
|
||||
},
|
||||
"h_separator_symbol" = {
|
||||
"path": "loggie/formats/h_separator_symbol",
|
||||
"default_value" : "-",
|
||||
"type" : TYPE_STRING,
|
||||
"hint" : PROPERTY_HINT_NONE,
|
||||
"hint_string" : "",
|
||||
"doc" : "The symbol used for the horizontal separator.",
|
||||
},
|
||||
"box_characters_mode" = {
|
||||
"path": "loggie/formats/box_characters_mode",
|
||||
"default_value" : LoggieEnums.BoxCharactersMode.COMPATIBLE,
|
||||
"type" : TYPE_INT,
|
||||
"hint" : PROPERTY_HINT_ENUM,
|
||||
"hint_string" : "Compatible:0,Pretty:1",
|
||||
"doc" : "There are two sets of box characters defined in LoggieSettings - one set contains prettier characters that produce a nicer looking box, but may not render correctly in the context of various terminals. The other set contains characters that produce a less pretty box, but are compatible with being shown in most terminals.",
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
# ----------------------------------------------- #
|
||||
#region Variables
|
||||
# ----------------------------------------------- #
|
||||
|
||||
## The current behavior Loggie uses to check for updates.
|
||||
var update_check_mode : LoggieEnums.UpdateCheckType = LoggieEnums.UpdateCheckType.CHECK_AND_SHOW_UPDATER_WINDOW
|
||||
|
||||
## The current Message Format Mode of Loggie.
|
||||
## Message Format Mode determines whether BBCode, ANSI or some other type of
|
||||
## formatting is used to convey text effects, such as bold, italic, colors, etc.
|
||||
## [br][br]BBCode is compatible with the Godot console.
|
||||
## [br]ANSI is compatible with consoles like Powershell and Windows CMD.
|
||||
## [br]PLAIN is used to strip any effects and use plain text instead, which is good for saving raw logs into log files.
|
||||
var msg_format_mode : LoggieEnums.MsgFormatMode = LoggieEnums.MsgFormatMode.BBCODE
|
||||
|
||||
## The current log level of Loggie.
|
||||
## It determines which types of messages are allowed to be logged.
|
||||
## Set this using [method setLogLevel].
|
||||
var log_level : LoggieEnums.LogLevel = LoggieEnums.LogLevel.INFO
|
||||
|
||||
## Whether or not Loggie should log the loggie specs on ready.
|
||||
var show_loggie_specs : LoggieEnums.ShowLoggieSpecsMode = LoggieEnums.ShowLoggieSpecsMode.ESSENTIAL
|
||||
|
||||
## Whether or not Loggie should log the system specs on ready.
|
||||
var show_system_specs : bool = true
|
||||
|
||||
## Whether to, in addition to logging errors with [method push_error],
|
||||
## Loggie should also print the error as a message in the standard output.
|
||||
var print_errors_to_console : bool = true
|
||||
|
||||
## Whether to, in addition to logging errors with [method push_warning],
|
||||
## Loggie should also print the error as a message in the standard output.
|
||||
var print_warnings_to_console : bool = true
|
||||
|
||||
## Defines which text will be used as a substitute for the 'class_name' of scripts that do not have a 'class_name'.
|
||||
## Relevant only during the [member LoggieEnums.PreprocessStep.APPEND_CLASS_NAME] step of preprocessing.
|
||||
var nameless_class_name_proxy : LoggieEnums.NamelessClassExtensionNameProxy
|
||||
|
||||
## Whether the outputted timestamps use UTC or local machine time.
|
||||
var timestamps_use_utc : bool = true
|
||||
|
||||
## If true, when outputting Debug level messages, the stack trace will also be appended.
|
||||
var debug_msgs_print_stack_trace : bool = false
|
||||
|
||||
## Whether Loggie should enforce optimal values for certain settings when in a Release/Production build.
|
||||
## [br]If true, Loggie will enforce:
|
||||
## [br] * [member msg_format_mode] to [member LoggieEnums.MsgFormatMode.PLAIN]
|
||||
## [br] * [member box_characters_mode] to [member LoggieEnums.BoxCharactersMode.COMPATIBLE]
|
||||
var enforce_optimal_settings_in_release_build : bool = true
|
||||
|
||||
## Endpoint URL for the Discord webhook (used in dev environment)
|
||||
## [br][b]NEVER[/b] distribute your webhook in your project's repository, source code, or built game, where it can be accessed by other people.
|
||||
## This is meant to be used only in controlled circumstances.
|
||||
var discord_webhook_url_dev : String = ""
|
||||
|
||||
## Endpoint URL for the Discord webhook (used in production/release environment)
|
||||
## [br][b]NEVER[/b] distribute your webhook in your project's repository, source code, or built game, where it can be accessed by other people.
|
||||
## This is meant to be used only in controlled circumstances.
|
||||
var discord_webhook_url_live : String = ""
|
||||
|
||||
## Endpoint URL for the Slack webhook (used in dev environment)
|
||||
## [br][b]NEVER[/b] distribute your webhook in your project's repository, source code, or built game, where it can be accessed by other people.
|
||||
## This is meant to be used only in controlled circumstances.
|
||||
var slack_webhook_url_dev : String = ""
|
||||
|
||||
## Endpoint URL for the Slack webhook (used in production/release environment)
|
||||
## [br][b]NEVER[/b] distribute your webhook in your project's repository, source code, or built game, where it can be accessed by other people.
|
||||
## This is meant to be used only in controlled circumstances.
|
||||
var slack_webhook_url_live : String = ""
|
||||
|
||||
## Defines the flags which LoggieMessages sent to the terminal channel will use during preprocessing.
|
||||
var preprocess_flags_terminal_channel = LoggieEnums.PreprocessStep.APPEND_TIMESTAMPS | LoggieEnums.PreprocessStep.APPEND_DOMAIN_NAME | LoggieEnums.PreprocessStep.APPEND_CLASS_NAME
|
||||
|
||||
## Defines the flags which LoggieMessages sent to the Discord channel output will use during preprocessing.
|
||||
var preprocess_flags_discord_channel = LoggieEnums.PreprocessStep.APPEND_DOMAIN_NAME | LoggieEnums.PreprocessStep.APPEND_CLASS_NAME
|
||||
|
||||
## Defines the flags which LoggieMessages sent to the Slack channel output will use during preprocessing.
|
||||
var preprocess_flags_slack_channel = LoggieEnums.PreprocessStep.APPEND_DOMAIN_NAME | LoggieEnums.PreprocessStep.APPEND_CLASS_NAME
|
||||
|
||||
## The list of channels a message outputted from Loggie should be sent to by default.
|
||||
var default_channels : PackedStringArray = ["terminal"]
|
||||
|
||||
## The list of file names, which, when appearing in a stack trace, should be omitted from the output..
|
||||
var skipped_filenames_in_stack_trace : PackedStringArray = ["loggie", "loggie_message"]
|
||||
|
||||
#endregion
|
||||
# ----------------------------------------------- #
|
||||
#region Formats for prints
|
||||
# ----------------------------------------------- #
|
||||
# As per the `print_rich` documentation, supported colors are: black, red, green, yellow, blue, magenta, pink, purple, cyan, white, orange, gray.
|
||||
# Any other color will be displayed in the Godot console or an ANSI based console, but the color tag (in case of BBCode) won't be properly stripped
|
||||
# when written to the .log file, resulting in BBCode visible in .log files.
|
||||
|
||||
## The format used to decorate a message as a header when using [method LoggieMsg.header].[br]
|
||||
## The [param {msg}] is a variable that will be replaced with the contents of the message.[br]
|
||||
var format_header = "[b][i]{msg}[/i][/b]"
|
||||
|
||||
## The format used when appending a domain to a message.[br]
|
||||
## The [param {msg}] is a variable that will be replaced with the contents of the message.[br]
|
||||
## The [param {domain}] is a variable that will be replaced with the domain key.[br]
|
||||
## You can customize this in your ProjectSettings, or custom_settings.gd (if using it).[br]
|
||||
var format_domain_prefix = "[b]({domain})[/b] {msg}"
|
||||
|
||||
## The format used when outputting error messages.[br]
|
||||
## The [param {msg}] is a variable that will be replaced with the contents of the message.[br]
|
||||
## You can customize this in your ProjectSettings, or custom_settings.gd (if using it).[br]
|
||||
var format_error_msg = "[b][color=red][ERROR]:[/color][/b] {msg}"
|
||||
|
||||
## The format used when outputting warning messages.[br]
|
||||
## The [param {msg}] is a variable that will be replaced with the contents of the message.[br]
|
||||
## You can customize this in your ProjectSettings, or custom_settings.gd (if using it).[br]
|
||||
var format_warning_msg = "[b][color=orange][WARN]:[/color][/b] {msg}"
|
||||
|
||||
## The format used when outputting notice messages.[br]
|
||||
## The [param {msg}] is a variable that will be replaced with the contents of the message.[br]
|
||||
## You can customize this in your ProjectSettings, or custom_settings.gd (if using it).[br]
|
||||
var format_notice_msg = "[b][color=cyan][NOTICE]:[/color][/b] {msg}"
|
||||
|
||||
## The format used when outputting info messages.[br]
|
||||
## The [param {msg}] is a variable that will be replaced with the contents of the message.[br]
|
||||
## You can customize this in your ProjectSettings, or custom_settings.gd (if using it).[br]
|
||||
var format_info_msg = "{msg}"
|
||||
|
||||
## The format used when outputting debug messages.[br]
|
||||
## The [param {msg}] is a variable that will be replaced with the contents of the message.[br]
|
||||
## You can customize this in your ProjectSettings, or custom_settings.gd (if using it).[br]
|
||||
var format_debug_msg = "[b][color=pink][DEBUG]:[/color][/b] {msg}"
|
||||
|
||||
## The format used for timestamps when they are prepended to the output.[br]
|
||||
## The variables [param {day}], [param {month}], [param {year}], [param {hour}], [param {minute}], [param {second}], [param {weekday}], and [param {dst}] are supported.
|
||||
## You can customize this in your ProjectSettings, or custom_settings.gd (if using it).[br]
|
||||
var format_timestamp = "[{day}.{month}.{year} {hour}:{minute}:{second}]"
|
||||
|
||||
## The format used for each entry in a stack trace that is obtained through [method Loggie.stack].
|
||||
## The variables [param {fn_name}], [param {index}], [param {source_path}], [param {line}] are supported.
|
||||
## You can customize this in your ProjectSettings, or custom_settings.gd (if using it).[br]
|
||||
var format_stacktrace_entry = "{index}: [color=#ff7085]func[/color] [color=#53b1c3][b]{fn_name}[/b]:{line}[/color] [color=slate_gray][i](in {source_path})[/i][/color]"
|
||||
|
||||
## The symbol which will be used for the HSeparator.
|
||||
var h_separator_symbol = "-"
|
||||
|
||||
## The mode used for drawing boxes.
|
||||
var box_characters_mode : LoggieEnums.BoxCharactersMode
|
||||
|
||||
## The symbols which will be used to construct a box decoration that will properly
|
||||
## display on any kind of terminal or text reader.
|
||||
## For a prettier but potentially incompatible box, use [member box_symbols_pretty] instead.
|
||||
var box_symbols_compatible = {
|
||||
# ANSI and .log compatible box characters:
|
||||
"top_left" : "-",
|
||||
"top_right" : "-",
|
||||
"bottom_left" : "-",
|
||||
"bottom_right" : "-",
|
||||
"h_line" : "-",
|
||||
"v_line" : ":",
|
||||
}
|
||||
|
||||
## The symbols which will be used to construct pretty box decoration.
|
||||
## These may not be compatible with some terminals or text readers.
|
||||
## Use the [member box_symbols_compatible] instead as an alternative.
|
||||
var box_symbols_pretty = {
|
||||
"top_left" : "┌",
|
||||
"top_right" : "┐",
|
||||
"bottom_left" : "└",
|
||||
"bottom_right" : "┘",
|
||||
"h_line" : "─",
|
||||
"v_line" : "│",
|
||||
}
|
||||
|
||||
#endregion
|
||||
# ----------------------------------------------- #
|
||||
|
||||
## A [Callable] function that takes 1 parameter [param something] (Variant),
|
||||
## and returns a [String] which represents the given [param something] in text.
|
||||
## By default, Loggie sets this to `LoggieTools.convert_to_string` when initialized.
|
||||
## [br][br]
|
||||
## You can, however, override that by changing this value to a valid replacement [Callable],
|
||||
## after Loggie has initialized.
|
||||
var custom_string_converter : Callable
|
||||
|
||||
## Loads the initial (default) values for all of the LoggieSettings variables.
|
||||
## (By default, loads them from ProjectSettings (if any modifications there exist),
|
||||
## or looks in [LoggieEditorPlugin..project_settings] for default values).
|
||||
## [br][br]Extend this class and override this function to write your own logic for
|
||||
## how loggie should obtain these settings if you have a need for a different approach.
|
||||
func load():
|
||||
update_check_mode = ProjectSettings.get_setting(project_settings.update_check_mode.path, project_settings.update_check_mode.default_value)
|
||||
msg_format_mode = ProjectSettings.get_setting(project_settings.msg_format_mode.path, project_settings.msg_format_mode.default_value)
|
||||
log_level = ProjectSettings.get_setting(project_settings.log_level.path, project_settings.log_level.default_value)
|
||||
show_loggie_specs = ProjectSettings.get_setting(project_settings.show_loggie_specs.path, project_settings.show_loggie_specs.default_value)
|
||||
show_system_specs = ProjectSettings.get_setting(project_settings.show_system_specs.path, project_settings.show_system_specs.default_value)
|
||||
timestamps_use_utc = ProjectSettings.get_setting(project_settings.timestamps_use_utc.path, project_settings.timestamps_use_utc.default_value)
|
||||
enforce_optimal_settings_in_release_build = ProjectSettings.get_setting(project_settings.enforce_optimal_settings_in_release_build.path, project_settings.enforce_optimal_settings_in_release_build.default_value)
|
||||
default_channels = ProjectSettings.get_setting(project_settings.default_channels.path, project_settings.default_channels.default_value)
|
||||
skipped_filenames_in_stack_trace = ProjectSettings.get_setting(project_settings.skipped_filenames_in_stack_trace.path, project_settings.skipped_filenames_in_stack_trace.default_value)
|
||||
|
||||
print_errors_to_console = ProjectSettings.get_setting(project_settings.output_errors_to_console.path, project_settings.output_errors_to_console.default_value)
|
||||
print_warnings_to_console = ProjectSettings.get_setting(project_settings.output_warnings_to_console.path, project_settings.output_warnings_to_console.default_value)
|
||||
debug_msgs_print_stack_trace = ProjectSettings.get_setting(project_settings.debug_msgs_print_stack_trace.path, project_settings.debug_msgs_print_stack_trace.default_value)
|
||||
|
||||
nameless_class_name_proxy = ProjectSettings.get_setting(project_settings.nameless_class_name_proxy.path, project_settings.nameless_class_name_proxy.default_value)
|
||||
box_characters_mode = ProjectSettings.get_setting(project_settings.box_characters_mode.path, project_settings.box_characters_mode.default_value)
|
||||
|
||||
format_timestamp = ProjectSettings.get_setting(project_settings.format_timestamp.path, project_settings.format_timestamp.default_value)
|
||||
format_stacktrace_entry = ProjectSettings.get_setting(project_settings.format_stacktrace_entry.path, project_settings.format_stacktrace_entry.default_value)
|
||||
format_info_msg = ProjectSettings.get_setting(project_settings.format_info_msg.path, project_settings.format_info_msg.default_value)
|
||||
format_notice_msg = ProjectSettings.get_setting(project_settings.format_notice_msg.path, project_settings.format_notice_msg.default_value)
|
||||
format_warning_msg = ProjectSettings.get_setting(project_settings.format_warning_msg.path, project_settings.format_warning_msg.default_value)
|
||||
format_error_msg = ProjectSettings.get_setting(project_settings.format_error_msg.path, project_settings.format_error_msg.default_value)
|
||||
format_debug_msg = ProjectSettings.get_setting(project_settings.format_debug_msg.path, project_settings.format_debug_msg.default_value)
|
||||
h_separator_symbol = ProjectSettings.get_setting(project_settings.h_separator_symbol.path, project_settings.h_separator_symbol.default_value)
|
||||
|
||||
discord_webhook_url_live = ProjectSettings.get_setting(project_settings.discord_webhook_url_live.path, project_settings.discord_webhook_url_live.default_value)
|
||||
discord_webhook_url_dev = ProjectSettings.get_setting(project_settings.discord_webhook_url_dev.path, project_settings.discord_webhook_url_dev.default_value)
|
||||
preprocess_flags_discord_channel = ProjectSettings.get_setting(project_settings.preprocess_flags_discord_channel.path, project_settings.preprocess_flags_discord_channel.default_value)
|
||||
slack_webhook_url_live = ProjectSettings.get_setting(project_settings.slack_webhook_url_live.path, project_settings.slack_webhook_url_live.default_value)
|
||||
slack_webhook_url_dev = ProjectSettings.get_setting(project_settings.slack_webhook_url_dev.path, project_settings.slack_webhook_url_dev.default_value)
|
||||
preprocess_flags_slack_channel = ProjectSettings.get_setting(project_settings.preprocess_flags_slack_channel.path, project_settings.preprocess_flags_slack_channel.default_value)
|
||||
preprocess_flags_terminal_channel = ProjectSettings.get_setting(project_settings.preprocess_flags_terminal_channel.path, project_settings.preprocess_flags_terminal_channel.default_value)
|
||||
|
||||
## Returns a dictionary where the indices are names of relevant variables in the LoggieSettings class,
|
||||
## and the values are their current values.
|
||||
func to_dict() -> Dictionary:
|
||||
var dict = {}
|
||||
var included = [
|
||||
"preprocess_flags_discord_channel", "preprocess_flags_slack_channel", "preprocess_flags_terminal_channel",
|
||||
"default_channels", "skipped_filenames_in_stack_trace", "msg_format_mode", "log_level", "show_loggie_specs", "show_system_specs", "enforce_optimal_settings_in_release_build",
|
||||
"print_errors_to_console", "print_warnings_to_console",
|
||||
"debug_msgs_print_stack_trace", "nameless_class_name_proxy",
|
||||
"timestamps_use_utc", "format_header", "format_domain_prefix", "format_error_msg",
|
||||
"format_warning_msg", "format_notice_msg", "format_info_msg", "format_debug_msg", "format_timestamp",
|
||||
"h_separator_symbol", "box_characters_mode", "box_symbols_compatible", "box_symbols_pretty",
|
||||
]
|
||||
|
||||
for var_name in included:
|
||||
dict[var_name] = get(var_name)
|
||||
return dict
|
||||
1
addons/loggie/loggie_settings.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cy4e5xusg8krn
|
||||
7
addons/loggie/plugin.cfg
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[plugin]
|
||||
|
||||
name="Loggie"
|
||||
description="Simple functional stylish logger for your basic logging needs."
|
||||
author="Shiva Shadowsong"
|
||||
version="2.0"
|
||||
script="plugin.gd"
|
||||
50
addons/loggie/plugin.gd
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
@tool
|
||||
class_name LoggieEditorPlugin extends EditorPlugin
|
||||
|
||||
func _enter_tree():
|
||||
add_autoload_singleton(LoggieSettings.loggie_singleton_name, "res://addons/loggie/loggie.gd")
|
||||
add_loggie_project_settings()
|
||||
Engine.set_meta("LoggieEditorPlugin", self)
|
||||
if Engine.is_editor_hint():
|
||||
Engine.set_meta("LoggieEditorInterfaceBaseControl", EditorInterface.get_base_control())
|
||||
|
||||
func _enable_plugin() -> void:
|
||||
add_loggie_project_settings()
|
||||
|
||||
func _disable_plugin() -> void:
|
||||
var wipe_setting_exists = ProjectSettings.has_setting(LoggieSettings.project_settings.remove_settings_if_plugin_disabled.path)
|
||||
if (not wipe_setting_exists) or (wipe_setting_exists and ProjectSettings.get_setting(LoggieSettings.project_settings.remove_settings_if_plugin_disabled.path, true)):
|
||||
push_warning("The Loggie plugin is being disabled, and all of its ProjectSettings are erased from Godot. If you wish to prevent this behavior, look for the 'Project Settings -> Loggie -> General -> Remove Settings if Plugin Disabled' option while the plugin is enabled.")
|
||||
remove_loggie_project_setings()
|
||||
else:
|
||||
push_warning("The Loggie plugin is being disabled, but its ProjectSettings have been prevented from being removed from Godot. If you wish to allow that behavior, look for the 'Project Settings -> Loggie -> General -> Remove Settings if Plugin Disabled' option while the plugin is enabled.")
|
||||
remove_autoload_singleton(LoggieSettings.loggie_singleton_name)
|
||||
|
||||
## Adds new Loggie related ProjectSettings to Godot.
|
||||
func add_loggie_project_settings():
|
||||
for setting in LoggieSettings.project_settings.values():
|
||||
add_project_setting(setting["path"], setting["default_value"], setting["type"], setting["hint"], setting["hint_string"], setting["doc"])
|
||||
|
||||
## Removes Loggie related ProjectSettings from Godot.
|
||||
func remove_loggie_project_setings():
|
||||
for setting in LoggieSettings.project_settings.values():
|
||||
ProjectSettings.set_setting(setting["path"], null)
|
||||
|
||||
var error: int = ProjectSettings.save()
|
||||
if error != OK:
|
||||
push_error("Loggie - Encountered error %d while saving project settings." % error)
|
||||
|
||||
## Adds a new project setting to Godot.
|
||||
## (WARNING): Unfortunately, as of Godot 4.3, it has been confirmed that it is not possible to set the
|
||||
## popup tooltip text for a project setting. This should be implemented here as soon as that becomes possible.
|
||||
func add_project_setting(setting_name: String, default_value : Variant, value_type: int, type_hint: int = PROPERTY_HINT_NONE, hint_string: String = "", documentation : String = ""):
|
||||
if !ProjectSettings.has_setting(setting_name):
|
||||
ProjectSettings.set_setting(setting_name, default_value)
|
||||
|
||||
ProjectSettings.set_initial_value(setting_name, default_value)
|
||||
ProjectSettings.add_property_info({ "name": setting_name, "type": value_type, "hint": type_hint, "hint_string": hint_string})
|
||||
ProjectSettings.set_as_basic(setting_name, true)
|
||||
|
||||
var error: int = ProjectSettings.save()
|
||||
if error:
|
||||
push_error("Loggie - Encountered error %d while saving project settings." % error)
|
||||
1
addons/loggie/plugin.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c877oew076160
|
||||
79
addons/loggie/tools/loggie_enums.gd
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
@tool
|
||||
class_name LoggieEnums extends Node
|
||||
|
||||
## Based on which log level is currently set to be used by the Loggie., attempting to log a message that's on
|
||||
## a higher-than-configured log level will result in nothing happening.
|
||||
enum LogLevel {
|
||||
ERROR, ## Log level which includes only the logging of Error type messages.
|
||||
WARN, ## Log level which includes the logging of Error and Warning type messages.
|
||||
NOTICE, ## Log level which includes the logging of Error, Warning and Notice type messages.
|
||||
INFO, ## Log level which includes the logging of Error, Warning, Notice and Info type messages.
|
||||
DEBUG ## Log level which includes the logging of Error, Warning, Notice, Info and Debug type messages.
|
||||
}
|
||||
|
||||
## The classification of message types that can be used to distinguish two identical strings in nature
|
||||
## of their origin. This is different from [enum LogLevel].
|
||||
enum MsgType {
|
||||
STANDARD, ## A message that is considered a standard text that is not special in any way.
|
||||
ERROR, ## A message that is considered to be an error message.
|
||||
WARNING, ## A message that is considered to be a warning message.
|
||||
DEBUG ## A message that is considered to be a message used for debugging.
|
||||
}
|
||||
|
||||
enum MsgFormatMode {
|
||||
PLAIN, ## Prints will be plain text.
|
||||
ANSI, ## Prints will be styled using the ANSI standard. Compatible with Powershell, Win CMD, etc.
|
||||
BBCODE, ## Prints will be styled using the Godot BBCode rules. Compatible with the Godot console.
|
||||
MARKDOWN, ## Prints will be styled using the Markdown standard. Compatible with most Markdown readers.
|
||||
}
|
||||
|
||||
## Classifies various steps that can happen during preprocessing.
|
||||
enum PreprocessStep {
|
||||
## A timestamp will be added to the message.
|
||||
APPEND_TIMESTAMPS = 1 << 0,
|
||||
|
||||
## The name of the domain from which the message is coming will be added to the message.
|
||||
APPEND_DOMAIN_NAME = 1 << 1,
|
||||
|
||||
## Whether Loggie should use the scripts from which it is being called to
|
||||
## figure out a class name for the class that called a loggie function,
|
||||
## and append it to the outputted message.
|
||||
## This only works in debug builds because it uses [method @GDScript.get_stack].
|
||||
## See that method's documentation to see why that can't be used in release builds.
|
||||
APPEND_CLASS_NAME = 1 << 2,
|
||||
}
|
||||
|
||||
enum BoxCharactersMode {
|
||||
COMPATIBLE, ## Boxes are drawn using characters that compatible with any kind of terminal or text reader.
|
||||
PRETTY ## Boxes are drawn using special unicode characters that create a prettier looking box which may not display properly in some terminals or text readers.
|
||||
}
|
||||
|
||||
## Defines a list of possible approaches that can be taken to derive some kind of a class name proxy from a script that doesn't have a 'class_name' clause.
|
||||
enum NamelessClassExtensionNameProxy {
|
||||
NOTHING, ## If there is no class_name, nothing will be displayed.
|
||||
SCRIPT_NAME, ## Use the name of the script whose class_name we tried to read. (e.g. "my_script.gd").
|
||||
BASE_TYPE, ## Use the name of the base type which the script extends (e.g. 'Node2D', 'Control', etc.)
|
||||
}
|
||||
|
||||
## Defines a list of possible behaviors for the 'show_loggie_specs' setting.
|
||||
enum ShowLoggieSpecsMode {
|
||||
DISABLED, ## Loggie specs won't be shown.
|
||||
ESSENTIAL, ## Show only the essentials.
|
||||
ADVANCED ## Show all loggie specs.
|
||||
}
|
||||
|
||||
## Defines a list of possible outcomes that can happen when attempting to log a message.
|
||||
enum LogAttemptResult {
|
||||
SUCCESS, ## Message will be logged successfully.
|
||||
LOG_LEVEL_INSUFFICIENT, ## Message won't be logged because it was output at a log level higher than what Loggie is currently set to.
|
||||
DOMAIN_DISABLED, ## Message won't be logged because it was outputted from a disabled domain.
|
||||
INVALID_CHANNEL, ## Message won't be logged because the channel which was supposed to send it doesn't exist.
|
||||
}
|
||||
|
||||
## Defines a list of possible ways to configure Loggie to check for updates.
|
||||
enum UpdateCheckType {
|
||||
DONT_CHECK, ## If the user doesn't want Loggie to check for updates at all.
|
||||
CHECK_AND_SHOW_MSG, ## If the user wants Loggie to check for updates, and display info in a terminal message.
|
||||
CHECK_DOWNLOAD_AND_SHOW_MSG, ## If the user wants Loggie to check for updates, download the update, and display info in a terminal message.
|
||||
CHECK_AND_SHOW_UPDATER_WINDOW, ## If the user wants Loggie to check for updats, and display the updater window.
|
||||
}
|
||||
1
addons/loggie/tools/loggie_enums.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cg22nkeh1ywxa
|
||||
186
addons/loggie/tools/loggie_system_specs.gd
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
@tool
|
||||
|
||||
## LoggieSystemSpecs is a helper class that defines various functions on how to access data about the local machine and its specs
|
||||
## and creates displayable strings out of them.
|
||||
class_name LoggieSystemSpecsMsg extends LoggieMsg
|
||||
|
||||
## Embeds various system specs into the content of this message.
|
||||
func embed_specs() -> LoggieSystemSpecsMsg:
|
||||
self.embed_system_specs()
|
||||
self.embed_localization_specs()
|
||||
self.embed_date_data().nl()
|
||||
self.embed_hardware_specs().nl()
|
||||
self.embed_video_specs().nl()
|
||||
self.embed_display_specs().nl()
|
||||
self.embed_audio_specs().nl()
|
||||
self.embed_engine_specs().nl()
|
||||
self.embed_input_specs()
|
||||
return self
|
||||
|
||||
## Embeds essential data about the logger into the content of this message.
|
||||
func embed_essential_logger_specs() -> LoggieSystemSpecsMsg:
|
||||
var loggie = get_logger()
|
||||
self.add(loggie.msg("|\t Is in Production:").bold(), loggie.is_in_production()).nl()
|
||||
self.add(loggie.msg("|\t Default Channel(s):").bold(),loggie.settings.default_channels).nl()
|
||||
self.add(loggie.msg("|\t Msg Format Mode:").bold(), LoggieEnums.MsgFormatMode.keys()[loggie.settings.msg_format_mode]).nl()
|
||||
self.add(loggie.msg("|\t Log Level:").bold(), LoggieEnums.LogLevel.keys()[loggie.settings.log_level]).nl()
|
||||
return self
|
||||
|
||||
## Embeds advanced data about the logger into the content of this message.
|
||||
func embed_advanced_logger_specs() -> LoggieSystemSpecsMsg:
|
||||
var loggie = get_logger()
|
||||
|
||||
self.add(loggie.msg("|\t Is in Production:").bold(), loggie.is_in_production()).nl()
|
||||
|
||||
var settings_dict = loggie.settings.to_dict()
|
||||
for setting_var_name : String in settings_dict.keys():
|
||||
var setting_value = settings_dict[setting_var_name]
|
||||
var content_to_print = setting_value
|
||||
|
||||
match setting_var_name:
|
||||
"msg_format_mode":
|
||||
content_to_print = LoggieEnums.MsgFormatMode.keys()[setting_value]
|
||||
"log_level":
|
||||
content_to_print = LoggieEnums.LogLevel.keys()[setting_value]
|
||||
"box_characters_mode":
|
||||
content_to_print = LoggieEnums.BoxCharactersMode.keys()[setting_value]
|
||||
|
||||
self.add(loggie.msg("|\t", setting_var_name.capitalize(), ":").bold(), content_to_print).nl()
|
||||
|
||||
return self
|
||||
|
||||
## Adds data about the user's software to the content of this message.
|
||||
func embed_system_specs() -> LoggieSystemSpecsMsg:
|
||||
var loggie = get_logger()
|
||||
var header = loggie.msg("Operating System: ").color(Color.ORANGE).add(OS.get_name()).box(4)
|
||||
self.add(header)
|
||||
return self
|
||||
|
||||
## Adds data about localization to the content of this message.
|
||||
func embed_localization_specs() -> LoggieSystemSpecsMsg:
|
||||
var loggie = get_logger()
|
||||
var header = loggie.msg("Localization: ").color(Color.ORANGE).add(OS.get_locale()).box(7)
|
||||
self.add(header)
|
||||
return self
|
||||
|
||||
## Adds data about the current date/time to the content of this message.
|
||||
func embed_date_data() -> LoggieSystemSpecsMsg:
|
||||
var loggie = get_logger()
|
||||
var header = loggie.msg("Date").color(Color.ORANGE).box(15)
|
||||
self.add(header)
|
||||
self.add(loggie.msg("Date and time (local):").bold(), Time.get_datetime_string_from_system(false, true)).nl()
|
||||
self.add(loggie.msg("Date and time (UTC):").bold(), Time.get_datetime_string_from_system(true, true)).nl()
|
||||
self.add(loggie.msg("Date (local):").bold(), Time.get_date_string_from_system(false)).nl()
|
||||
self.add(loggie.msg("Date (UTC):").bold(), Time.get_date_string_from_system(true)).nl()
|
||||
self.add(loggie.msg("Time (local):").bold(), Time.get_time_string_from_system(false)).nl()
|
||||
self.add(loggie.msg("Time (UTC):").bold(), Time.get_time_string_from_system(true)).nl()
|
||||
self.add(loggie.msg("Timezone:").bold(), Time.get_time_zone_from_system()).nl()
|
||||
self.add(loggie.msg("UNIX time:").bold(), Time.get_unix_time_from_system()).nl()
|
||||
return self
|
||||
|
||||
## Adds data about the user's hardware to the content of this message.
|
||||
func embed_hardware_specs() -> LoggieSystemSpecsMsg:
|
||||
var loggie = get_logger()
|
||||
var header = loggie.msg("Hardware").color(Color.ORANGE).box(13)
|
||||
self.add(header)
|
||||
self.add(loggie.msg("Model name:").bold(), OS.get_model_name()).nl()
|
||||
self.add(loggie.msg("Processor name:").bold(), OS.get_processor_name()).nl()
|
||||
return self
|
||||
|
||||
## Adds data about the video system to the content of this message.
|
||||
func embed_video_specs() -> LoggieSystemSpecsMsg:
|
||||
const adapter_type_to_string = ["Other (Unknown)", "Integrated", "Discrete", "Virtual", "CPU"]
|
||||
var adapter_type_string = adapter_type_to_string[RenderingServer.get_video_adapter_type()]
|
||||
var video_adapter_driver_info = OS.get_video_adapter_driver_info()
|
||||
var loggie = get_logger()
|
||||
|
||||
var header = loggie.msg("Video").color(Color.ORANGE).box(15)
|
||||
self.add(header)
|
||||
self.add(loggie.msg("Adapter name:").bold(), RenderingServer.get_video_adapter_name()).nl()
|
||||
self.add(loggie.msg("Adapter vendor:").bold(), RenderingServer.get_video_adapter_vendor()).nl()
|
||||
self.add(loggie.msg("Adapter type:").bold(), adapter_type_string).nl()
|
||||
self.add(loggie.msg("Adapter graphics API version:").bold(), RenderingServer.get_video_adapter_api_version()).nl()
|
||||
|
||||
if video_adapter_driver_info.size() > 0:
|
||||
self.add(loggie.msg("Adapter driver name:").bold(), video_adapter_driver_info[0]).nl()
|
||||
if video_adapter_driver_info.size() > 1:
|
||||
self.add(loggie.msg("Adapter driver version:").bold(), video_adapter_driver_info[1]).nl()
|
||||
|
||||
return self
|
||||
|
||||
## Adds data about the display to the content of this message.
|
||||
func embed_display_specs() -> LoggieSystemSpecsMsg:
|
||||
const screen_orientation_to_string = [
|
||||
"Landscape",
|
||||
"Portrait",
|
||||
"Landscape (reverse)",
|
||||
"Portrait (reverse)",
|
||||
"Landscape (defined by sensor)",
|
||||
"Portrait (defined by sensor)",
|
||||
"Defined by sensor",
|
||||
]
|
||||
var screen_orientation_string = screen_orientation_to_string[DisplayServer.screen_get_orientation()]
|
||||
var loggie = get_logger()
|
||||
|
||||
var header = loggie.msg("Display").color(Color.ORANGE).box(13)
|
||||
self.add(header)
|
||||
self.add(loggie.msg("Screen count:").bold(), DisplayServer.get_screen_count()).nl()
|
||||
self.add(loggie.msg("DPI:").bold(), DisplayServer.screen_get_dpi()).nl()
|
||||
self.add(loggie.msg("Scale factor:").bold(), DisplayServer.screen_get_scale()).nl()
|
||||
self.add(loggie.msg("Maximum scale factor:").bold(), DisplayServer.screen_get_max_scale()).nl()
|
||||
self.add(loggie.msg("Startup screen position:").bold(), DisplayServer.screen_get_position()).nl()
|
||||
self.add(loggie.msg("Startup screen size:").bold(), DisplayServer.screen_get_size()).nl()
|
||||
self.add(loggie.msg("Startup screen refresh rate:").bold(), ("%f Hz" % DisplayServer.screen_get_refresh_rate()) if DisplayServer.screen_get_refresh_rate() > 0.0 else "").nl()
|
||||
self.add(loggie.msg("Usable (safe) area rectangle:").bold(), DisplayServer.get_display_safe_area()).nl()
|
||||
self.add(loggie.msg("Screen orientation:").bold(), screen_orientation_string).nl()
|
||||
return self
|
||||
|
||||
## Adds data about the audio system to the content of this message.
|
||||
func embed_audio_specs() -> LoggieSystemSpecsMsg:
|
||||
var loggie = get_logger()
|
||||
var header = loggie.msg("Audio").color(Color.ORANGE).box(14)
|
||||
self.add(header)
|
||||
self.add(loggie.msg("Mix rate:").bold(), "%d Hz" % AudioServer.get_mix_rate()).nl()
|
||||
self.add(loggie.msg("Output latency:").bold(), "%f ms" % (AudioServer.get_output_latency() * 1000)).nl()
|
||||
self.add(loggie.msg("Output device list:").bold(), ", ".join(AudioServer.get_output_device_list())).nl()
|
||||
self.add(loggie.msg("Capture device list:").bold(), ", ".join(AudioServer.get_input_device_list())).nl()
|
||||
return self
|
||||
|
||||
## Adds data about the godot engine to the content of this message.
|
||||
func embed_engine_specs() -> LoggieSystemSpecsMsg:
|
||||
var loggie = get_logger()
|
||||
var header = loggie.msg("Engine").color(Color.ORANGE).box(14)
|
||||
self.add(header)
|
||||
self.add(loggie.msg("Version:").bold(), Engine.get_version_info()["string"]).nl()
|
||||
self.add(loggie.msg("Command-line arguments:").bold(), str(OS.get_cmdline_args())).nl()
|
||||
self.add(loggie.msg("Is debug build:").bold(), OS.is_debug_build()).nl()
|
||||
self.add(loggie.msg("Filesystem is persistent:").bold(), OS.is_userfs_persistent()).nl()
|
||||
return self
|
||||
|
||||
## Adds data about the input device to the content of this message.
|
||||
func embed_input_specs() -> LoggieSystemSpecsMsg:
|
||||
var has_virtual_keyboard = DisplayServer.has_feature(DisplayServer.FEATURE_VIRTUAL_KEYBOARD)
|
||||
var loggie = get_logger()
|
||||
|
||||
var header = loggie.msg("Input").color(Color.ORANGE).box(14)
|
||||
self.add(header)
|
||||
self.add(loggie.msg("Device has touch screen:").bold(), DisplayServer.is_touchscreen_available()).nl()
|
||||
self.add(loggie.msg("Device has virtual keyboard:").bold(), has_virtual_keyboard).nl()
|
||||
|
||||
if has_virtual_keyboard:
|
||||
self.add(loggie.msg("Virtual keyboard height:").bold(), DisplayServer.virtual_keyboard_get_height())
|
||||
|
||||
return self
|
||||
|
||||
## Prints out a bunch of useful data about a given script.
|
||||
## Useful for debugging.
|
||||
func embed_script_data(script : Script):
|
||||
var loggie = get_logger()
|
||||
self.add("Script Data for:", script.get_path()).color("pink")
|
||||
self.add(":").nl()
|
||||
self.add(loggie.msg("get_class(): ").color("slate_blue").bold()).add(script.get_class()).nl()
|
||||
self.add(loggie.msg("get_global_name(): ").color("slate_blue").bold()).add(script.get_global_name()).nl()
|
||||
self.add(loggie.msg("get_base_script(): ").color("slate_blue").bold()).add(script.get_base_script().resource_path if script.get_base_script() != null else "No base script.").nl()
|
||||
self.add(loggie.msg("get_instance_base_type(): ").color("slate_blue").bold()).add(script.get_instance_base_type()).nl()
|
||||
self.add(loggie.msg("get_script_property_list(): ").color("slate_blue").bold()).add(script.get_script_property_list()).nl()
|
||||
return self
|
||||
1
addons/loggie/tools/loggie_system_specs.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://b1tkhkakrmwdu
|
||||
530
addons/loggie/tools/loggie_tools.gd
Normal file
|
|
@ -0,0 +1,530 @@
|
|||
@tool
|
||||
class_name LoggieTools extends Node
|
||||
|
||||
## Removes BBCode from the given text.
|
||||
## If [param specific_tags] is an array, it removes only the tags found in that array.
|
||||
## Otherwise, it removes the tags found in the default_tags array:[br]
|
||||
## [param ["b", "i", "u", "s", "indent", "code", "url", "center", "right", "color", "bgcolor", "fgcolor"]]
|
||||
static func remove_BBCode(text: String, specific_tags = null) -> String:
|
||||
# The bbcode tags to remove.
|
||||
var default_tags = ["b", "i", "u", "s", "indent", "code", "url", "center", "right", "color", "bgcolor", "fgcolor"]
|
||||
var tags = specific_tags if specific_tags is Array else default_tags
|
||||
|
||||
var regex = RegEx.new()
|
||||
var tags_pattern = "|".join(tags)
|
||||
regex.compile("\\[/?(" + tags_pattern + ")(=[^\\]]*)?\\]")
|
||||
|
||||
var stripped_text = regex.sub(text, "", true)
|
||||
return stripped_text
|
||||
|
||||
## Concatenates all elements of the given [param args] array into one single string, in consecutive order.
|
||||
## If [param custom_converter_fn] is provided, and is a [Callable], that function will be used to convert each element of the array into a string
|
||||
## instead of using [method convert_to_string]. That function will receive 1 argument, which will be a 'Variant', and it has to return a 'String'.
|
||||
static func concatenate_args(args : Array, custom_converter_fn : Variant = null) -> String:
|
||||
if args.size() == 0:
|
||||
return ""
|
||||
|
||||
var converter_fn : Callable = LoggieTools.convert_to_string
|
||||
if custom_converter_fn is Callable and custom_converter_fn.is_valid() and !custom_converter_fn.is_null():
|
||||
converter_fn = custom_converter_fn
|
||||
|
||||
# Start with first element without modifying array
|
||||
var final_msg : String = converter_fn.call(args[0])
|
||||
|
||||
# Start from index 1 since we already handled index 0
|
||||
for i in range(1, args.size()):
|
||||
var arg = args[i]
|
||||
var is_not_followed_by_a_null_arg = true if (i + 1 <= args.size() - 1) and (args[i + 1] != null) else false
|
||||
if (arg != null) or (arg == null and is_not_followed_by_a_null_arg):
|
||||
var converted_arg : String = converter_fn.call(arg)
|
||||
final_msg += (" " + converted_arg)
|
||||
|
||||
return final_msg
|
||||
|
||||
## Converts a text with BBCode in it to markdown.
|
||||
## A limited set of BBCode tags are supported for this conversion, because standard Markdown can't handle everything
|
||||
## that BBCode can. For example, colors will be entirely stripped.
|
||||
static func convert_BBCode_to_markdown(text: String) -> String:
|
||||
# Purge the unsupported tags.
|
||||
var unsupported_tags = ["indent", "url", "center", "right", "color", "bgcolor", "fgcolor"]
|
||||
text = LoggieTools.remove_BBCode(text, unsupported_tags)
|
||||
|
||||
# Space out all instances where "*" characters from multiple tags are strung together,
|
||||
# since that would break them from rendering with the proper effect in markdown.
|
||||
# This is only an issue with [b] and [i] tags because they both use the same "*" character
|
||||
# in markdown to be represented.
|
||||
text = text.replace("[/b][i]", "** *")
|
||||
text = text.replace("[/b][/i]", "** *")
|
||||
text = text.replace("[/i][b]", "* **")
|
||||
text = text.replace("[/i][/b]", "* **")
|
||||
text = text.replace("[/i][i]", "* *")
|
||||
text = text.replace("[/i][/i]", "* *")
|
||||
text = text.replace("[/b][b]", "** **")
|
||||
text = text.replace("[/b][/b]", "** **")
|
||||
|
||||
# Perform all supported conversion.
|
||||
var supported_conversions = {
|
||||
"[b]" : "**", "[/b]" : "**",
|
||||
"[i]" : "*", "[/i]" : "*",
|
||||
"[u]" : "__", "[/u]" : "__",
|
||||
"[s]" : "~~", "[/s]" : "~~",
|
||||
}
|
||||
for bbcodetag in supported_conversions.keys():
|
||||
text = text.replace(bbcodetag, supported_conversions[bbcodetag])
|
||||
|
||||
return text
|
||||
|
||||
## Converts [param something] into a string, with custom handling for
|
||||
## certain native and custom classes.
|
||||
static func convert_to_string(something : Variant) -> String:
|
||||
var result : String
|
||||
if something is Dictionary:
|
||||
result = JSON.new().stringify(something, " ", false, true)
|
||||
elif something is LoggieMsg:
|
||||
result = something.string()
|
||||
else:
|
||||
result = str(something)
|
||||
return result
|
||||
|
||||
## Takes the given [param str] and returns a terminal-ready version of it by converting its content
|
||||
## to the appropriate format required to display the string correctly in the provided [param mode]
|
||||
## msg format mode.
|
||||
## [b]The provided [param str] is expected to be either in Plain or BBCode format.[/b]
|
||||
static func convert_string_to_format_mode(str : String, mode : LoggieEnums.MsgFormatMode) -> String:
|
||||
match mode:
|
||||
LoggieEnums.MsgFormatMode.ANSI:
|
||||
# We put the message through the rich_to_ANSI converter which takes care of converting BBCode
|
||||
# to appropriate ANSI. (Only if the MsgFormatMode is set to ANSI).
|
||||
# Godot claims to be already preparing BBCode output for ANSI, but it only works with a small
|
||||
# predefined set of colors, and I think it totally strips stuff like [b], [i], etc.
|
||||
# It is possible to display those stylings in ANSI, but we have to do our own conversion here
|
||||
# to support these features instead of having them stripped.
|
||||
str = LoggieTools.rich_to_ANSI(str)
|
||||
LoggieEnums.MsgFormatMode.BBCODE:
|
||||
# No need to do anything for BBCODE mode, because we already expect all strings to
|
||||
# start out with this format in mind.
|
||||
pass
|
||||
LoggieEnums.MsgFormatMode.MARKDOWN:
|
||||
str = LoggieTools.convert_BBCode_to_markdown(str)
|
||||
LoggieEnums.MsgFormatMode.PLAIN, _:
|
||||
str = LoggieTools.remove_BBCode(str)
|
||||
return str
|
||||
|
||||
## Converts a given [Color] to an ANSI compatible representation of it in code.
|
||||
static func color_to_ANSI(color: Color) -> String:
|
||||
var r = int(color.r * 255)
|
||||
var g = int(color.g * 255)
|
||||
var b = int(color.b * 255)
|
||||
return "\u001b[38;2;%d;%d;%dm" % [r, g, b]
|
||||
|
||||
## Strips the BBCode from the given text, and converts all [b], [i] and [color] tags to appropriate ANSI representable codes,
|
||||
## then returns the converted string. The result of this conversion becomes an ANSI compatible representation of the given [param text].
|
||||
static func rich_to_ANSI(text: String) -> String:
|
||||
var regex_color = RegEx.new()
|
||||
regex_color.compile("\\[color=(.*?)\\](.*?)\\[/color\\]")
|
||||
|
||||
# Process color tags first.
|
||||
while regex_color.search(text):
|
||||
var match = regex_color.search(text)
|
||||
var color_str = match.get_string(1).to_upper()
|
||||
var color: Color
|
||||
var color_code: String
|
||||
var reset_code = "\u001b[0m"
|
||||
|
||||
# Try to parse the color string
|
||||
if LoggieTools.NamedColors.has(color_str):
|
||||
color = LoggieTools.NamedColors[color_str]
|
||||
else:
|
||||
color = Color(color_str)
|
||||
|
||||
if color:
|
||||
color_code = color_to_ANSI(color)
|
||||
else:
|
||||
color_code = ""
|
||||
reset_code = ""
|
||||
|
||||
var replacement = color_code + match.get_string(2) + reset_code
|
||||
text = text.replace(match.get_string(0), replacement)
|
||||
|
||||
# Process bold and italic tags.
|
||||
var bold_on = "\u001b[1m"
|
||||
var bold_off = "\u001b[22m"
|
||||
var italic_on = "\u001b[3m"
|
||||
var italic_off = "\u001b[23m"
|
||||
|
||||
text = text.replace("[b]", bold_on).replace("[/b]", bold_off)
|
||||
text = text.replace("[i]", italic_on).replace("[/i]", italic_off)
|
||||
|
||||
# Remove any other BBCode tags but retain the text between them.
|
||||
var regex_bbcode = RegEx.new()
|
||||
regex_bbcode.compile("\\[(b|/b|i|/i|color=[^\\]]+|/color)\\]")
|
||||
text = regex_bbcode.sub(text, "", true)
|
||||
|
||||
return text
|
||||
|
||||
## Returns a dictionary of call stack data related to the stack the call to this function is a part of.
|
||||
## This function only works in debug builds, and on the main thread, because it uses [method get_stack].
|
||||
## Read more about why in that function's documentation.
|
||||
static func get_current_stack_frame_data() -> Dictionary:
|
||||
var stack = get_stack()
|
||||
if stack.size() > 0:
|
||||
stack.reverse()
|
||||
# Prune the frames starting from the first one that comes from loggie_message and onwards.
|
||||
var pruned_stack = []
|
||||
for index in stack.size():
|
||||
var source : String = stack[index].source
|
||||
var prune_breakpoint_files = ["loggie", "loggie_message"]
|
||||
if prune_breakpoint_files.has(source.get_file().get_basename()):
|
||||
break
|
||||
pruned_stack.push_back(stack[index])
|
||||
|
||||
# The back-most remaining entry in the pruned stack is the first non-Loggie caller.
|
||||
if pruned_stack.size() >= 1:
|
||||
return pruned_stack.back()
|
||||
|
||||
return {
|
||||
"source" : "UnknownStackFrameSource",
|
||||
"line" : 0,
|
||||
"function" : "UnknownFunction"
|
||||
}
|
||||
|
||||
## Returns the `class_name` of a script.
|
||||
## [br][param path_or_script] should be either an absolute path to the script
|
||||
## (String, e.g. "res://my_script.gd"), or a [Script] object.
|
||||
## [br][param proxy] defines which kind of text will be used as a replacement
|
||||
## for the class name if the script has no 'class_name'.
|
||||
static func get_class_name_from_script(path_or_script : Variant, proxy : LoggieEnums.NamelessClassExtensionNameProxy) -> String:
|
||||
var script
|
||||
var _class_name = ""
|
||||
|
||||
if path_or_script is String or path_or_script is StringName:
|
||||
if !ResourceLoader.exists(path_or_script, "Script"):
|
||||
return _class_name
|
||||
script = load(path_or_script)
|
||||
elif path_or_script is Script:
|
||||
script = path_or_script
|
||||
|
||||
if not (script is Script):
|
||||
push_error("Invalid 'path_or_script' param provided to get_class_name_from_script: {path}".format({"path" : path_or_script}))
|
||||
else:
|
||||
if not script.has_method("get_global_name"):
|
||||
# User is using a pre-4.3 version of Godot that doesn't have Script.get_global_name.
|
||||
# We must use a different method to achieve this then.
|
||||
return extract_class_name_from_gd_script(path_or_script, proxy)
|
||||
|
||||
# Try to get the class name directly.
|
||||
_class_name = script.get_global_name()
|
||||
|
||||
if _class_name != "":
|
||||
return _class_name
|
||||
|
||||
# If that's empty, the script is either a base class, or a class without a name.
|
||||
# Check if this script has a base script, and if so, use that one as the target whose name to obtain.
|
||||
# If it doesn't have it, use what the [param proxy] demands.
|
||||
var base_script = script.get_base_script()
|
||||
if base_script != null:
|
||||
return get_class_name_from_script(base_script, proxy)
|
||||
else:
|
||||
match proxy:
|
||||
LoggieEnums.NamelessClassExtensionNameProxy.BASE_TYPE:
|
||||
_class_name = script.get_instance_base_type()
|
||||
LoggieEnums.NamelessClassExtensionNameProxy.SCRIPT_NAME:
|
||||
_class_name = script.get_script_property_list().front()["name"]
|
||||
|
||||
return _class_name
|
||||
|
||||
## Opens and reads a .gd script file to find out its 'class_name' or what it 'extends'.
|
||||
## [param path_or_script] should be either an absolute path to the script
|
||||
## (String, e.g. "res://my_script.gd"), or a [Script] object.
|
||||
## [br][param proxy] defines which kind of text will be used as a replacement
|
||||
## for the class name if the script has no 'class_name'.
|
||||
## [br][br][b]Note:[/b] This is a compatibility method that will be used on older versions of Godot which
|
||||
## don't support [method Script.get_global_name].
|
||||
static func extract_class_name_from_gd_script(path_or_script : Variant, proxy : LoggieEnums.NamelessClassExtensionNameProxy) -> String:
|
||||
var path : String
|
||||
|
||||
if path_or_script is String:
|
||||
path = path_or_script
|
||||
elif path_or_script is Script:
|
||||
path = path_or_script.resource_path
|
||||
else:
|
||||
push_error("Invalid 'path_or_script' param provided to extract_class_name_from_gd_script: {path}".format({"path" : path_or_script}))
|
||||
return ""
|
||||
|
||||
var file = FileAccess.open(path, FileAccess.READ)
|
||||
if not file:
|
||||
return "File Open Error {filepath}".format({"filepath" : path})
|
||||
|
||||
var _class_name: String = ""
|
||||
|
||||
for line_num in 40: # Loop only up to 40 lines
|
||||
if file.eof_reached():
|
||||
break
|
||||
|
||||
var line = file.get_line().strip_edges()
|
||||
|
||||
if line.begins_with("class_name"):
|
||||
_class_name = line.split(" ")[1]
|
||||
break
|
||||
|
||||
if _class_name == "":
|
||||
var script = load(path)
|
||||
if script is Script:
|
||||
match proxy:
|
||||
LoggieEnums.NamelessClassExtensionNameProxy.BASE_TYPE:
|
||||
_class_name = script.get_instance_base_type()
|
||||
LoggieEnums.NamelessClassExtensionNameProxy.SCRIPT_NAME:
|
||||
_class_name = script.get_script_property_list().front()["name"]
|
||||
|
||||
file.close()
|
||||
return _class_name
|
||||
|
||||
## Takes the given [param string] and returns an array made out of chunks of the given size.
|
||||
## The string is chunked from start to end.
|
||||
static func chunk_string(string : String, chunk_size : int) -> Array:
|
||||
var message_chunks = []
|
||||
if string.length() >= chunk_size:
|
||||
# Cut chunk_size pieces from the left side of the string and push them to message_chunks.
|
||||
while string.length() >= chunk_size:
|
||||
message_chunks.append(string.left(chunk_size))
|
||||
string = string.substr(chunk_size, -1)
|
||||
|
||||
# Append the remaining slice as the final chunk.
|
||||
if string.length() > 0:
|
||||
message_chunks.append(string)
|
||||
return message_chunks
|
||||
else:
|
||||
return [string]
|
||||
|
||||
## Copies the directory at the given [param path_dir_to_copy] path and places the copy at the given [param path_dir_to_copy_into] path.
|
||||
## Returns a dictionary with 2 keys:
|
||||
##[codeblock]
|
||||
##`errors` : Array[Error] # An array of all errors that occured during the process. ('Error.OK' is an exception and won't be included here)
|
||||
##`messages` : Array[LoggieMsg] # An array of messages describing the process, including informational or error related content.
|
||||
##[/codeblock]
|
||||
static func copy_dir_absolute(path_dir_to_copy: String, path_dir_to_copy_into: String, overwrite_existing_files_with_same_name : bool = false) -> Dictionary:
|
||||
const debug_enabled = false
|
||||
var result = {
|
||||
"errors" : [],
|
||||
"messages" : []
|
||||
}
|
||||
|
||||
# Ensure source directory is openable.
|
||||
var source_dir = DirAccess.open(path_dir_to_copy)
|
||||
if source_dir == null:
|
||||
var open_error = DirAccess.get_open_error()
|
||||
result.errors.push_back(open_error)
|
||||
result.messages.push_back(LoggieMsg.new("Failed to open source directory: ", path_dir_to_copy, " with error: ", error_string(open_error)))
|
||||
return result
|
||||
|
||||
# Ensure target directory is openable.
|
||||
var target_dir = DirAccess.open(path_dir_to_copy_into)
|
||||
var target_dir_path_abs = ProjectSettings.globalize_path(path_dir_to_copy_into)
|
||||
if target_dir == null:
|
||||
var msg = LoggieMsg.new("📂 Target directory not found - creating it at:").msg(path_dir_to_copy_into).color(Color.CADET_BLUE)
|
||||
result.messages.push_back(msg)
|
||||
DirAccess.make_dir_recursive_absolute(path_dir_to_copy_into)
|
||||
target_dir = DirAccess.open(path_dir_to_copy_into)
|
||||
|
||||
# Copy all files from the current source directory into the target directory.
|
||||
for file_name : String in source_dir.get_files():
|
||||
var file_path_abs = ProjectSettings.globalize_path(path_dir_to_copy.path_join(file_name))
|
||||
var target_file_path_abs = target_dir_path_abs.path_join(file_name)
|
||||
var copying_msg = LoggieMsg.new("📝 Copying file...")
|
||||
copying_msg.msg(file_path_abs).italic().color(Color.CORNFLOWER_BLUE).add(" -> ")
|
||||
copying_msg.msg(target_file_path_abs).bold().color(Color.CORNFLOWER_BLUE)
|
||||
|
||||
var is_overwrite_required = false
|
||||
if FileAccess.file_exists(target_file_path_abs):
|
||||
is_overwrite_required = true
|
||||
if overwrite_existing_files_with_same_name:
|
||||
copying_msg.nl().msg("\t[!] Target file already exists and will be overwritten.").bold().color(Color.DARK_KHAKI)
|
||||
else:
|
||||
copying_msg.nl().msg("\t🛑 File will not be copied as overwriting existing files is disabled.").bold().color(Color.SALMON)
|
||||
|
||||
result.messages.push_back(copying_msg)
|
||||
|
||||
if (not is_overwrite_required) or (is_overwrite_required and overwrite_existing_files_with_same_name):
|
||||
var copy_error = DirAccess.copy_absolute(file_path_abs, target_file_path_abs)
|
||||
if copy_error != OK:
|
||||
result.errors.push_back(copy_error)
|
||||
result.messages.push_back(LoggieMsg.new("Attempt to copy file failed with error: '", error_string(copy_error)))
|
||||
|
||||
# Create all of source directory's subdirectories in the target directory and copy their contents.
|
||||
for dir_name : String in source_dir.get_directories():
|
||||
var source_subdir_path = path_dir_to_copy.path_join(dir_name)
|
||||
var source_subdir_path_abs = ProjectSettings.globalize_path(source_subdir_path)
|
||||
var target_subdir_path = path_dir_to_copy_into.path_join(dir_name)
|
||||
var dir_path_abs = ProjectSettings.globalize_path(target_subdir_path)
|
||||
|
||||
result.messages.push_back(LoggieMsg.new("📂 Creating directory: ").msg("{dir}".format({"dir": dir_path_abs})).color(Color.CADET_BLUE))
|
||||
var make_dir_error = DirAccess.make_dir_recursive_absolute(dir_path_abs)
|
||||
if make_dir_error != OK:
|
||||
result.errors.push_back(make_dir_error)
|
||||
var error_msg = LoggieMsg.new("Attempt to create directory at absolute path recursively failed with error: '", error_string(make_dir_error))
|
||||
result.messages.push_back(error_msg)
|
||||
continue
|
||||
|
||||
# Recursively copy the contents of the subdirectory
|
||||
var subdir_copy_result = copy_dir_absolute(source_subdir_path_abs, target_subdir_path, overwrite_existing_files_with_same_name)
|
||||
result.errors = result.errors + subdir_copy_result.errors
|
||||
result.messages = result.messages + subdir_copy_result.messages
|
||||
|
||||
if debug_enabled:
|
||||
for msg : LoggieMsg in result.messages:
|
||||
print_rich(msg.string())
|
||||
|
||||
return result
|
||||
|
||||
## A dictionary of named colors matching the constants from [Color] used to help with rich text coloring.
|
||||
## There may be a way to obtain these Color values without this dictionary if one can somehow check for the
|
||||
## existence and value of a constant on the Color class (since they're already there),
|
||||
## but I can't seem to find a way, so this will have to do for now.
|
||||
static var NamedColors = {
|
||||
"ALICE_BLUE": Color(0.941176, 0.972549, 1, 1),
|
||||
"ANTIQUE_WHITE": Color(0.980392, 0.921569, 0.843137, 1),
|
||||
"AQUA": Color(0, 1, 1, 1),
|
||||
"AQUAMARINE": Color(0.498039, 1, 0.831373, 1),
|
||||
"AZURE": Color(0.941176, 1, 1, 1),
|
||||
"BEIGE": Color(0.960784, 0.960784, 0.862745, 1),
|
||||
"BISQUE": Color(1, 0.894118, 0.768627, 1),
|
||||
"BLACK": Color(0, 0, 0, 1),
|
||||
"BLANCHED_ALMOND": Color(1, 0.921569, 0.803922, 1),
|
||||
"BLUE": Color(0, 0, 1, 1),
|
||||
"BLUE_VIOLET": Color(0.541176, 0.168627, 0.886275, 1),
|
||||
"BROWN": Color(0.647059, 0.164706, 0.164706, 1),
|
||||
"BURLYWOOD": Color(0.870588, 0.721569, 0.529412, 1),
|
||||
"CADET_BLUE": Color(0.372549, 0.619608, 0.627451, 1),
|
||||
"CHARTREUSE": Color(0.498039, 1, 0, 1),
|
||||
"CHOCOLATE": Color(0.823529, 0.411765, 0.117647, 1),
|
||||
"CORAL": Color(1, 0.498039, 0.313726, 1),
|
||||
"CORNFLOWER_BLUE": Color(0.392157, 0.584314, 0.929412, 1),
|
||||
"CORNSILK": Color(1, 0.972549, 0.862745, 1),
|
||||
"CRIMSON": Color(0.862745, 0.0784314, 0.235294, 1),
|
||||
"CYAN": Color(0, 1, 1, 1),
|
||||
"DARK_BLUE": Color(0, 0, 0.545098, 1),
|
||||
"DARK_CYAN": Color(0, 0.545098, 0.545098, 1),
|
||||
"DARK_GOLDENROD": Color(0.721569, 0.52549, 0.0431373, 1),
|
||||
"DARK_GRAY": Color(0.662745, 0.662745, 0.662745, 1),
|
||||
"DARK_GREEN": Color(0, 0.392157, 0, 1),
|
||||
"DARK_KHAKI": Color(0.741176, 0.717647, 0.419608, 1),
|
||||
"DARK_MAGENTA": Color(0.545098, 0, 0.545098, 1),
|
||||
"DARK_OLIVE_GREEN": Color(0.333333, 0.419608, 0.184314, 1),
|
||||
"DARK_ORANGE": Color(1, 0.54902, 0, 1),
|
||||
"DARK_ORCHID": Color(0.6, 0.196078, 0.8, 1),
|
||||
"DARK_RED": Color(0.545098, 0, 0, 1),
|
||||
"DARK_SALMON": Color(0.913725, 0.588235, 0.478431, 1),
|
||||
"DARK_SEA_GREEN": Color(0.560784, 0.737255, 0.560784, 1),
|
||||
"DARK_SLATE_BLUE": Color(0.282353, 0.239216, 0.545098, 1),
|
||||
"DARK_SLATE_GRAY": Color(0.184314, 0.309804, 0.309804, 1),
|
||||
"DARK_TURQUOISE": Color(0, 0.807843, 0.819608, 1),
|
||||
"DARK_VIOLET": Color(0.580392, 0, 0.827451, 1),
|
||||
"DEEP_PINK": Color(1, 0.0784314, 0.576471, 1),
|
||||
"DEEP_SKY_BLUE": Color(0, 0.74902, 1, 1),
|
||||
"DIM_GRAY": Color(0.411765, 0.411765, 0.411765, 1),
|
||||
"DODGER_BLUE": Color(0.117647, 0.564706, 1, 1),
|
||||
"FIREBRICK": Color(0.698039, 0.133333, 0.133333, 1),
|
||||
"FLORAL_WHITE": Color(1, 0.980392, 0.941176, 1),
|
||||
"FOREST_GREEN": Color(0.133333, 0.545098, 0.133333, 1),
|
||||
"FUCHSIA": Color(1, 0, 1, 1),
|
||||
"GAINSBORO": Color(0.862745, 0.862745, 0.862745, 1),
|
||||
"GHOST_WHITE": Color(0.972549, 0.972549, 1, 1),
|
||||
"GOLD": Color(1, 0.843137, 0, 1),
|
||||
"GOLDENROD": Color(0.854902, 0.647059, 0.12549, 1),
|
||||
"GRAY": Color(0.745098, 0.745098, 0.745098, 1),
|
||||
"GREEN": Color(0, 1, 0, 1),
|
||||
"GREEN_YELLOW": Color(0.678431, 1, 0.184314, 1),
|
||||
"HONEYDEW": Color(0.941176, 1, 0.941176, 1),
|
||||
"HOT_PINK": Color(1, 0.411765, 0.705882, 1),
|
||||
"INDIAN_RED": Color(0.803922, 0.360784, 0.360784, 1),
|
||||
"INDIGO": Color(0.294118, 0, 804, 1),
|
||||
"IVORY": Color(1, 1, 0.941176, 1),
|
||||
"KHAKI": Color(0.941176, 0.901961, 0.54902, 1),
|
||||
"LAVENDER": Color(0.901961, 0.901961, 0.980392, 1),
|
||||
"LAVENDER_BLUSH": Color(1, 0.941176, 0.960784, 1),
|
||||
"LAWN_GREEN": Color(0.486275, 0.988235, 0, 1),
|
||||
"LEMON_CHIFFON": Color(1, 0.980392, 0.803922, 1),
|
||||
"LIGHT_BLUE": Color(0.678431, 0.847059, 0.901961, 1),
|
||||
"LIGHT_CORAL": Color(0.941176, 0.501961, 0.501961, 1),
|
||||
"LIGHT_CYAN": Color(0.878431, 1, 1, 1),
|
||||
"LIGHT_GOLDENROD": Color(0.980392, 0.980392, 0.823529, 1),
|
||||
"LIGHT_GRAY": Color(0.827451, 0.827451, 0.827451, 1),
|
||||
"LIGHT_GREEN": Color(0.564706, 0.933333, 0.564706, 1),
|
||||
"LIGHT_PINK": Color(1, 0.713726, 0.756863, 1),
|
||||
"LIGHT_SALMON": Color(1, 0.627451, 0.478431, 1),
|
||||
"LIGHT_SEA_GREEN": Color(0.12549, 0.698039, 0.666667, 1),
|
||||
"LIGHT_SKY_BLUE": Color(0.529412, 0.807843, 0.980392, 1),
|
||||
"LIGHT_SLATE_GRAY": Color(0.466667, 0.533333, 0.6, 1),
|
||||
"LIGHT_STEEL_BLUE": Color(0.690196, 0.768627, 0.870588, 1),
|
||||
"LIGHT_YELLOW": Color(1, 1, 0.878431, 1),
|
||||
"LIME": Color(0, 1, 0, 1),
|
||||
"LIME_GREEN": Color(0.196078, 0.803922, 0.196078, 1),
|
||||
"LINEN": Color(0.980392, 0.941176, 0.901961, 1),
|
||||
"MAGENTA": Color(1, 0, 1, 1),
|
||||
"MAROON": Color(0.690196, 0.188235, 0.376471, 1),
|
||||
"MEDIUM_AQUAMARINE": Color(0.4, 0.803922, 0.666667, 1),
|
||||
"MEDIUM_BLUE": Color(0, 0, 0.803922, 1),
|
||||
"MEDIUM_ORCHID": Color(0.729412, 0.333333, 0.827451, 1),
|
||||
"MEDIUM_PURPLE": Color(0.576471, 0.439216, 0.858824, 1),
|
||||
"MEDIUM_SEA_GREEN": Color(0.235294, 0.701961, 0.443137, 1),
|
||||
"MEDIUM_SLATE_BLUE": Color(0.482353, 0.407843, 0.933333, 1),
|
||||
"MEDIUM_SPRING_GREEN": Color(0, 0.980392, 0.603922, 1),
|
||||
"MEDIUM_TURQUOISE": Color(0.282353, 0.819608, 0.8, 1),
|
||||
"MEDIUM_VIOLET_RED": Color(0.780392, 0.0823529, 0.521569, 1),
|
||||
"MIDNIGHT_BLUE": Color(0.0980392, 0.0980392, 0.439216, 1),
|
||||
"MINT_CREAM": Color(0.960784, 1, 0.980392, 1),
|
||||
"MISTY_ROSE": Color(1, 0.894118, 0.882353, 1),
|
||||
"MOCCASIN": Color(1, 0.894118, 0.709804, 1),
|
||||
"NAVAJO_WHITE": Color(1, 0.870588, 0.678431, 1),
|
||||
"NAVY_BLUE": Color(0, 0, 0.501961, 1),
|
||||
"OLD_LACE": Color(0.992157, 0.960784, 0.901961, 1),
|
||||
"OLIVE": Color(0.501961, 0.501961, 0, 1),
|
||||
"OLIVE_DRAB": Color(0.419608, 0.556863, 0.137255, 1),
|
||||
"ORANGE": Color(1, 0.647059, 0, 1),
|
||||
"ORANGE_RED": Color(1, 0.270588, 0, 1),
|
||||
"ORCHID": Color(0.854902, 0.439216, 0.839216, 1),
|
||||
"PALE_GOLDENROD": Color(0.933333, 0.909804, 0.666667, 1),
|
||||
"PALE_GREEN": Color(0.596078, 0.984314, 0.596078, 1),
|
||||
"PALE_TURQUOISE": Color(0.686275, 0.933333, 0.933333, 1),
|
||||
"PALE_VIOLET_RED": Color(0.858824, 0.439216, 0.576471, 1),
|
||||
"PAPAYA_WHIP": Color(1, 0.937255, 0.835294, 1),
|
||||
"PEACH_PUFF": Color(1, 0.854902, 0.72549, 1),
|
||||
"PERU": Color(0.803922, 0.521569, 0.247059, 1),
|
||||
"PINK": Color(1, 0.752941, 0.796078, 1),
|
||||
"PLUM": Color(0.866667, 0.627451, 0.866667, 1),
|
||||
"POWDER_BLUE": Color(0.690196, 0.878431, 0.901961, 1),
|
||||
"PURPLE": Color(0.627451, 0.12549, 0.941176, 1),
|
||||
"REBECCA_PURPLE": Color(0.4, 0.2, 0.6, 1),
|
||||
"RED": Color(1, 0, 0, 1),
|
||||
"ROSY_BROWN": Color(0.737255, 0.560784, 0.560784, 1),
|
||||
"ROYAL_BLUE": Color(0.254902, 0.411765, 0.882353, 1),
|
||||
"SADDLE_BROWN": Color(0.545098, 0.270588, 0.0745098, 1),
|
||||
"SALMON": Color(0.980392, 0.501961, 0.447059, 1),
|
||||
"SANDY_BROWN": Color(0.956863, 0.643137, 0.376471, 1),
|
||||
"SEA_GREEN": Color(0.180392, 0.545098, 0.341176, 1),
|
||||
"SEASHELL": Color(1, 0.960784, 0.933333, 1),
|
||||
"SIENNA": Color(0.627451, 0.321569, 0.176471, 1),
|
||||
"SILVER": Color(0.752941, 0.752941, 0.752941, 1),
|
||||
"SKY_BLUE": Color(0.529412, 0.807843, 0.921569, 1),
|
||||
"SLATE_BLUE": Color(0.415686, 0.352941, 0.803922, 1),
|
||||
"SLATE_GRAY": Color(0.439216, 0.501961, 0.564706, 1),
|
||||
"SNOW": Color(1, 0.980392, 0.980392, 1),
|
||||
"SPRING_GREEN": Color(0, 1, 0.498039, 1),
|
||||
"STEEL_BLUE": Color(0.27451, 0.509804, 0.705882, 1),
|
||||
"TAN": Color(0.823529, 0.705882, 0.54902, 1),
|
||||
"TEAL": Color(0, 0.501961, 0.501961, 1),
|
||||
"THISTLE": Color(0.847059, 0.74902, 0.847059, 1),
|
||||
"TOMATO": Color(1, 0.388235, 0.278431, 1),
|
||||
"TRANSPARENT": Color(1, 1, 1, 0),
|
||||
"TURQUOISE": Color(0.25098, 0.878431, 0.815686, 1),
|
||||
"VIOLET": Color(0.933333, 0.509804, 0.933333, 1),
|
||||
"WEB_GRAY": Color(0.501961, 0.501961, 0.501961, 1),
|
||||
"WEB_GREEN": Color(0, 0.501961, 0, 1),
|
||||
"WEB_MAROON": Color(0.501961, 0, 0, 1),
|
||||
"WEB_PURPLE": Color(0.501961, 0, 0.501961, 1),
|
||||
"WHEAT": Color(0.960784, 0.870588, 0.701961, 1),
|
||||
"WHITE": Color(1, 1, 1, 1),
|
||||
"WHITE_SMOKE": Color(0.960784, 0.960784, 0.960784, 1),
|
||||
"YELLOW": Color(1, 1, 0, 1),
|
||||
"YELLOW_GREEN": Color(0.603922, 0.803922, 0.196078, 1)
|
||||
}
|
||||
1
addons/loggie/tools/loggie_tools.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://qv478m7f4x2s
|
||||
335
addons/loggie/version_management/loggie_update.gd
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
@tool
|
||||
class_name LoggieUpdate extends Node
|
||||
|
||||
## Emitted when this update fails.
|
||||
signal failed()
|
||||
|
||||
## Emitted when this update succeeds.
|
||||
signal succeeded()
|
||||
|
||||
## Emitted when this declares that it has made some progress.
|
||||
signal progress(value : float)
|
||||
|
||||
## Emitted when this declares that it wants a new status/substatus message to be used.
|
||||
signal status_changed(status_msg : Variant, substatus_msg : Variant)
|
||||
|
||||
## Emitted when this update is starting.
|
||||
signal starting()
|
||||
|
||||
## Emitted when the 'is_in_progress' status of this update changes.
|
||||
signal is_in_progress_changed(new_value : bool)
|
||||
|
||||
## The path to the directory that should have a temporary file created and filled with the patch zipball buffer.
|
||||
const TEMP_FILES_DIR = "user://"
|
||||
|
||||
## If this is set to a non-empty string, it will be used as the directory into which the new update will be
|
||||
## installed. Used for testing/debugging. When set to empty string, Loggie will automatically figure out
|
||||
## where it is being updated from and use that directory instead.
|
||||
const ALT_LOGGIE_PLUGIN_CONTAINER_DIR = ""
|
||||
|
||||
## The domain from which status report [LoggieMsg]s from this update will be logged from.
|
||||
const REPORTS_DOMAIN : String = "loggie_update_status_reports"
|
||||
|
||||
## Stores a reference to the logger that's requesting this update.
|
||||
var _logger : Variant
|
||||
|
||||
## The URL used to visit a page that contains the release notes for this update.
|
||||
var release_notes_url = ""
|
||||
|
||||
## Stores a reference to the previous version the connected [member _logger] is/was using.
|
||||
var prev_version : LoggieVersion = null
|
||||
|
||||
## Stores a reference to the new version the connected [member _logger] should be using after the update.
|
||||
var new_version : LoggieVersion = null
|
||||
|
||||
## Indicates whether this update is currently in progress.
|
||||
var is_in_progress : bool = false
|
||||
|
||||
## Whether the update should retain or purge the backup it makes of the previous version files once it is done
|
||||
## installing and applying the new update.
|
||||
var _clean_up_backup_files : bool = true
|
||||
|
||||
func _init(_prev_version : LoggieVersion, _new_version : LoggieVersion) -> void:
|
||||
self.prev_version = _prev_version
|
||||
self.new_version = _new_version
|
||||
|
||||
## Returns a reference to the logger that's requesting this update.
|
||||
func get_logger() -> Variant:
|
||||
return self._logger
|
||||
|
||||
## Sets the URL used to visit a page that contains the release notes for this update.
|
||||
func set_release_notes_url(url : String) -> void:
|
||||
self.release_notes_url = url
|
||||
|
||||
## Sets whether this window is currently performing the update.
|
||||
func set_is_in_progress(value : bool) -> void:
|
||||
self.is_in_progress = value
|
||||
self.is_in_progress_changed.emit(value)
|
||||
|
||||
## Tries to start the version update. Prevents the update from starting
|
||||
## if something is not configured correctly and pushes a warning/error.
|
||||
func try_start():
|
||||
if Engine.has_meta("LoggieUpdateSuccessful") and Engine.get_meta("LoggieUpdateSuccessful"):
|
||||
# No plan to allow multiple updates to run during a single Engine session anyway so no need to start another one.
|
||||
# Also, this helps with internal testing of the updater and prevents an updated plugin from auto-starting another update
|
||||
# when dealing with proxy versions.
|
||||
return
|
||||
|
||||
if self._logger == null:
|
||||
push_warning("Attempt to start Loggie update failed - member '_logger' on the LoggieUpdate object is null.")
|
||||
return
|
||||
|
||||
if self.is_in_progress:
|
||||
push_warning("Attempt to start Loggie update failed - the update is already in progress.")
|
||||
return
|
||||
|
||||
if self.new_version == null or self.prev_version == null:
|
||||
push_warning("Attempt to start Loggie update failed - the updater prompt has the 'new_version' or 'prev_version' variable at null value.")
|
||||
return
|
||||
elif !self.new_version.is_higher_than(self.prev_version):
|
||||
push_warning("Attempt to start Loggie update failed - the 'new_version' is not higher than 'prev_version'.")
|
||||
return
|
||||
|
||||
if self.new_version.has_meta("github_data"):
|
||||
var github_data = self.new_version.get_meta("github_data")
|
||||
if !github_data.has("zipball_url"):
|
||||
push_error("Attempt to start Loggie update failed - the meta key 'github_data' on the 'new_version' is a dictionary that does not contain the required 'zipball_url' key/value pair.")
|
||||
return
|
||||
else:
|
||||
push_error("Attempt to start Loggie update failed - the meta key 'github_data' on the 'new_version' was not found.")
|
||||
return
|
||||
|
||||
_start()
|
||||
|
||||
## Internal function. Starts the updating of the [param _logger] to the [param new_version].
|
||||
## Do not run without verification that configuration is correct.
|
||||
## Use [method try_start] to call this safely.
|
||||
func _start():
|
||||
var loggie = self.get_logger()
|
||||
|
||||
loggie.msg("Loggie is updating from version {v_prev} to {v_new}.".format({
|
||||
"v_prev" : self.prev_version,
|
||||
"v_new" : self.new_version
|
||||
})).domain(REPORTS_DOMAIN).color(Color.ORANGE).box(12).info()
|
||||
|
||||
set_is_in_progress(true)
|
||||
starting.emit()
|
||||
|
||||
# Make request to configured endpoint.
|
||||
var update_data = self.new_version.get_meta("github_data")
|
||||
var http_request = HTTPRequest.new()
|
||||
loggie.add_child(http_request)
|
||||
http_request.request_completed.connect(_on_download_request_completed)
|
||||
http_request.request(update_data.zipball_url)
|
||||
|
||||
## Internal callback function.
|
||||
## Defines what happens when new update content is successfully downloaded from GitHub.
|
||||
## Called automatically during [method _start] if everything is going according to plan.
|
||||
func _on_download_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||
var loggie = self.get_logger()
|
||||
|
||||
if loggie == null:
|
||||
_failure("The _logger used by the updater window is null.")
|
||||
return
|
||||
|
||||
if result != HTTPRequest.RESULT_SUCCESS:
|
||||
_failure("Download request returned non-zero code: " + str(result))
|
||||
return
|
||||
|
||||
#region || Prepare: Define variables and callbacks that will be used throughout.
|
||||
# The path to the directory which is supposed to contain the plugin directory.
|
||||
# This will usually be 'res://addons/', but could be anything else too. We'll read it dynamically
|
||||
# from the connected logger to guarantee correctness.
|
||||
var LOGGIE_PLUGIN_CONTAINER_DIR = ALT_LOGGIE_PLUGIN_CONTAINER_DIR if !ALT_LOGGIE_PLUGIN_CONTAINER_DIR.is_empty() else loggie.get_directory_path().get_base_dir() + "/"
|
||||
|
||||
# The path to the `loggie` plugin directory.
|
||||
var LOGGIE_PLUGIN_DIR = ProjectSettings.globalize_path(LOGGIE_PLUGIN_CONTAINER_DIR.path_join("loggie/"))
|
||||
|
||||
# The full path filename of the temporary .zip archive that will be created to store the downloaded data.
|
||||
var TEMP_ZIP_FILE_PATH = ProjectSettings.globalize_path(TEMP_FILES_DIR.path_join("_temp_loggie_{ver}.zip".format({"ver": str(new_version)})))
|
||||
|
||||
# The path to the directory where a temporary backup of current loggie plugin files will be copied to.
|
||||
# (will be created if doesn't exist).
|
||||
var TEMP_PREV_VER_FILES_DIR_PATH = ProjectSettings.globalize_path(TEMP_FILES_DIR.path_join("_temp_loggie_{ver}_backup".format({"ver": str(prev_version)})))
|
||||
|
||||
# A callable that can be reused within this function that cleans up the temporary and unused directories,
|
||||
# once this function comes to a conclusion.
|
||||
var clean_up : Callable = func():
|
||||
if FileAccess.file_exists(TEMP_ZIP_FILE_PATH):
|
||||
OS.move_to_trash(TEMP_ZIP_FILE_PATH)
|
||||
if DirAccess.dir_exists_absolute(TEMP_PREV_VER_FILES_DIR_PATH) and self._clean_up_backup_files:
|
||||
OS.move_to_trash(TEMP_PREV_VER_FILES_DIR_PATH)
|
||||
|
||||
# A callable that can be used to replace the currently existing Loggie plugin directory
|
||||
# with whatever is currently (temporarily) stored as its backup.
|
||||
var revert_to_backup = func():
|
||||
if FileAccess.file_exists(LOGGIE_PLUGIN_DIR):
|
||||
OS.move_to_trash(LOGGIE_PLUGIN_DIR)
|
||||
if DirAccess.dir_exists_absolute(TEMP_PREV_VER_FILES_DIR_PATH):
|
||||
DirAccess.rename_absolute(TEMP_PREV_VER_FILES_DIR_PATH, LOGGIE_PLUGIN_DIR)
|
||||
|
||||
#endregion
|
||||
|
||||
#region || Step 1: Store the downloaded content into a temporary zip file.
|
||||
send_progress_update(20, "Processing Files", "Storing patch locally...")
|
||||
|
||||
var zip_file: FileAccess = FileAccess.open(TEMP_ZIP_FILE_PATH, FileAccess.WRITE)
|
||||
if zip_file == null:
|
||||
_failure("Failed to open temp. file for writing: {path}".format({"path": TEMP_ZIP_FILE_PATH}))
|
||||
clean_up.call()
|
||||
return
|
||||
|
||||
zip_file.store_buffer(body)
|
||||
zip_file.close()
|
||||
#endregion
|
||||
|
||||
#region || Step 2: Make a temporary backup of the currently used Loggie plugin directory.
|
||||
send_progress_update(30, "Processing Files", "Backing up previous version files...")
|
||||
|
||||
if !DirAccess.dir_exists_absolute(LOGGIE_PLUGIN_DIR):
|
||||
_failure("The Loggie plugin directory ({path}) could not be found.".format({
|
||||
"path" : LOGGIE_PLUGIN_DIR
|
||||
}))
|
||||
clean_up.call()
|
||||
return
|
||||
|
||||
var copy_prev_ver_result = LoggieTools.copy_dir_absolute(LOGGIE_PLUGIN_DIR, TEMP_PREV_VER_FILES_DIR_PATH, true)
|
||||
if copy_prev_ver_result.errors.size() > 0:
|
||||
var copy_prev_var_result_errors_msg = LoggieMsg.new("Errors encountered:")
|
||||
for error in copy_prev_ver_result.errors:
|
||||
copy_prev_var_result_errors_msg.nl().add(error_string(error))
|
||||
_failure(copy_prev_var_result_errors_msg.string())
|
||||
clean_up.call()
|
||||
return
|
||||
#endregion
|
||||
|
||||
#region || Step 3: Remove currently used Loggie plugin directory and create a new one in its place populated with new version files.
|
||||
send_progress_update(50, "Processing Files", "Copying new version files...")
|
||||
var zip_reader: ZIPReader = ZIPReader.new()
|
||||
var zip_reader_open_error = zip_reader.open(TEMP_ZIP_FILE_PATH)
|
||||
if zip_reader_open_error != OK:
|
||||
_failure("Attempt to open temp. file(s) archive at {path} failed with error: {err_str}".format({
|
||||
"path": LOGGIE_PLUGIN_DIR,
|
||||
"err_str" : error_string(zip_reader_open_error)
|
||||
}))
|
||||
clean_up.call()
|
||||
return
|
||||
|
||||
# Trash the previously existing loggie plugin dir entirely.
|
||||
# A new one will be created in a moment.
|
||||
OS.move_to_trash(LOGGIE_PLUGIN_DIR)
|
||||
|
||||
# Get a list of all files and dirs in the zip.
|
||||
var files : PackedStringArray = zip_reader.get_files()
|
||||
|
||||
# This will always be the "addons" directory in the zip archive in which we expect
|
||||
# to find the "loggie" directory containing the plugin.
|
||||
var base_path_in_zip = files[1]
|
||||
|
||||
# Remove the first 2 parts of the path that we won't be needing at all.
|
||||
files.remove_at(0)
|
||||
files.remove_at(0)
|
||||
|
||||
# Create all needed files and directories.
|
||||
for path in files:
|
||||
var new_file_path: String = path.replace(base_path_in_zip, "")
|
||||
if path.ends_with("/"):
|
||||
DirAccess.make_dir_recursive_absolute(LOGGIE_PLUGIN_CONTAINER_DIR + new_file_path)
|
||||
else:
|
||||
var abs_path = LOGGIE_PLUGIN_CONTAINER_DIR + new_file_path
|
||||
var file : FileAccess = FileAccess.open(abs_path, FileAccess.WRITE)
|
||||
if file == null:
|
||||
_failure("Error while storing buffer data into temporary files - write target directory or file {target} gave the error: {error}".format({
|
||||
"error" : error_string(FileAccess.get_open_error()),
|
||||
"target" : abs_path
|
||||
}))
|
||||
revert_to_backup.call()
|
||||
clean_up.call()
|
||||
return
|
||||
else:
|
||||
var file_content = zip_reader.read_file(path)
|
||||
file.store_buffer(file_content)
|
||||
file.close()
|
||||
|
||||
zip_reader.close()
|
||||
#endregion
|
||||
|
||||
#region || Step 4: Move the user's 'custom_settings.gd' to the new version directory if it existed in prev version.
|
||||
send_progress_update(70, "Processing Files", "Reapplying custom settings...")
|
||||
var CUSTOM_SETTINGS_IN_PREV_VER_PATH = TEMP_PREV_VER_FILES_DIR_PATH.path_join("custom_settings.gd")
|
||||
if FileAccess.file_exists(CUSTOM_SETTINGS_IN_PREV_VER_PATH):
|
||||
var CUSTOM_SETTINGS_IN_NEW_VER_PATH = ProjectSettings.globalize_path(LOGGIE_PLUGIN_DIR.path_join("custom_settings.gd"))
|
||||
var custom_settings_copy_error = DirAccess.copy_absolute(CUSTOM_SETTINGS_IN_PREV_VER_PATH, CUSTOM_SETTINGS_IN_NEW_VER_PATH)
|
||||
if custom_settings_copy_error != OK:
|
||||
push_error("Attempt to copy the 'custom_settings.gd' file from {p1} to {p2} failed with error: {error}".format({
|
||||
"p1" : CUSTOM_SETTINGS_IN_PREV_VER_PATH,
|
||||
"p2" : CUSTOM_SETTINGS_IN_NEW_VER_PATH,
|
||||
"error" : error_string(custom_settings_copy_error)
|
||||
}))
|
||||
#endregion
|
||||
|
||||
#region || Step 5: Move the user's 'channels/custom_channels' directory to the new version if it existed in prev version.
|
||||
send_progress_update(80, "Processing Files", "Reapplying custom channels...")
|
||||
var CUSTOM_CHANNELS_IN_PREV_VER_PATH = ProjectSettings.globalize_path(TEMP_PREV_VER_FILES_DIR_PATH.path_join("channels/custom_channels/"))
|
||||
if DirAccess.dir_exists_absolute(CUSTOM_CHANNELS_IN_PREV_VER_PATH):
|
||||
var CUSTOM_CHANNELS_IN_NEW_VER_PATH = ProjectSettings.globalize_path(LOGGIE_PLUGIN_DIR.path_join("channels/custom_channels/"))
|
||||
var copy_prev_ver_custom_channels_result = LoggieTools.copy_dir_absolute(CUSTOM_CHANNELS_IN_PREV_VER_PATH, CUSTOM_CHANNELS_IN_NEW_VER_PATH, true)
|
||||
if copy_prev_ver_custom_channels_result.errors.size() > 0:
|
||||
var copy_prev_var_result_errors_msg = LoggieMsg.new("Errors encountered:")
|
||||
for error in copy_prev_ver_result.errors:
|
||||
copy_prev_var_result_errors_msg.nl().add(error_string(error))
|
||||
push_error("Attempt to copy the 'channels/custom_channels' directory from {p1} to {p2} failed with error: {error}".format({
|
||||
"p1" : CUSTOM_CHANNELS_IN_PREV_VER_PATH,
|
||||
"p2" : CUSTOM_CHANNELS_IN_NEW_VER_PATH,
|
||||
"error" : copy_prev_var_result_errors_msg.string()
|
||||
}))
|
||||
else:
|
||||
print("The {path} directory doesn't exist.".format({"path": CUSTOM_CHANNELS_IN_PREV_VER_PATH}))
|
||||
#endregion
|
||||
|
||||
#region || Step 6: Clean up temporarily created files and close filewrite.
|
||||
send_progress_update(90, "Processing Files", "Cleaning up...")
|
||||
clean_up.call()
|
||||
#endregion
|
||||
|
||||
#region || Step 7: Declare successful. Wrap up.
|
||||
send_progress_update(100, "Finishing up", "")
|
||||
_success()
|
||||
#endregion
|
||||
|
||||
## Internal function used at the end of the updating process if it is successfully completed.
|
||||
func _success():
|
||||
set_is_in_progress(false)
|
||||
|
||||
var msg = "💬 You may see temporary errors in the console due to Loggie files being re-scanned and reloaded on the spot.\nIt should be safe to dismiss them, but for the best experience, reload the Godot editor (and the plugin, if something seems wrong).\n\n🚩 If you see a 'Files have been modified on disk' window pop up, choose 'Discard local changes and reload' to accept incoming changes."
|
||||
status_changed.emit(null, msg)
|
||||
succeeded.emit()
|
||||
|
||||
print_rich(LoggieMsg.new("👀 Loggie updated to version {new_ver}!".format({"new_ver": self.new_version})).bold().color(Color.ORANGE).string())
|
||||
print_rich(LoggieMsg.new("\t📚 Release Notes: ").bold().msg("[url={url}]{url}[/url]".format({"url": release_notes_url})).color(Color.CORNFLOWER_BLUE).string())
|
||||
print_rich(LoggieMsg.new("\t💬 Support, Development & Feature Requests: ").bold().msg("[url=https://discord.gg/XPdxpMqmcs]https://discord.gg/XPdxpMqmcs[/url]").color(Color.CORNFLOWER_BLUE).string())
|
||||
|
||||
if Engine.is_editor_hint():
|
||||
var editor_plugin = Engine.get_meta("LoggieEditorPlugin")
|
||||
editor_plugin.get_editor_interface().get_resource_filesystem().scan()
|
||||
editor_plugin.get_editor_interface().call_deferred("set_plugin_enabled", "loggie", true)
|
||||
editor_plugin.get_editor_interface().set_plugin_enabled("loggie", false)
|
||||
Engine.set_meta("LoggieUpdateSuccessful", true)
|
||||
print_rich("[b]Updater:[/b] ", msg)
|
||||
|
||||
## Internal function used to interrupt an ongoing update and cause it to fail.
|
||||
func _failure(status_msg : String):
|
||||
var loggie = self.get_logger()
|
||||
loggie.msg(status_msg).color(Color.SALMON).preprocessed(false).error()
|
||||
loggie.msg("\t💬 If this issue persists, consider reporting: ").bold().msg("https://github.com/Shiva-Shadowsong/loggie/issues").color(Color.CORNFLOWER_BLUE).preprocessed(false).info()
|
||||
set_is_in_progress(false)
|
||||
failed.emit()
|
||||
status_changed.emit(null, status_msg)
|
||||
|
||||
## Informs the listeners of the [signal progress] / [signal status_changed] signals about a change in the progress of the update.
|
||||
func send_progress_update(progress_amount : float, status_msg : String, substatus_msg : String):
|
||||
var loggie = self.get_logger()
|
||||
if !substatus_msg.is_empty():
|
||||
loggie.msg("•• ").msg(substatus_msg).domain(REPORTS_DOMAIN).preprocessed(false).info()
|
||||
progress.emit(progress_amount)
|
||||
status_changed.emit(status_msg, substatus_msg)
|
||||
1
addons/loggie/version_management/loggie_update.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dr553ffcn7yyg
|
||||
39
addons/loggie/version_management/loggie_version.gd
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
## A utility class that helps with storing data about a Loggie Version and converting and comparing version strings.
|
||||
class_name LoggieVersion extends Resource
|
||||
|
||||
var minor : int = -1 ## The minor component of the version.
|
||||
var major : int = -1 ## The major component of the version.
|
||||
var proxy_for : LoggieVersion = null ## The version that this version is a proxy for. (Internal use only)
|
||||
|
||||
func _init(_major : int = -1, _minor : int = -1) -> void:
|
||||
self.minor = _minor
|
||||
self.major = _major
|
||||
|
||||
func _to_string() -> String:
|
||||
return str(self.major) + "." + str(self.minor)
|
||||
|
||||
## Checks if this version is valid.
|
||||
## (neither minor nor major component can be less than 0).
|
||||
func is_valid() -> bool:
|
||||
return (minor >= 0 and major >= 0)
|
||||
|
||||
## Checks if the given [param version] if higher than this version.
|
||||
func is_higher_than(version : LoggieVersion):
|
||||
if self.major > version.major:
|
||||
return true
|
||||
if self.minor > version.minor:
|
||||
return true
|
||||
return false
|
||||
|
||||
## Given a string that has 2 sets of digits separated by a ".", breaks that down
|
||||
## into a version with a major and minor version component (ints).
|
||||
static func from_string(version_string : String) -> LoggieVersion:
|
||||
var version : LoggieVersion = LoggieVersion.new()
|
||||
var regex = RegEx.new()
|
||||
regex.compile("(\\d+)\\.(\\d+)")
|
||||
|
||||
var result = regex.search(version_string)
|
||||
if result:
|
||||
version.major = result.get_string(1).to_int()
|
||||
version.minor = result.get_string(2).to_int()
|
||||
return version
|
||||
1
addons/loggie/version_management/loggie_version.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bxwv6sw76aqxm
|
||||
225
addons/loggie/version_management/loggie_version_manager.gd
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
@tool
|
||||
## A class that can be used to inquire about, generate, and operate on [LoggieVersion]s.
|
||||
## It is also responsible for notifying about an available update, and starting it, if configured to do so.
|
||||
class_name LoggieVersionManager extends RefCounted
|
||||
|
||||
## Emitted when this version manager updates the known [member latest_version].
|
||||
signal latest_version_updated()
|
||||
|
||||
## Emitted when this version manager has created a valid [LoggieUpdate] and is ready to use it.
|
||||
signal update_ready()
|
||||
|
||||
## The URL where loggie releases on GitHub can be found.
|
||||
const REMOTE_RELEASES_URL = "https://api.github.com/repos/Shiva-Shadowsong/loggie/releases"
|
||||
|
||||
## The domain from which [LoggieMsg]s from this version manager will be logged from.
|
||||
const REPORTS_DOMAIN : String = "loggie_version_check_reports"
|
||||
|
||||
## Stores the result of reading the Loggie version with [method get_current_Version].
|
||||
var version : LoggieVersion = null
|
||||
|
||||
## Stores the result of reading the latest Loggie version with [method get_latest_version].
|
||||
var latest_version : LoggieVersion = null
|
||||
|
||||
## Stores a reference to a ConfigFile which will be loaded from [member CONFIG_PATH] during [method find_and_store_current_version].
|
||||
var config : ConfigFile = ConfigFile.new()
|
||||
|
||||
## Stores a reference to the logger that's using this version manager.
|
||||
var _logger : Variant = null
|
||||
|
||||
## Stores a reference to the [LoggieUpdate] that has been created to handle an available update.
|
||||
var _update : LoggieUpdate = null
|
||||
|
||||
## Internal debug variable.
|
||||
## If not null, this version manager will treat the [LoggieVersion] provided under this variable to be the current [param version].
|
||||
## Useful for debugging this module when you want to simulate that the current version is something different than it actually is.
|
||||
var _version_proxy : LoggieVersion = null
|
||||
|
||||
## Initializes this version manager, connecting it to the logger that's using it and updating the version cache based on that logger,
|
||||
## which will further prompt the emission of signals in this class, and so on.
|
||||
func connect_logger(logger : Variant) -> void:
|
||||
self.latest_version_updated.connect(on_latest_version_updated)
|
||||
self._logger = logger
|
||||
|
||||
# Set to true during development to enable debug prints related to version management.
|
||||
self._logger.set_domain_enabled(self.REPORTS_DOMAIN, false)
|
||||
|
||||
update_version_cache()
|
||||
|
||||
## Returns a reference to the logger object that is using this version manager.
|
||||
func get_logger() -> Variant:
|
||||
return self._logger
|
||||
|
||||
## Reads the current version of Loggie from plugin.cfg and stores it in [member version].
|
||||
func find_and_store_current_version():
|
||||
var detected_version = self._logger.version
|
||||
if self._version_proxy != null:
|
||||
self.version = self._version_proxy
|
||||
self.version.proxy_for = detected_version
|
||||
else:
|
||||
self.version = detected_version
|
||||
|
||||
## Reads the latest version of Loggie from a GitHub API response and stores it in [member latest_version].
|
||||
func find_and_store_latest_version():
|
||||
var loggie = self.get_logger()
|
||||
var http_request = HTTPRequest.new()
|
||||
loggie.add_child(http_request)
|
||||
loggie.msg("Retrieving version(s) info from endpoint:", REMOTE_RELEASES_URL).domain(REPORTS_DOMAIN).debug()
|
||||
http_request.request_completed.connect(_on_get_latest_version_request_completed, CONNECT_ONE_SHOT)
|
||||
http_request.request(REMOTE_RELEASES_URL)
|
||||
|
||||
## Defines what happens once this version manager emits the signal saying that an update is available.
|
||||
func on_update_available_detected() -> void:
|
||||
var loggie = self.get_logger()
|
||||
if loggie.settings.update_check_mode == LoggieEnums.UpdateCheckType.DONT_CHECK:
|
||||
return
|
||||
|
||||
self._update = LoggieUpdate.new(self.version, self.latest_version)
|
||||
self._update._logger = loggie
|
||||
|
||||
var github_data = self.latest_version.get_meta("github_data")
|
||||
var latest_release_notes_url = github_data.html_url
|
||||
self._update.set_release_notes_url(latest_release_notes_url)
|
||||
loggie.add_child(self._update)
|
||||
update_ready.emit()
|
||||
|
||||
# No plan to allow multiple updates to run during a single Engine session anyway so no need to start another one.
|
||||
# Also, this helps with internal testing of the updater and prevents an updated plugin from auto-starting another update
|
||||
# when dealing with proxy versions.
|
||||
var hasUpdatedAlready = Engine.has_meta("LoggieUpdateSuccessful") and Engine.get_meta("LoggieUpdateSuccessful")
|
||||
|
||||
match loggie.settings.update_check_mode:
|
||||
LoggieEnums.UpdateCheckType.CHECK_AND_SHOW_UPDATER_WINDOW:
|
||||
if hasUpdatedAlready:
|
||||
loggie.info("Update already performed. ✔️")
|
||||
return
|
||||
create_and_show_updater_widget(self._update)
|
||||
LoggieEnums.UpdateCheckType.CHECK_AND_SHOW_MSG:
|
||||
loggie.msg("👀 Loggie update available!").color(Color.ORANGE).header().msg(" > Current version: {version}, Latest version: {latest}".format({
|
||||
"version" : self.version,
|
||||
"latest" : self.latest_version
|
||||
})).info()
|
||||
LoggieEnums.UpdateCheckType.CHECK_DOWNLOAD_AND_SHOW_MSG:
|
||||
if hasUpdatedAlready:
|
||||
loggie.info("Update already performed. ✔️")
|
||||
return
|
||||
loggie.set_domain_enabled("loggie_update_status_reports", true)
|
||||
self._update.try_start()
|
||||
|
||||
## Defines what happens when the request to GitHub API which grabs all the Loggie releases is completed.
|
||||
func _on_get_latest_version_request_completed(result : int, response_code : int, headers : PackedStringArray, body: PackedByteArray):
|
||||
var loggie = self.get_logger()
|
||||
loggie.msg("Response for request received:", response_code).domain(REPORTS_DOMAIN).debug()
|
||||
|
||||
if result != HTTPRequest.RESULT_SUCCESS:
|
||||
return
|
||||
|
||||
var response = JSON.parse_string(body.get_string_from_utf8())
|
||||
|
||||
if typeof(response) != TYPE_ARRAY:
|
||||
loggie.error("The response parsed from GitHub was not an array. Response received in an unsupported format.")
|
||||
return
|
||||
|
||||
var latest_version_data = response[0] # GitHub releases are in order of creation, so grab the first one from the response, that's the latest one.
|
||||
self.latest_version = LoggieVersion.from_string(latest_version_data.tag_name)
|
||||
self.latest_version.set_meta("github_data", latest_version_data)
|
||||
|
||||
loggie.msg("Current version of Loggie:", self.version).msg(" (proxy for: {value})".format({"value": self.version.proxy_for})).domain(REPORTS_DOMAIN).debug()
|
||||
loggie.msg("Latest version of Loggie:", self.latest_version).domain(REPORTS_DOMAIN).debug()
|
||||
latest_version_updated.emit()
|
||||
|
||||
## Executes every time this version manager updates the known latest_version.
|
||||
func on_latest_version_updated() -> void:
|
||||
var loggie = self.get_logger()
|
||||
if loggie == null:
|
||||
return
|
||||
|
||||
# Check if update is available.
|
||||
if loggie.settings.update_check_mode != LoggieEnums.UpdateCheckType.DONT_CHECK:
|
||||
loggie.msg("👀 Loggie:").bold().color("orange").msg(" Checking for updates...").info()
|
||||
if is_update_available():
|
||||
on_update_available_detected()
|
||||
else:
|
||||
loggie.msg("👀 Loggie:").bold().color("orange").msg(" Up to date. ✔️").color(Color.LIGHT_GREEN).info()
|
||||
|
||||
## Displays the widget which informs the user of the available update and offers actions that they can take next.
|
||||
func create_and_show_updater_widget(update : LoggieUpdate) -> Window:
|
||||
const PATH_TO_WIDGET_SCENE = "addons/loggie/version_management/update_prompt_window.tscn"
|
||||
var WIDGET_SCENE = load(PATH_TO_WIDGET_SCENE)
|
||||
if !is_instance_valid(WIDGET_SCENE):
|
||||
push_error("Loggie Update Prompt Window scene not found on expected path: {path}".format({"path": PATH_TO_WIDGET_SCENE}))
|
||||
return
|
||||
|
||||
var loggie = self.get_logger()
|
||||
if loggie == null:
|
||||
return
|
||||
|
||||
var popup_parent = null
|
||||
if Engine.is_editor_hint() and Engine.has_meta("LoggieEditorInterfaceBaseControl"):
|
||||
popup_parent = Engine.get_meta("LoggieEditorInterfaceBaseControl")
|
||||
else:
|
||||
popup_parent = SceneTree.current_scene
|
||||
|
||||
# Configure popup window.
|
||||
var _popup = Window.new()
|
||||
update.succeeded.connect(func():
|
||||
_popup.queue_free()
|
||||
var success_dialog = AcceptDialog.new()
|
||||
var msg = "💬 You may see temporary errors in the console due to Loggie files being re-scanned and reloaded on the spot.\nIt should be safe to dismiss them, but for the best experience, reload the Godot editor (and the plugin, if something seems wrong).\n\n🚩 If you see a 'Files have been modified on disk' window pop up, choose 'Discard local changes and reload' to accept incoming changes."
|
||||
success_dialog.dialog_text = msg
|
||||
success_dialog.title = "Loggie Updater"
|
||||
if is_instance_valid(popup_parent):
|
||||
popup_parent.add_child(success_dialog)
|
||||
success_dialog.popup_centered()
|
||||
)
|
||||
var on_close_requested = func():
|
||||
_popup.queue_free()
|
||||
|
||||
_popup.close_requested.connect(on_close_requested, CONNECT_ONE_SHOT)
|
||||
_popup.borderless = false
|
||||
_popup.unresizable = true
|
||||
_popup.transient = true
|
||||
_popup.title = "Update Available"
|
||||
|
||||
# Configure window widget and add it as a child of the popup window.
|
||||
var widget : LoggieUpdatePrompt = WIDGET_SCENE.instantiate()
|
||||
widget.connect_to_update(update)
|
||||
widget.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
widget._logger = loggie
|
||||
widget.close_requested.connect(on_close_requested, CONNECT_ONE_SHOT)
|
||||
|
||||
if is_instance_valid(popup_parent):
|
||||
popup_parent.add_child(_popup)
|
||||
_popup.popup_centered(widget.host_window_size)
|
||||
_popup.add_child(widget)
|
||||
|
||||
return _popup
|
||||
|
||||
## Updates the local variables which point to the current and latest version of Loggie.
|
||||
func update_version_cache():
|
||||
# Read and cache the current version of Loggie from plugin.cfg.
|
||||
find_and_store_current_version()
|
||||
|
||||
# Read and cache the latest version of Loggie from GitHub.
|
||||
# (Do it only if running in editor, no need for this if running in a game).
|
||||
var logger = self.get_logger()
|
||||
if logger is Node:
|
||||
if !Engine.is_editor_hint():
|
||||
return
|
||||
if logger.is_node_ready():
|
||||
find_and_store_latest_version()
|
||||
else:
|
||||
logger.ready.connect(func():
|
||||
find_and_store_latest_version()
|
||||
, CONNECT_ONE_SHOT)
|
||||
|
||||
## Checks if an update for Loggie is available. Run only after running [method update_version_cache].
|
||||
func is_update_available() -> bool:
|
||||
var loggie = self.get_logger()
|
||||
if !(self.version is LoggieVersion and self.version.is_valid()):
|
||||
loggie.error("The current version of Loggie is not valid. Run `find_and_store_current_version` once to obtain this value first.")
|
||||
return false
|
||||
if !(self.latest_version is LoggieVersion and self.latest_version.is_valid()):
|
||||
loggie.error("The latest version of Loggie is not valid. Run `find_and_store_latest_version` once to obtain this value first.")
|
||||
return false
|
||||
return self.latest_version.is_higher_than(self.version)
|
||||
|
|
@ -0,0 +1 @@
|
|||
uid://cdu8t2jbqqvb2
|
||||
192
addons/loggie/version_management/update_prompt_window.gd
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
@tool
|
||||
## The Loggie Update Prompt is a control node that is meant to be created and added as a child of some other node, most commonly a [Window].
|
||||
## It connects to a [LoggieUpdate] via its [method connect_to_update] method, then displays data about that update depending on what kind of
|
||||
## data that [LoggieUpdate] provides with its signals.
|
||||
class_name LoggieUpdatePrompt extends Panel
|
||||
|
||||
## Emitted when the user requests to close the update prompt.
|
||||
signal close_requested()
|
||||
|
||||
## The animation player that will be used to animate the appearance of this window.
|
||||
@export var animator : AnimationPlayer
|
||||
|
||||
## The size the window that's hosting this panel will be forced to assume when
|
||||
## it's ready to pop this up on the screen.
|
||||
@export var host_window_size : Vector2 = Vector2(1063, 672)
|
||||
|
||||
## Stores a reference to the logger that's using this window.
|
||||
var _logger : Variant
|
||||
|
||||
## The update this window is visually representing.
|
||||
var _update : LoggieUpdate
|
||||
|
||||
## Stores a boolean which indicates whether the update is currently in progress.
|
||||
var is_currently_updating : bool = false
|
||||
|
||||
func _ready() -> void:
|
||||
connect_control_effects()
|
||||
%UpdateNowBtn.grab_focus()
|
||||
refresh_remind_later_btn()
|
||||
animator.play("RESET")
|
||||
|
||||
## Connects this window to an instance of [LoggieUpdate] whose progress and properties this window is supposed to track.
|
||||
func connect_to_update(p_update : LoggieUpdate) -> void:
|
||||
self._update = p_update
|
||||
_update.is_in_progress_changed.connect(is_update_in_progress_changed)
|
||||
_update.starting.connect(on_update_starting)
|
||||
_update.succeeded.connect(on_update_succeeded)
|
||||
_update.failed.connect(on_update_failed)
|
||||
_update.progress.connect(on_update_progress)
|
||||
_update.status_changed.connect(on_update_status_changed)
|
||||
|
||||
## Returns a reference to the logger object that is using this widget.
|
||||
func get_logger() -> Variant:
|
||||
return self._logger
|
||||
|
||||
## Defines what happens when the update this window is representing updates its "is in progress" status.
|
||||
func is_update_in_progress_changed(is_in_progress : bool) -> void:
|
||||
self.is_currently_updating = is_in_progress
|
||||
|
||||
# The first time we enter the UpdateMonitor view and start an update,
|
||||
# the %Notice node and its children should no longer (ever) be interactive or processing,
|
||||
# since there is no way to get back to that view anymore.
|
||||
if is_in_progress and %Notice.process_mode != Node.PROCESS_MODE_DISABLED:
|
||||
%Notice.process_mode = Node.PROCESS_MODE_DISABLED
|
||||
for child in %NoticeButtons.get_children():
|
||||
if child is Button:
|
||||
child.focus_mode = Button.FOCUS_NONE
|
||||
|
||||
## Connects the effects and functionalities of various controls in this window.
|
||||
func connect_control_effects():
|
||||
if !is_instance_valid(self._update):
|
||||
return
|
||||
|
||||
# Configure version(s) labels.
|
||||
%LabelCurrentVersion.text = str(self._update.prev_version)
|
||||
%LabelLatestVersion.text = str(self._update.new_version)
|
||||
%LabelOldVersion.text = str(self._update.prev_version)
|
||||
%LabelNewVersion.text = str(self._update.new_version)
|
||||
|
||||
# Configure onhover/focused effects.
|
||||
var buttons_with_on_focushover_effect = [%OptionExitBtn, %OptionRestartGodotBtn, %OptionRetryUpdateBtn, %ReleaseNotesBtn, %RemindLaterBtn, %UpdateNowBtn]
|
||||
for button : Button in buttons_with_on_focushover_effect:
|
||||
button.focus_entered.connect(_on_button_focus_entered.bind(button))
|
||||
button.focus_exited.connect(_on_button_focus_exited.bind(button))
|
||||
button.mouse_entered.connect(_on_button_focus_entered.bind(button))
|
||||
button.mouse_exited.connect(_on_button_focus_exited.bind(button))
|
||||
button.pivot_offset = button.size * 0.5
|
||||
|
||||
# Connect behavior which executes when "Update Now" is pressed.
|
||||
%UpdateNowBtn.pressed.connect(func():
|
||||
self._update.try_start()
|
||||
)
|
||||
|
||||
# Configure Release Notes button.
|
||||
%ReleaseNotesBtn.visible = !self._update.release_notes_url.is_empty()
|
||||
%ReleaseNotesBtn.tooltip_text = "Opens {url} in browser.".format({"url": self._update.release_notes_url})
|
||||
%ReleaseNotesBtn.pressed.connect(func():
|
||||
if !self._update.release_notes_url.is_empty():
|
||||
OS.shell_open(self._update.release_notes_url)
|
||||
)
|
||||
|
||||
# Connect behavior which executes when the "Remind Me Later / Close" is pressed.
|
||||
%RemindLaterBtn.pressed.connect(func(): close_requested.emit())
|
||||
%OptionExitBtn.pressed.connect(func(): close_requested.emit())
|
||||
|
||||
# Connect behavior which executes when the "Retry" button is pressed.
|
||||
%OptionRetryUpdateBtn.pressed.connect(self._update.try_start)
|
||||
|
||||
# Connect behavior which executes when the "Restart Godot" button is pressed.
|
||||
%OptionRestartGodotBtn.pressed.connect(func():
|
||||
close_requested.emit()
|
||||
if Engine.is_editor_hint():
|
||||
var editor_plugin = Engine.get_meta("LoggieEditorPlugin")
|
||||
editor_plugin.get_editor_interface().restart_editor(true)
|
||||
)
|
||||
|
||||
# The "Don't show again checkbox" updates project settings whenever it is toggled.
|
||||
%DontShowAgainCheckbox.toggled.connect(func(toggled_on : bool):
|
||||
var loggie = self.get_logger()
|
||||
if Engine.is_editor_hint() and loggie != null:
|
||||
if toggled_on:
|
||||
loggie.settings.update_check_mode = LoggieEnums.UpdateCheckType.CHECK_AND_SHOW_MSG
|
||||
ProjectSettings.set_setting(loggie.settings.project_settings.update_check_mode.path, LoggieEnums.UpdateCheckType.CHECK_AND_SHOW_MSG)
|
||||
else:
|
||||
loggie.settings.update_check_mode = LoggieEnums.UpdateCheckType.CHECK_AND_SHOW_UPDATER_WINDOW
|
||||
ProjectSettings.set_setting(loggie.settings.project_settings.update_check_mode.path, LoggieEnums.UpdateCheckType.CHECK_AND_SHOW_UPDATER_WINDOW)
|
||||
refresh_remind_later_btn()
|
||||
)
|
||||
|
||||
## Updates the content of the "Remind Later Btn" to show text appropriate to the current state of the "Don't show again" checkbox.
|
||||
func refresh_remind_later_btn():
|
||||
if %DontShowAgainCheckbox.button_pressed:
|
||||
%RemindLaterBtn.text = "close"
|
||||
else:
|
||||
%RemindLaterBtn.text = "remind later"
|
||||
|
||||
## Defines what happens when the [member _update] is detected to be starting.
|
||||
func on_update_starting():
|
||||
if animator.current_animation != "show_update_overview":
|
||||
animator.queue("show_update_overview")
|
||||
%ProgressBar.value = 0
|
||||
%LabelMainStatus.text = "Downloading"
|
||||
%LabelUpdateStatus.text = "Waiting for server response."
|
||||
%OptionExitBtn.disabled = true
|
||||
%OptionExitBtn.visible = false
|
||||
%OptionRetryUpdateBtn.disabled = true
|
||||
%OptionRetryUpdateBtn.visible = false
|
||||
%OptionRestartGodotBtn.disabled = true
|
||||
%OptionRestartGodotBtn.visible = false
|
||||
|
||||
## Defines what happens when the [member _update] declares it has made progress.
|
||||
func on_update_progress(value : float):
|
||||
%ProgressBar.value = value
|
||||
|
||||
## Defines what happens when the [member _update] declares it has succeeded.
|
||||
func on_update_succeeded():
|
||||
%LabelMainStatus.text = "Updated"
|
||||
%OptionExitBtn.disabled = false
|
||||
%OptionExitBtn.visible = true
|
||||
%OptionRestartGodotBtn.disabled = false
|
||||
%OptionRestartGodotBtn.visible = true
|
||||
if animator.is_playing():
|
||||
animator.stop()
|
||||
animator.play("finish_success")
|
||||
|
||||
## Defines what happens when the [member _update] declares it wants the status message to change.
|
||||
func on_update_status_changed(status_msg : Variant, substatus_msg : Variant):
|
||||
if status_msg is String:
|
||||
%LabelMainStatus.text = status_msg
|
||||
if substatus_msg is String:
|
||||
%LabelUpdateStatus.text = substatus_msg
|
||||
|
||||
## Defines what happens when the [member _update] declares it has failed.
|
||||
func on_update_failed():
|
||||
%ProgressBar.value = 0
|
||||
%LabelMainStatus.text = "Failed"
|
||||
%OptionExitBtn.disabled = false
|
||||
%OptionExitBtn.visible = true
|
||||
%OptionRetryUpdateBtn.disabled = false
|
||||
%OptionRetryUpdateBtn.visible = true
|
||||
%OptionRestartGodotBtn.disabled = true
|
||||
%OptionRestartGodotBtn.visible = false
|
||||
|
||||
func _on_button_focus_entered(button : Button):
|
||||
if button.has_meta("scale_tween"):
|
||||
var old_tween = button.get_meta("scale_tween")
|
||||
old_tween.kill()
|
||||
button.set_meta("scale_tween", null)
|
||||
|
||||
var tween : Tween = button.create_tween()
|
||||
tween.tween_property(button, "scale", Vector2(1.2, 1.2), 0.1)
|
||||
button.set_meta("scale_tween", tween)
|
||||
|
||||
func _on_button_focus_exited(button : Button):
|
||||
if button.has_meta("scale_tween"):
|
||||
var old_tween = button.get_meta("scale_tween")
|
||||
old_tween.kill()
|
||||
button.set_meta("scale_tween", null)
|
||||
|
||||
var tween : Tween = button.create_tween()
|
||||
tween.tween_property(button, "scale", Vector2(1.0, 1.0), 0.1).from_current()
|
||||
button.set_meta("scale_tween", tween)
|
||||
|
|
@ -0,0 +1 @@
|
|||
uid://dh7dlpputw3bg
|
||||
818
addons/loggie/version_management/update_prompt_window.tscn
Normal file
|
|
@ -0,0 +1,818 @@
|
|||
[gd_scene load_steps=32 format=3 uid="uid://d2eq0khfi3s15"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/loggie/version_management/update_prompt_window.gd" id="1_xb7je"]
|
||||
[ext_resource type="Texture2D" uid="uid://cgh6hd3u8nlpj" path="res://addons/loggie/assets/updater_bg.png" id="2_lbawe"]
|
||||
[ext_resource type="FontFile" uid="uid://e3rpni7mpu0p" path="res://addons/loggie/assets/theme/fonts/coffee_soda.ttf" id="3_a36jf"]
|
||||
[ext_resource type="FontFile" uid="uid://btuvtbhws7p8s" path="res://addons/loggie/assets/theme/fonts/PatrickHandSC-Regular.ttf" id="4_lnh27"]
|
||||
[ext_resource type="StyleBox" uid="uid://ckw36egxdynxc" path="res://addons/loggie/assets/theme/loggie_border_box.tres" id="5_avea8"]
|
||||
[ext_resource type="Texture2D" uid="uid://bfnp2a0sbhi2x" path="res://addons/loggie/assets/theme/checkbox/checkbox_checked.png" id="6_yoxpw"]
|
||||
[ext_resource type="Texture2D" uid="uid://bib1lwikra5kr" path="res://addons/loggie/assets/theme/checkbox/checkbox_unchecked.png" id="7_xu2hn"]
|
||||
[ext_resource type="Texture2D" uid="uid://dqf5cye21gyw8" path="res://addons/loggie/assets/theme/checkbox/checkbox_checked_disabled.png" id="8_4h2cx"]
|
||||
[ext_resource type="Texture2D" uid="uid://cloe7vx2ej0nf" path="res://addons/loggie/assets/theme/checkbox/checkbox_unchecked_disabled.png" id="9_51n7f"]
|
||||
[ext_resource type="Texture2D" uid="uid://2fr6et0qni2y" path="res://addons/loggie/assets/icon.png" id="10_abt8m"]
|
||||
[ext_resource type="Theme" uid="uid://bntkg3oi4b314" path="res://addons/loggie/assets/theme/loggie_theme.tres" id="11_5uxhl"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3bh8r"]
|
||||
draw_center = false
|
||||
border_width_bottom = 4
|
||||
border_color = Color(0.86, 0.6794, 0.344, 1)
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_rjg43"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_bwgrr"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_qbrxo"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_3asxb"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_86pfv"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_rw12e"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_j1ttd"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_3sc8x"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ndum7"]
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_wnvcb"]
|
||||
colors = PackedColorArray(0.0311948, 0.0139167, 0.0080977, 1, 0.125625, 0.1125, 0.15, 1)
|
||||
|
||||
[sub_resource type="GradientTexture1D" id="GradientTexture1D_k1b6u"]
|
||||
gradient = SubResource("Gradient_wnvcb")
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_rkgx7"]
|
||||
interpolation_mode = 2
|
||||
offsets = PackedFloat32Array(0, 0.270059, 1)
|
||||
colors = PackedColorArray(0.584314, 0.337255, 0.145098, 1, 0.495303, 0.287021, 0.13981, 1, 0.188235, 0.113725, 0.121569, 1)
|
||||
|
||||
[sub_resource type="GradientTexture2D" id="GradientTexture2D_p6ad4"]
|
||||
gradient = SubResource("Gradient_rkgx7")
|
||||
fill_from = Vector2(0.239316, 0)
|
||||
fill_to = Vector2(0.303419, 0.876068)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_y40yc"]
|
||||
bg_color = Color(0.12, 0.0528, 0.09088, 0.556863)
|
||||
border_width_left = 3
|
||||
border_width_top = 3
|
||||
border_width_right = 3
|
||||
border_width_bottom = 6
|
||||
border_color = Color(0.533576, 0.418125, 0.23421, 1)
|
||||
border_blend = true
|
||||
corner_radius_top_left = 11
|
||||
corner_radius_top_right = 11
|
||||
corner_radius_bottom_right = 11
|
||||
corner_radius_bottom_left = 11
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_h3ybw"]
|
||||
bg_color = Color(0.69, 0.49128, 0.2484, 0.486275)
|
||||
border_width_right = 4
|
||||
border_color = Color(0.94, 0.6768, 0.282, 0.541176)
|
||||
corner_radius_top_left = 18
|
||||
corner_radius_top_right = 18
|
||||
corner_radius_bottom_right = 18
|
||||
corner_radius_bottom_left = 18
|
||||
|
||||
[sub_resource type="Animation" id="Animation_almc7"]
|
||||
length = 0.001
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath(".:position")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": [Vector2(0, -673)]
|
||||
}
|
||||
tracks/1/type = "value"
|
||||
tracks/1/imported = false
|
||||
tracks/1/enabled = true
|
||||
tracks/1/path = NodePath(".:visible")
|
||||
tracks/1/interp = 1
|
||||
tracks/1/loop_wrap = true
|
||||
tracks/1/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [false]
|
||||
}
|
||||
tracks/2/type = "value"
|
||||
tracks/2/imported = false
|
||||
tracks/2/enabled = true
|
||||
tracks/2/path = NodePath("VBoxContainer/OptionButtons/OptionRetryUpdateBtn:disabled")
|
||||
tracks/2/interp = 1
|
||||
tracks/2/loop_wrap = true
|
||||
tracks/2/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [true]
|
||||
}
|
||||
tracks/3/type = "value"
|
||||
tracks/3/imported = false
|
||||
tracks/3/enabled = true
|
||||
tracks/3/path = NodePath("VBoxContainer/OptionButtons/OptionExitBtn:disabled")
|
||||
tracks/3/interp = 1
|
||||
tracks/3/loop_wrap = true
|
||||
tracks/3/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [true]
|
||||
}
|
||||
tracks/4/type = "value"
|
||||
tracks/4/imported = false
|
||||
tracks/4/enabled = true
|
||||
tracks/4/path = NodePath("VBoxContainer/OptionButtons/OptionRetryUpdateBtn:visible")
|
||||
tracks/4/interp = 1
|
||||
tracks/4/loop_wrap = true
|
||||
tracks/4/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [false]
|
||||
}
|
||||
tracks/5/type = "value"
|
||||
tracks/5/imported = false
|
||||
tracks/5/enabled = true
|
||||
tracks/5/path = NodePath("VBoxContainer/OptionButtons/OptionExitBtn:visible")
|
||||
tracks/5/interp = 1
|
||||
tracks/5/loop_wrap = true
|
||||
tracks/5/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [false]
|
||||
}
|
||||
tracks/6/type = "value"
|
||||
tracks/6/imported = false
|
||||
tracks/6/enabled = true
|
||||
tracks/6/path = NodePath("%LabelOldVersion:modulate")
|
||||
tracks/6/interp = 1
|
||||
tracks/6/loop_wrap = true
|
||||
tracks/6/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": [Color(1, 1, 1, 1)]
|
||||
}
|
||||
tracks/7/type = "value"
|
||||
tracks/7/imported = false
|
||||
tracks/7/enabled = true
|
||||
tracks/7/path = NodePath("%LabelNewVersion:modulate")
|
||||
tracks/7/interp = 1
|
||||
tracks/7/loop_wrap = true
|
||||
tracks/7/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": [Color(1, 1, 1, 1)]
|
||||
}
|
||||
tracks/8/type = "value"
|
||||
tracks/8/imported = false
|
||||
tracks/8/enabled = true
|
||||
tracks/8/path = NodePath("%ProgressBar:modulate")
|
||||
tracks/8/interp = 1
|
||||
tracks/8/loop_wrap = true
|
||||
tracks/8/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": [Color(1, 1, 1, 1)]
|
||||
}
|
||||
tracks/9/type = "value"
|
||||
tracks/9/imported = false
|
||||
tracks/9/enabled = true
|
||||
tracks/9/path = NodePath("VBoxContainer:position")
|
||||
tracks/9/interp = 1
|
||||
tracks/9/loop_wrap = true
|
||||
tracks/9/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": [Vector2(0, 532)]
|
||||
}
|
||||
tracks/10/type = "value"
|
||||
tracks/10/imported = false
|
||||
tracks/10/enabled = true
|
||||
tracks/10/path = NodePath("LoggieIcon:scale")
|
||||
tracks/10/interp = 1
|
||||
tracks/10/loop_wrap = true
|
||||
tracks/10/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": [Vector2(1, 1)]
|
||||
}
|
||||
|
||||
[sub_resource type="Animation" id="Animation_dewq5"]
|
||||
resource_name = "finish_success"
|
||||
length = 0.5
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath(".:position")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": [Vector2(0, 0)]
|
||||
}
|
||||
tracks/1/type = "value"
|
||||
tracks/1/imported = false
|
||||
tracks/1/enabled = true
|
||||
tracks/1/path = NodePath(".:visible")
|
||||
tracks/1/interp = 1
|
||||
tracks/1/loop_wrap = true
|
||||
tracks/1/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [true]
|
||||
}
|
||||
tracks/2/type = "value"
|
||||
tracks/2/imported = false
|
||||
tracks/2/enabled = true
|
||||
tracks/2/path = NodePath("%LabelOldVersion:modulate")
|
||||
tracks/2/interp = 1
|
||||
tracks/2/loop_wrap = true
|
||||
tracks/2/keys = {
|
||||
"times": PackedFloat32Array(0, 0.2),
|
||||
"transitions": PackedFloat32Array(1, 1),
|
||||
"update": 0,
|
||||
"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 0)]
|
||||
}
|
||||
tracks/3/type = "value"
|
||||
tracks/3/imported = false
|
||||
tracks/3/enabled = true
|
||||
tracks/3/path = NodePath("%LabelNewVersion:modulate")
|
||||
tracks/3/interp = 1
|
||||
tracks/3/loop_wrap = true
|
||||
tracks/3/keys = {
|
||||
"times": PackedFloat32Array(0, 0.2),
|
||||
"transitions": PackedFloat32Array(1, 1),
|
||||
"update": 0,
|
||||
"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 0)]
|
||||
}
|
||||
tracks/4/type = "value"
|
||||
tracks/4/imported = false
|
||||
tracks/4/enabled = true
|
||||
tracks/4/path = NodePath("%ProgressBar:modulate")
|
||||
tracks/4/interp = 1
|
||||
tracks/4/loop_wrap = true
|
||||
tracks/4/keys = {
|
||||
"times": PackedFloat32Array(0, 0.2),
|
||||
"transitions": PackedFloat32Array(1, 1),
|
||||
"update": 0,
|
||||
"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 0)]
|
||||
}
|
||||
tracks/5/type = "value"
|
||||
tracks/5/imported = false
|
||||
tracks/5/enabled = true
|
||||
tracks/5/path = NodePath("VBoxContainer:position")
|
||||
tracks/5/interp = 1
|
||||
tracks/5/loop_wrap = true
|
||||
tracks/5/keys = {
|
||||
"times": PackedFloat32Array(0, 0.466667),
|
||||
"transitions": PackedFloat32Array(1, 1),
|
||||
"update": 0,
|
||||
"values": [Vector2(0, 532), Vector2(1, 432)]
|
||||
}
|
||||
tracks/6/type = "value"
|
||||
tracks/6/imported = false
|
||||
tracks/6/enabled = true
|
||||
tracks/6/path = NodePath("LoggieIcon:scale")
|
||||
tracks/6/interp = 1
|
||||
tracks/6/loop_wrap = true
|
||||
tracks/6/keys = {
|
||||
"times": PackedFloat32Array(0, 0.166667, 0.2, 0.3, 0.366667, 0.433333),
|
||||
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1),
|
||||
"update": 0,
|
||||
"values": [Vector2(1, 1), Vector2(1, 1), Vector2(1.33, 1), Vector2(1.075, 1.575), Vector2(0.735, 1.465), Vector2(1, 1)]
|
||||
}
|
||||
|
||||
[sub_resource type="Animation" id="Animation_jm1cu"]
|
||||
resource_name = "show_update_overview"
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath(".:position")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"times": PackedFloat32Array(0, 0.2, 0.266667, 0.333333),
|
||||
"transitions": PackedFloat32Array(1, 1, 1, 1),
|
||||
"update": 0,
|
||||
"values": [Vector2(0, -673), Vector2(0, 0), Vector2(0, 16), Vector2(0, 0)]
|
||||
}
|
||||
tracks/1/type = "value"
|
||||
tracks/1/imported = false
|
||||
tracks/1/enabled = true
|
||||
tracks/1/path = NodePath(".:visible")
|
||||
tracks/1/interp = 1
|
||||
tracks/1/loop_wrap = true
|
||||
tracks/1/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [true]
|
||||
}
|
||||
tracks/2/type = "value"
|
||||
tracks/2/imported = false
|
||||
tracks/2/enabled = true
|
||||
tracks/2/path = NodePath("%ProgressBar:modulate")
|
||||
tracks/2/interp = 1
|
||||
tracks/2/loop_wrap = true
|
||||
tracks/2/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": [Color(1, 1, 1, 1)]
|
||||
}
|
||||
tracks/3/type = "value"
|
||||
tracks/3/imported = false
|
||||
tracks/3/enabled = true
|
||||
tracks/3/path = NodePath("%LabelOldVersion:modulate")
|
||||
tracks/3/interp = 1
|
||||
tracks/3/loop_wrap = true
|
||||
tracks/3/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": [Color(1, 1, 1, 1)]
|
||||
}
|
||||
tracks/4/type = "value"
|
||||
tracks/4/imported = false
|
||||
tracks/4/enabled = true
|
||||
tracks/4/path = NodePath("%LabelNewVersion:modulate")
|
||||
tracks/4/interp = 1
|
||||
tracks/4/loop_wrap = true
|
||||
tracks/4/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": [Color(1, 1, 1, 1)]
|
||||
}
|
||||
tracks/5/type = "value"
|
||||
tracks/5/imported = false
|
||||
tracks/5/enabled = true
|
||||
tracks/5/path = NodePath("LoggieIcon:scale")
|
||||
tracks/5/interp = 1
|
||||
tracks/5/loop_wrap = true
|
||||
tracks/5/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": [Vector2(1, 1)]
|
||||
}
|
||||
tracks/6/type = "value"
|
||||
tracks/6/imported = false
|
||||
tracks/6/enabled = true
|
||||
tracks/6/path = NodePath("VBoxContainer:position")
|
||||
tracks/6/interp = 1
|
||||
tracks/6/loop_wrap = true
|
||||
tracks/6/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": [Vector2(0, 532)]
|
||||
}
|
||||
|
||||
[sub_resource type="AnimationLibrary" id="AnimationLibrary_lwiu5"]
|
||||
_data = {
|
||||
"RESET": SubResource("Animation_almc7"),
|
||||
"finish_success": SubResource("Animation_dewq5"),
|
||||
"show_update_overview": SubResource("Animation_jm1cu")
|
||||
}
|
||||
|
||||
[node name="UpdatePromptWindow" type="Panel" node_paths=PackedStringArray("animator")]
|
||||
anchors_preset = -1
|
||||
anchor_right = 0.923
|
||||
anchor_bottom = 1.037
|
||||
offset_right = -0.296021
|
||||
offset_bottom = 0.0239868
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_xb7je")
|
||||
animator = NodePath("AnimationPlayer")
|
||||
|
||||
[node name="Notice" type="Control" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Background" type="TextureRect" parent="Notice"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = ExtResource("2_lbawe")
|
||||
expand_mode = 1
|
||||
|
||||
[node name="LabelLatestVersion" type="Label" parent="Notice"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = -1
|
||||
anchor_left = 0.148
|
||||
anchor_top = 0.549
|
||||
anchor_right = 0.352
|
||||
anchor_bottom = 0.735
|
||||
offset_left = 0.414978
|
||||
offset_top = 0.115936
|
||||
offset_right = -0.122009
|
||||
offset_bottom = 0.123932
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
theme_override_colors/font_color = Color(0.878431, 0.662745, 0.266667, 1)
|
||||
theme_override_colors/font_shadow_color = Color(0, 0, 0, 1)
|
||||
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||
theme_override_constants/shadow_offset_x = 3
|
||||
theme_override_constants/shadow_offset_y = 3
|
||||
theme_override_constants/outline_size = 18
|
||||
theme_override_constants/shadow_outline_size = 3
|
||||
theme_override_fonts/font = ExtResource("3_a36jf")
|
||||
theme_override_font_sizes/font_size = 133
|
||||
text = "X.Y"
|
||||
|
||||
[node name="LabelCurrentVersion" type="Label" parent="Notice"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = -1
|
||||
anchor_left = 0.76
|
||||
anchor_top = 0.454
|
||||
anchor_right = 0.869
|
||||
anchor_bottom = 0.571
|
||||
offset_left = 0.0449829
|
||||
offset_top = 0.18399
|
||||
offset_right = 57.004
|
||||
offset_bottom = -0.107056
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 2
|
||||
theme_override_colors/font_color = Color(0.784314, 0.619608, 0.317647, 1)
|
||||
theme_override_colors/font_shadow_color = Color(0, 0, 0, 1)
|
||||
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||
theme_override_constants/shadow_offset_x = 3
|
||||
theme_override_constants/shadow_offset_y = 3
|
||||
theme_override_constants/outline_size = 18
|
||||
theme_override_constants/shadow_outline_size = 3
|
||||
theme_override_fonts/font = ExtResource("3_a36jf")
|
||||
theme_override_font_sizes/font_size = 73
|
||||
text = "X.Y"
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Notice"]
|
||||
layout_mode = 1
|
||||
anchors_preset = -1
|
||||
anchor_left = 0.229
|
||||
anchor_top = 0.789
|
||||
anchor_right = 0.771
|
||||
anchor_bottom = 0.926
|
||||
offset_left = 0.0729828
|
||||
offset_top = -0.208008
|
||||
offset_right = -0.0730591
|
||||
offset_bottom = 9.72797
|
||||
grow_horizontal = 2
|
||||
theme_override_constants/separation = -15
|
||||
|
||||
[node name="Label" type="Label" parent="Notice/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
theme_override_colors/font_color = Color(0.872047, 0.774098, 0.671572, 1)
|
||||
theme_override_constants/outline_size = 7
|
||||
theme_override_fonts/font = ExtResource("4_lnh27")
|
||||
theme_override_font_sizes/font_size = 32
|
||||
text = "a new version of Loggie is available for download."
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Notice/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
alignment = 1
|
||||
|
||||
[node name="NoticeButtons" type="HBoxContainer" parent="Notice/VBoxContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 6
|
||||
theme_override_constants/separation = 51
|
||||
|
||||
[node name="ReleaseNotesBtn" type="Button" parent="Notice/VBoxContainer/HBoxContainer/NoticeButtons"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
theme_override_colors/font_disabled_color = Color(0.53, 0.475145, 0.3869, 1)
|
||||
theme_override_colors/font_hover_pressed_color = Color(0.996078, 0.94902, 0.882353, 1)
|
||||
theme_override_colors/font_hover_color = Color(0.98, 0.67571, 0.1862, 1)
|
||||
theme_override_colors/font_pressed_color = Color(0.994326, 0.950716, 0.88154, 1)
|
||||
theme_override_colors/font_focus_color = Color(0.996078, 0.94902, 0.882353, 1)
|
||||
theme_override_colors/font_color = Color(0.980932, 0.843799, 0.621104, 1)
|
||||
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||
theme_override_constants/outline_size = 8
|
||||
theme_override_fonts/font = ExtResource("4_lnh27")
|
||||
theme_override_font_sizes/font_size = 43
|
||||
theme_override_styles/focus = SubResource("StyleBoxFlat_3bh8r")
|
||||
theme_override_styles/disabled_mirrored = SubResource("StyleBoxEmpty_rjg43")
|
||||
theme_override_styles/disabled = SubResource("StyleBoxEmpty_bwgrr")
|
||||
theme_override_styles/hover_pressed_mirrored = SubResource("StyleBoxEmpty_qbrxo")
|
||||
theme_override_styles/hover_pressed = SubResource("StyleBoxEmpty_3asxb")
|
||||
theme_override_styles/hover_mirrored = SubResource("StyleBoxEmpty_86pfv")
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_3bh8r")
|
||||
theme_override_styles/pressed_mirrored = SubResource("StyleBoxEmpty_rw12e")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxEmpty_j1ttd")
|
||||
theme_override_styles/normal_mirrored = SubResource("StyleBoxEmpty_3sc8x")
|
||||
theme_override_styles/normal = SubResource("StyleBoxEmpty_ndum7")
|
||||
text = "release notes"
|
||||
|
||||
[node name="UpdateNowBtn" type="Button" parent="Notice/VBoxContainer/HBoxContainer/NoticeButtons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
theme_override_colors/font_disabled_color = Color(0.53, 0.475145, 0.3869, 1)
|
||||
theme_override_colors/font_hover_pressed_color = Color(0.996078, 0.94902, 0.882353, 1)
|
||||
theme_override_colors/font_hover_color = Color(0.98, 0.67571, 0.1862, 1)
|
||||
theme_override_colors/font_pressed_color = Color(0.994326, 0.950716, 0.88154, 1)
|
||||
theme_override_colors/font_focus_color = Color(0.996078, 0.94902, 0.882353, 1)
|
||||
theme_override_colors/font_color = Color(0.980932, 0.843799, 0.621104, 1)
|
||||
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||
theme_override_constants/outline_size = 8
|
||||
theme_override_fonts/font = ExtResource("4_lnh27")
|
||||
theme_override_font_sizes/font_size = 43
|
||||
theme_override_styles/focus = SubResource("StyleBoxFlat_3bh8r")
|
||||
theme_override_styles/disabled_mirrored = SubResource("StyleBoxEmpty_rjg43")
|
||||
theme_override_styles/disabled = SubResource("StyleBoxEmpty_bwgrr")
|
||||
theme_override_styles/hover_pressed_mirrored = SubResource("StyleBoxEmpty_qbrxo")
|
||||
theme_override_styles/hover_pressed = SubResource("StyleBoxEmpty_3asxb")
|
||||
theme_override_styles/hover_mirrored = SubResource("StyleBoxEmpty_86pfv")
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_3bh8r")
|
||||
theme_override_styles/pressed_mirrored = SubResource("StyleBoxEmpty_rw12e")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxEmpty_j1ttd")
|
||||
theme_override_styles/normal_mirrored = SubResource("StyleBoxEmpty_3sc8x")
|
||||
theme_override_styles/normal = SubResource("StyleBoxEmpty_ndum7")
|
||||
text = "update now"
|
||||
|
||||
[node name="RemindLaterBtn" type="Button" parent="Notice/VBoxContainer/HBoxContainer/NoticeButtons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
theme_override_colors/font_disabled_color = Color(0.53, 0.475145, 0.3869, 1)
|
||||
theme_override_colors/font_hover_pressed_color = Color(0.996078, 0.94902, 0.882353, 1)
|
||||
theme_override_colors/font_hover_color = Color(0.98, 0.67571, 0.1862, 1)
|
||||
theme_override_colors/font_pressed_color = Color(0.994326, 0.950716, 0.88154, 1)
|
||||
theme_override_colors/font_focus_color = Color(0.996078, 0.94902, 0.882353, 1)
|
||||
theme_override_colors/font_color = Color(0.98, 0.49, 0.5635, 1)
|
||||
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||
theme_override_constants/outline_size = 8
|
||||
theme_override_fonts/font = ExtResource("4_lnh27")
|
||||
theme_override_font_sizes/font_size = 43
|
||||
theme_override_styles/focus = SubResource("StyleBoxFlat_3bh8r")
|
||||
theme_override_styles/disabled_mirrored = SubResource("StyleBoxEmpty_rjg43")
|
||||
theme_override_styles/disabled = SubResource("StyleBoxEmpty_bwgrr")
|
||||
theme_override_styles/hover_pressed_mirrored = SubResource("StyleBoxEmpty_qbrxo")
|
||||
theme_override_styles/hover_pressed = SubResource("StyleBoxEmpty_3asxb")
|
||||
theme_override_styles/hover_mirrored = SubResource("StyleBoxEmpty_86pfv")
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_3bh8r")
|
||||
theme_override_styles/pressed_mirrored = SubResource("StyleBoxEmpty_rw12e")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxEmpty_j1ttd")
|
||||
theme_override_styles/normal_mirrored = SubResource("StyleBoxEmpty_3sc8x")
|
||||
theme_override_styles/normal = SubResource("StyleBoxEmpty_ndum7")
|
||||
text = "remind later"
|
||||
|
||||
[node name="DontShowAgainCheckbox" type="CheckBox" parent="Notice"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 1
|
||||
anchor_left = 1.0
|
||||
anchor_right = 1.0
|
||||
offset_left = -289.0
|
||||
offset_top = 9.0
|
||||
offset_right = -20.0
|
||||
offset_bottom = 62.0
|
||||
grow_horizontal = 0
|
||||
mouse_default_cursor_shape = 2
|
||||
theme_override_colors/font_hover_color = Color(0.490196, 0.701961, 0.501961, 1)
|
||||
theme_override_colors/font_focus_color = Color(0.92549, 0.92549, 0.756863, 1)
|
||||
theme_override_colors/font_color = Color(0.980932, 0.843799, 0.621104, 1)
|
||||
theme_override_constants/outline_size = 7
|
||||
theme_override_fonts/font = ExtResource("4_lnh27")
|
||||
theme_override_font_sizes/font_size = 27
|
||||
theme_override_styles/focus = ExtResource("5_avea8")
|
||||
theme_override_icons/checked = ExtResource("6_yoxpw")
|
||||
theme_override_icons/unchecked = ExtResource("7_xu2hn")
|
||||
theme_override_icons/radio_checked = ExtResource("6_yoxpw")
|
||||
theme_override_icons/radio_unchecked = ExtResource("7_xu2hn")
|
||||
theme_override_icons/checked_disabled = ExtResource("8_4h2cx")
|
||||
theme_override_icons/unchecked_disabled = ExtResource("9_51n7f")
|
||||
theme_override_icons/radio_checked_disabled = ExtResource("8_4h2cx")
|
||||
theme_override_icons/radio_unchecked_disabled = ExtResource("9_51n7f")
|
||||
text = "do not show this again"
|
||||
|
||||
[node name="UpdateMonitor" type="Control" parent="."]
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = -673.0
|
||||
offset_bottom = -673.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="BackgroundUnder" type="TextureRect" parent="UpdateMonitor"]
|
||||
modulate = Color(1, 1, 1, 0.811765)
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = SubResource("GradientTexture1D_k1b6u")
|
||||
|
||||
[node name="BackgroundOver" type="TextureRect" parent="UpdateMonitor"]
|
||||
modulate = Color(1, 1, 1, 0.752941)
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = SubResource("GradientTexture2D_p6ad4")
|
||||
|
||||
[node name="ProgressBar" type="ProgressBar" parent="UpdateMonitor"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 7
|
||||
anchor_left = 0.5
|
||||
anchor_top = 1.0
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -448.5
|
||||
offset_top = -224.0
|
||||
offset_right = 449.5
|
||||
offset_bottom = -160.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
theme_override_constants/outline_size = 19
|
||||
theme_override_fonts/font = ExtResource("3_a36jf")
|
||||
theme_override_font_sizes/font_size = 31
|
||||
theme_override_styles/background = SubResource("StyleBoxFlat_y40yc")
|
||||
theme_override_styles/fill = SubResource("StyleBoxFlat_h3ybw")
|
||||
|
||||
[node name="LabelMainStatus" type="Label" parent="UpdateMonitor"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -201.0
|
||||
offset_top = -13.0
|
||||
offset_right = 201.0
|
||||
offset_bottom = 63.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_colors/font_color = Color(0.878431, 0.662745, 0.266667, 1)
|
||||
theme_override_colors/font_shadow_color = Color(0, 0, 0, 1)
|
||||
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||
theme_override_constants/outline_size = 14
|
||||
theme_override_fonts/font = ExtResource("3_a36jf")
|
||||
theme_override_font_sizes/font_size = 81
|
||||
text = "status"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="LabelOldVersion" type="Label" parent="UpdateMonitor"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -426.5
|
||||
offset_top = 77.0
|
||||
offset_right = -24.5
|
||||
offset_bottom = 153.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_colors/font_color = Color(0.878431, 0.662745, 0.266667, 1)
|
||||
theme_override_colors/font_shadow_color = Color(0, 0, 0, 1)
|
||||
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||
theme_override_constants/outline_size = 14
|
||||
theme_override_fonts/font = ExtResource("3_a36jf")
|
||||
theme_override_font_sizes/font_size = 25
|
||||
text = "v_old"
|
||||
|
||||
[node name="LabelNewVersion" type="Label" parent="UpdateMonitor"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = 33.5
|
||||
offset_top = 76.0
|
||||
offset_right = 435.5
|
||||
offset_bottom = 152.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_colors/font_color = Color(0.878431, 0.662745, 0.266667, 1)
|
||||
theme_override_colors/font_shadow_color = Color(0, 0, 0, 1)
|
||||
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||
theme_override_constants/outline_size = 14
|
||||
theme_override_fonts/font = ExtResource("3_a36jf")
|
||||
theme_override_font_sizes/font_size = 25
|
||||
text = "V_NEW"
|
||||
horizontal_alignment = 2
|
||||
|
||||
[node name="LoggieIcon" type="TextureRect" parent="UpdateMonitor"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -79.5
|
||||
offset_top = -213.0
|
||||
offset_right = 80.5
|
||||
offset_bottom = -53.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
pivot_offset = Vector2(80, 84)
|
||||
texture = ExtResource("10_abt8m")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="UpdateMonitor"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 12
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = -140.0
|
||||
offset_bottom = 77.0002
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
theme_override_constants/separation = 5
|
||||
|
||||
[node name="OptionButtons" type="HBoxContainer" parent="UpdateMonitor/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 24
|
||||
alignment = 1
|
||||
|
||||
[node name="OptionRetryUpdateBtn" type="Button" parent="UpdateMonitor/VBoxContainer/OptionButtons"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
theme = ExtResource("11_5uxhl")
|
||||
disabled = true
|
||||
text = "Retry"
|
||||
|
||||
[node name="OptionRestartGodotBtn" type="Button" parent="UpdateMonitor/VBoxContainer/OptionButtons"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
theme = ExtResource("11_5uxhl")
|
||||
disabled = true
|
||||
text = "Reload Godot"
|
||||
|
||||
[node name="OptionExitBtn" type="Button" parent="UpdateMonitor/VBoxContainer/OptionButtons"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
theme = ExtResource("11_5uxhl")
|
||||
theme_override_colors/font_color = Color(0.980919, 0.500031, 0.570874, 1)
|
||||
theme_override_colors/font_outline_color = Color(0.224555, 0.120107, 0.0255992, 1)
|
||||
theme_override_constants/outline_size = 8
|
||||
disabled = true
|
||||
text = "Exit"
|
||||
|
||||
[node name="LabelUpdateStatus" type="Label" parent="UpdateMonitor/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme = ExtResource("11_5uxhl")
|
||||
theme_override_font_sizes/font_size = 20
|
||||
text = "UPDATE_STATUS_DETAILED"
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
root_node = NodePath("../UpdateMonitor")
|
||||
libraries = {
|
||||
"": SubResource("AnimationLibrary_lwiu5")
|
||||
}
|
||||