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
This commit is contained in:
Dan Baker 2025-04-29 17:35:39 +01:00
parent f8140c83ff
commit 02b3be35b0
144 changed files with 7399 additions and 0 deletions

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

View file

@ -0,0 +1 @@
uid://dr553ffcn7yyg

View 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

View file

@ -0,0 +1 @@
uid://bxwv6sw76aqxm

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

View file

@ -0,0 +1 @@
uid://cdu8t2jbqqvb2

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

View file

@ -0,0 +1 @@
uid://dh7dlpputw3bg

View 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")
}