Sensor refactor #1

Merged
ssmith merged 5 commits from sensor-refactor into main 2026-08-02 00:54:20 +00:00
147 changed files with 523 additions and 8763 deletions
Showing only changes of commit 10e9a0f5df - Show all commits
-9
View File
@@ -14,15 +14,6 @@ var WGS84_AB = WGS84_A_M * WGS84_B_M
var g = 9.80665
func calc_time_diff(timestamp_1, timestamp_2):
if timestamp_2 < 3600 and timestamp_1 > 82800:
return (timestamp_2 + 86400) - timestamp_1
return timestamp_2 - timestamp_1
func is_timestamp_newer(old_timestamp: float, new_timestamp: float) -> bool:
if old_timestamp > 82800. and new_timestamp < 3600: # earlier than 23 hours
return true
return old_timestamp < new_timestamp
func calc_wgs84_altitude(latitude_rad: float):
var theta_rad = abs(latitude_rad)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 B

@@ -1,40 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ivppb5cqrsak"
path="res://.godot/imported/NinePatchWindow-InnerRecess.png-07bd09e8d845ac941af5cf12e9f19c54.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/loggie-console/assets/NinePatchWindow-InnerRecess.png"
dest_files=["res://.godot/imported/NinePatchWindow-InnerRecess.png-07bd09e8d845ac941af5cf12e9f19c54.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 B

@@ -1,40 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d1w4w4yi6p3w3"
path="res://.godot/imported/NinePatchWindowInner.png-47f4d96fc18c0a2a90855797a8cd0e05.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/loggie-console/assets/NinePatchWindowInner.png"
dest_files=["res://.godot/imported/NinePatchWindowInner.png-47f4d96fc18c0a2a90855797a8cd0e05.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 B

@@ -1,40 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cs676riq457do"
path="res://.godot/imported/NinePatchWindowOuter.png-bca6314f960490a8a5e8b837d843d94e.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/loggie-console/assets/NinePatchWindowOuter.png"
dest_files=["res://.godot/imported/NinePatchWindowOuter.png-bca6314f960490a8a5e8b837d843d94e.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

@@ -1,40 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bhps6csrl6lef"
path="res://.godot/imported/close.png-9f5b597652eecfc041a6acfb8a79a5f3.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/loggie-console/assets/close.png"
dest_files=["res://.godot/imported/close.png-9f5b597652eecfc041a6acfb8a79a5f3.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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
@@ -1,34 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ckoblpvpivjma"
path="res://.godot/imported/icon.png-b025e725a72f01c7e881efaf61058c1e.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/loggie-console/assets/icon.png"
dest_files=["res://.godot/imported/icon.png-b025e725a72f01c7e881efaf61058c1e.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
@@ -1,17 +0,0 @@
[gd_resource type="StyleBoxTexture" load_steps=2 format=3 uid="uid://b7k8yo7jfhpue"]
[ext_resource type="Texture2D" uid="uid://d1w4w4yi6p3w3" path="res://addons/loggie-console/assets/NinePatchWindowInner.png" id="1_vngi6"]
[resource]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
texture = ExtResource("1_vngi6")
texture_margin_left = 2.0
texture_margin_top = 2.0
texture_margin_right = 2.0
texture_margin_bottom = 2.0
axis_stretch_horizontal = 1
axis_stretch_vertical = 1
region_rect = Rect2(0, 0, 36, 36)
@@ -1,16 +0,0 @@
[gd_resource type="StyleBoxTexture" load_steps=2 format=3 uid="uid://dolgdjpxlvt8f"]
[ext_resource type="Texture2D" uid="uid://ivppb5cqrsak" path="res://addons/loggie-console/assets/NinePatchWindow-InnerRecess.png" id="1_lfme6"]
[resource]
content_margin_left = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
texture = ExtResource("1_lfme6")
texture_margin_left = 2.0
texture_margin_top = 2.0
texture_margin_right = 2.0
texture_margin_bottom = 2.0
axis_stretch_horizontal = 1
axis_stretch_vertical = 1
region_rect = Rect2(0, 0, 36, 36)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

@@ -1,40 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://2ie83nlho48h"
path="res://.godot/imported/minimize.png-3f9dff399b51d5e6e7a64266986b96cf.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/loggie-console/assets/minimize.png"
dest_files=["res://.godot/imported/minimize.png-3f9dff399b51d5e6e7a64266986b96cf.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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
@@ -1,13 +0,0 @@
[gd_resource type="StyleBoxTexture" load_steps=2 format=3 uid="uid://bpkqcbx15ejak"]
[ext_resource type="Texture2D" uid="uid://cs676riq457do" path="res://sprites/ui/NinePatchWindowBrown.png" id="1_4h7p8"]
[resource]
texture = ExtResource("1_4h7p8")
texture_margin_left = 2.0
texture_margin_top = 2.0
texture_margin_right = 2.0
texture_margin_bottom = 2.0
axis_stretch_horizontal = 1
axis_stretch_vertical = 1
region_rect = Rect2(0, 0, 36, 36)
@@ -1,21 +0,0 @@
[gd_resource type="StyleBoxTexture" load_steps=2 format=3 uid="uid://cfafk3ybc15uo"]
[ext_resource type="Texture2D" uid="uid://cs676riq457do" path="res://sprites/ui/NinePatchWindowBrown.png" id="1_8qfda"]
[resource]
content_margin_left = 2.0
content_margin_top = 2.0
content_margin_right = 2.0
content_margin_bottom = 2.0
texture = ExtResource("1_8qfda")
texture_margin_left = 2.0
texture_margin_top = 2.0
texture_margin_right = 2.0
texture_margin_bottom = 2.0
expand_margin_left = 4.0
expand_margin_top = 24.0
expand_margin_right = 4.0
expand_margin_bottom = 4.0
axis_stretch_horizontal = 1
axis_stretch_vertical = 1
region_rect = Rect2(0, 0, 36, 36)
-7
View File
@@ -1,7 +0,0 @@
[plugin]
name="Loggie Console"
description="Interactive debug console built upon the Loggie logging framework"
author="Smitner Studio"
version="1.0"
script="plugin.gd"
-16
View File
@@ -1,16 +0,0 @@
@tool
class_name LoggieConsoleEditorPlugin extends EditorPlugin
const AUTOLOAD_NAME = &"LoggieConsoleAutoload"
func _enter_tree():
add_autoload_singleton(AUTOLOAD_NAME, "res://addons/loggie-console/scenes/console.tscn")
func _exit_tree():
remove_autoload_singleton(AUTOLOAD_NAME)
func _enable_plugin() -> void:
print("%s enabled" % AUTOLOAD_NAME)
func _disable_plugin() -> void:
print("%s disabled" % AUTOLOAD_NAME)
-1
View File
@@ -1 +0,0 @@
uid://b8ul8pbaeqx2l
@@ -1,55 +0,0 @@
@tool
class_name LoggieConsoleFilterState extends Resource
## Persistent filter state resource for LoggieConsole
##
## Stores filtering criteria (domains, log levels, search terms) in a format compatible
## with Godot's Resource system while maintaining compatibility with LogBuffer.FilterState.
## This enables console filter settings to persist between application sessions.
## List of domain names that are currently enabled for display
@export var enabled_domains: Array[String] = []
## Minimum log level to display (stored as int for persistence compatibility)
@export var min_log_level: int = LoggieEnums.LogLevel.DEBUG
## Text search filter - only messages containing this text will be shown
@export var text_search: String = ""
## Whether to show only messages that include stack traces
@export var show_stack_only: bool = false
## Converts this persistent resource to runtime FilterState object
## Returns: A new LogBuffer.FilterState instance with matching filter criteria
func to_filter_state() -> LogBuffer.FilterState:
var filter_state: LogBuffer.FilterState = LogBuffer.FilterState.new()
filter_state.enabled_domains = enabled_domains.duplicate()
filter_state.min_log_level = min_log_level as LoggieEnums.LogLevel
filter_state.text_search = text_search
filter_state.show_stack_only = show_stack_only
return filter_state
## Populates this resource from runtime FilterState object
## Param filter_state: The LogBuffer.FilterState to copy data from
func from_filter_state(filter_state: LogBuffer.FilterState) -> void:
enabled_domains = filter_state.enabled_domains.duplicate()
min_log_level = filter_state.min_log_level as int
text_search = filter_state.text_search
show_stack_only = filter_state.show_stack_only
## Validates and sanitizes all data after loading from disk
## Ensures all fields have valid values even if the saved file was corrupted
func validate() -> void:
# Ensure domain array exists
if enabled_domains == null:
enabled_domains = []
# Validate log level is within valid enum range
if min_log_level < 0 or min_log_level >= LoggieEnums.LogLevel.size():
min_log_level = LoggieEnums.LogLevel.DEBUG
Loggie.msg("Invalid log level, reset to DEBUG").domain(LoggieConsoleConstants.DOMAIN).warn()
# Ensure search text is never null
if text_search == null:
text_search = ""
@@ -1 +0,0 @@
uid://b6iuus8kvu3y3
@@ -1,294 +0,0 @@
@tool
class_name LoggieConsoleSettings extends Resource
## Complete persistent settings for LoggieConsole
##
## This resource stores all console state including window properties, filter settings,
## component states, and discovered domains. It automatically saves when the console
## exits and loads on startup to restore the user's previous console configuration.
const LoggieConsoleFilterState = preload("res://addons/loggie-console/resources/loggie_console_filter_state.gd")
# Configuration constants
const MIN_WINDOW_WIDTH: int = 400
const MIN_WINDOW_HEIGHT: int = 300
const DEFAULT_WINDOW_WIDTH: int = 800
const DEFAULT_WINDOW_HEIGHT: int = 400
const SCREEN_MARGIN_PIXELS: int = 100 # 50px on each side
const WINDOW_EDGE_MARGIN: int = 20
const WINDOW_MIN_MARGIN: int = 10
## Filter state for log message display (domains, levels, search terms)
@export var filter_state: LoggieConsoleFilterState
## Currently enabled domain names for filtering
@export var enabled_domains: Array[String] = []
## All domain names discovered during runtime (persisted for next session)
@export var all_known_domains: Array[String] = []
## Console window screen position (-1, -1 means use default centering)
@export var window_position: Vector2i = Vector2i(-1, -1)
## Console window size in pixels
@export var window_size: Vector2i = Vector2i(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT)
## Whether console is currently minimized
@export var is_minimized: bool = false
## Whether console window is visible
@export var is_visible: bool = true
## Display/screen index where console was last positioned (-1 means primary display)
@export var display_index: int = -1
## Whether subwindows were embedded when settings were last saved
@export var was_embedded_subwindows: bool = false
## Current log level filter (stored as int for persistence)
@export var log_level: int = LoggieEnums.LogLevel.DEBUG
## Whether to auto-scroll to newest messages
@export var scroll_follow_enabled: bool = true
## Current text search filter term
@export var search_text: String = ""
## Whether to show only messages with stack traces
@export var stack_filter_enabled: bool = false
## Maximum number of messages to keep in buffer before cleanup
@export var max_messages: int = 5000
## Text size multiplier for console fonts (0.5 to 2.0, default 1.0)
@export var text_size_multiplier: float = 1.0
## Restore button alignment position (0=TOP_LEFT, 1=TOP_RIGHT, 2=BOTTOM_LEFT, 3=BOTTOM_RIGHT)
@export var restore_button_alignment: int = 1 # Default to TOP_RIGHT
## Settings file format version for future migration compatibility
@export var version: int = 1
## Unix timestamp when settings were last saved
@export var last_saved: float = 0.0
## Initializes default settings values
##
## Creates the filter state resource if it doesn't exist. This ensures that
## all required sub-resources are properly initialized even when creating
## settings from scratch.
func _init() -> void:
if not filter_state:
filter_state = LoggieConsoleFilterState.new()
## Validates and sanitizes all data after loading from disk
## Ensures all fields have valid values even if the saved file was corrupted
func validate() -> void:
# Ensure filter state exists and is valid
if not filter_state:
filter_state = LoggieConsoleFilterState.new()
filter_state.validate()
# Ensure domain arrays exist
if enabled_domains == null:
enabled_domains = []
if all_known_domains == null:
all_known_domains = []
# Validate log level is within enum range
if log_level < 0 or log_level >= LoggieEnums.LogLevel.size():
log_level = LoggieEnums.LogLevel.DEBUG
Loggie.msg("Invalid log level, reset to DEBUG").domain(LoggieConsoleConstants.DOMAIN).warn()
# Enforce minimum and maximum window size for usability
_validate_window_size()
# Ensure search text is never null
if search_text == null:
search_text = ""
# Validate text size multiplier is within reasonable bounds
if text_size_multiplier < 0.5 or text_size_multiplier > 2.0:
text_size_multiplier = 1.0
Loggie.msg("Invalid text size multiplier, reset to 1.0").domain(LoggieConsoleConstants.DOMAIN).warn()
# Validate restore button alignment is within enum range (0-3)
if restore_button_alignment < 0 or restore_button_alignment > 3:
restore_button_alignment = 1 # Default to TOP_RIGHT
Loggie.msg("Invalid restore button alignment, reset to TOP_RIGHT").domain(LoggieConsoleConstants.DOMAIN).warn()
# Update save timestamp
last_saved = Time.get_unix_time_from_system()
## Validates and ensures window size fits within available screen space
##
## Enforces minimum size for usability (400x300) and maximum size based on
## screen dimensions. Checks all connected displays to find the largest
## available space and clamps the window size accordingly.
func _validate_window_size() -> void:
# Get the largest available screen to determine maximum constraints
var max_screen_size: Vector2i = Vector2i.ZERO
var screens: int = DisplayServer.get_screen_count()
for i: int in screens:
var screen_rect: Rect2i = DisplayServer.screen_get_usable_rect(i)
max_screen_size.x = maxi(max_screen_size.x, screen_rect.size.x)
max_screen_size.y = maxi(max_screen_size.y, screen_rect.size.y)
# Fallback if no screens detected
if max_screen_size == Vector2i.ZERO:
max_screen_size = Vector2i(1920, 1080)
# Enforce minimum window size for usability
window_size.x = maxi(window_size.x, MIN_WINDOW_WIDTH)
window_size.y = maxi(window_size.y, MIN_WINDOW_HEIGHT)
# Enforce maximum window size to fit on screen (with margins)
var max_width: int = max_screen_size.x - SCREEN_MARGIN_PIXELS
var max_height: int = max_screen_size.y - SCREEN_MARGIN_PIXELS
if window_size.x > max_width:
window_size.x = max_width
Loggie.msg("Window width too large, clamped to %d" % max_width).domain(LoggieConsoleConstants.DOMAIN).warn()
if window_size.y > max_height:
window_size.y = max_height
Loggie.msg("Window height too large, clamped to %d" % max_height).domain(LoggieConsoleConstants.DOMAIN).warn()
## Detects if embed_subwindows setting has changed since last save
## Returns: true if setting has changed and window positioning needs reset
func _has_embed_setting_changed() -> bool:
var current_embedded = ProjectSettings.get_setting("display/window/subwindows/embed_subwindows", true)
return current_embedded != was_embedded_subwindows
## Updates the stored embed_subwindows setting to current project setting
##
## This should be called whenever settings are saved to track the current
## embed_subwindows state for future change detection.
func update_embed_setting() -> void:
was_embedded_subwindows = ProjectSettings.get_setting("display/window/subwindows/embed_subwindows", true)
## Validates and corrects window position for current display setup
## Returns: A valid window position that will be visible on screen
func validate_window_position() -> Vector2i:
# Check if embed_subwindows setting has changed
var embed_changed = _has_embed_setting_changed()
var current_embedded = ProjectSettings.get_setting("display/window/subwindows/embed_subwindows", true)
# If embed setting changed and window was previously invisible, reset position
if embed_changed and not is_visible:
Loggie.msg("embed_subwindows changed from %s to %s, resetting window position" % [was_embedded_subwindows, current_embedded]).domain(LoggieConsoleConstants.DOMAIN).warn()
update_embed_setting()
# Reset to default centering
var target_display = _get_target_display()
var target_rect: Rect2i = DisplayServer.screen_get_usable_rect(target_display)
window_position = Vector2i(-1, -1) # Mark for centering
display_index = target_display
return _center_window_on_screen(target_rect)
# Update embed setting for future comparisons
update_embed_setting()
# Use default centering if position is not set
if window_position == Vector2i(-1, -1):
var target_display = _get_target_display()
var target_rect: Rect2i = DisplayServer.screen_get_usable_rect(target_display)
return _center_window_on_screen(target_rect)
# If we have a specific display preference, validate against it first
if display_index >= 0 and display_index < DisplayServer.get_screen_count():
var preferred_rect: Rect2i = DisplayServer.screen_get_usable_rect(display_index)
var validated_pos = _clamp_window_to_screen(window_position, preferred_rect)
if validated_pos != window_position:
Loggie.msg("Window position adjusted to fit display %d" % display_index).domain(LoggieConsoleConstants.DOMAIN).warn()
return validated_pos
# Check if window can fit properly on any connected display
var screens: int = DisplayServer.get_screen_count()
var best_display: int = -1
var best_pos: Vector2i = window_position
for i: int in screens:
var screen_rect: Rect2i = DisplayServer.screen_get_usable_rect(i)
var window_rect: Rect2i = Rect2i(window_position, window_size)
# Check if window intersects with this screen
if screen_rect.intersects(window_rect):
best_display = i
best_pos = _clamp_window_to_screen(window_position, screen_rect)
# If the window fits perfectly on this screen, use it
if best_pos == window_position:
display_index = i
return window_position
break
# If we found a suitable display, use the clamped position
if best_display >= 0:
display_index = best_display
if best_pos != window_position:
Loggie.msg("Window position clamped to fit display %d" % best_display).domain(LoggieConsoleConstants.DOMAIN).warn()
return best_pos
# Window is completely off-screen, center it on target display
var target_display = _get_target_display()
var target_rect: Rect2i = DisplayServer.screen_get_usable_rect(target_display)
display_index = target_display
Loggie.msg("Window position off-screen, centering on display %d" % target_display).domain(LoggieConsoleConstants.DOMAIN).warn()
return _center_window_on_screen(target_rect)
## Centers the window on the given screen rectangle with margins
##
## Calculates a centered position that keeps the window fully visible with
## appropriate margins from screen edges.
## @param screen_rect: The target screen's usable rectangle
## @return: Position that centers the window with 20px margins
func _center_window_on_screen(screen_rect: Rect2i) -> Vector2i:
var margin_vector: Vector2i = Vector2i(WINDOW_EDGE_MARGIN, WINDOW_EDGE_MARGIN)
var available_size: Vector2i = screen_rect.size - margin_vector * 2
var centered_offset: Vector2i = (available_size - window_size) / 2
return screen_rect.position + margin_vector + centered_offset
## Clamps window position to ensure it fits entirely within screen bounds
##
## Adjusts the position to keep the entire window visible on the screen,
## respecting minimum margins from edges.
## @param pos: The desired window position
## @param screen_rect: The target screen's rectangle
## @return: Adjusted position that keeps the entire window visible
func _clamp_window_to_screen(pos: Vector2i, screen_rect: Rect2i) -> Vector2i:
var margin: Vector2i = Vector2i(WINDOW_MIN_MARGIN, WINDOW_MIN_MARGIN)
var usable_rect: Rect2i = Rect2i(
screen_rect.position + margin,
screen_rect.size - margin * 2
)
# Ensure window fits within usable area
var max_pos: Vector2i = usable_rect.position + usable_rect.size - window_size
var clamped_pos: Vector2i = Vector2i(
clampi(pos.x, usable_rect.position.x, max_pos.x),
clampi(pos.y, usable_rect.position.y, max_pos.y)
)
return clamped_pos
## Gets the target display index for window positioning
## Returns: Valid display index (0 if saved display no longer exists)
func _get_target_display() -> int:
# Use saved display if it still exists
if display_index >= 0 and display_index < DisplayServer.get_screen_count():
return display_index
# Fall back to primary display (index 0)
return 0
## Gets the log level as a proper enum value
## Returns: The current log level as LoggieEnums.LogLevel
func get_log_level() -> LoggieEnums.LogLevel:
return log_level as LoggieEnums.LogLevel
## Sets the log level from an enum value
## @param level: The new log level to store
func set_log_level(level: LoggieEnums.LogLevel) -> void:
log_level = level as int
@@ -1 +0,0 @@
uid://w2lnu7heu47v
-563
View File
@@ -1,563 +0,0 @@
## Enhanced LoggieConsole with persistence, minimize/restore, and multi-display support
##
## This console window provides an advanced debugging interface for the Loggie logging system.
## Features include:
## - Persistent window positioning and settings across sessions
## - Multi-display awareness for non-embedded subwindows
## - Domain-based filtering with dynamic discovery
## - Minimize/restore functionality with floating restore button
## - Complete component-based architecture for extensibility
##
## The console automatically saves all settings when closed or minimized, and restores
## the exact previous state on next startup, including window position, size, filters,
## and discovered domains.
class_name LoggieConsole extends Window
# Constants
const LoggieConsoleChannel = preload("res://addons/loggie-console/scripts/loggie_console_channel.gd")
const ConsoleSettingsManager = preload("res://addons/loggie-console/scripts/managers/console_settings_manager.gd")
const LoggieConsoleSettings = preload("res://addons/loggie-console/resources/loggie_console_settings.gd")
const RestoreButtonScene := preload("res://addons/loggie-console/scenes/restore_button.tscn")
const DomainColorManager = preload("res://addons/loggie-console/scripts/domain_color_manager.gd")
# UI Constants
const WINDOW_MARGIN_PIXELS: int = 5
# Display detection constants
const DEFAULT_DOMAIN_OFFSET: int = 1 # +1 for default domain in total domain count
# Exported component references - automatically assigned from scene
@export var buffer: LogBuffer ## Log message display and filtering component
@export var output_level: LogLevelFilter ## Log level selection component
@export var domain_filter: PanelDomainSelector ## Domain multi-selection component
@export var controls: ConsoleControls ## Control buttons and settings component
@export var status_display: StatusDisplay ## Status and statistics display component
# Settings UI references
var _settings_button: Button ## Settings button in controls
var _settings_panel: LoggieConsoleSettingsPanel ## Settings panel component
# Minimize/restore system state
var _restore_button_component: RestoreButton ## Restore button component instance
var _is_minimized: bool = false ## Current minimized state
# Custom Loggie channel for receiving log messages
var _console_channel: LoggieConsoleChannel ## Direct channel integration with Loggie
# Component coordination
var _filter_state: LogBuffer.FilterState ## Shared filter state between components
# Persistence system
var _settings_manager: ConsoleSettingsManager ## Handles save/load operations
var _current_settings: LoggieConsoleSettings ## Current settings instance
# Domain color management
var _domain_color_manager: DomainColorManager ## Centralized color management for all components
## Initializes the console window and all its components
##
## This is the main initialization sequence that:
## 1. Loads persistent settings from disk
## 2. Applies window positioning and visibility settings
## 3. Initializes all components with loaded state
## 4. Sets up the custom Loggie channel for message reception
## 5. Connects all necessary signals for runtime operation
##
## The order is critical - settings must be loaded and applied before
## components are initialized to prevent unwanted signal emissions during startup.
func _ready() -> void:
# Initialize persistence system FIRST
_settings_manager = ConsoleSettingsManager.new()
_current_settings = _settings_manager.load_settings()
_restore_button_component = RestoreButtonScene.instantiate()
_restore_button_component.restore_requested.connect(_on_restore_requested)
# Apply window settings before component initialization
_apply_window_settings()
# Connect window signals for persistence
close_requested.connect(_on_close_requested)
# Initialize components with loaded settings
_initialize_components()
# Setup save triggers after initialization
_setup_save_triggers()
# Setup custom channel
_setup_console_channel()
# Apply loaded filters after components are ready
_apply_loaded_settings()
## Called by our custom LoggieConsoleChannel with complete message data
##
## This is the main message reception callback that receives log messages directly
## from the Loggie system through our custom channel. Unlike signal-based approaches,
## this provides complete type information including LogLevel and MsgType enums.
##
## @param msg: The original LoggieMsg with all metadata
## @param preprocessed_content: Fully formatted message text from Loggie
## @param msg_type: Message type enum (ERROR, WARNING, DEBUG, STANDARD)
## @param log_level: Log level enum (ERROR, WARN, NOTICE, INFO, DEBUG)
func receive_log_message(msg: LoggieMsg, preprocessed_content: String, msg_type: LoggieEnums.MsgType, log_level: LoggieEnums.LogLevel) -> void:
# Check if we need to add a new domain to our list
if not msg.domain_name.is_empty():
domain_filter.add_domain(msg.domain_name)
# Add message to buffer (buffer handles filtering internally)
buffer.add_message(msg, preprocessed_content, msg_type, log_level)
## Sets up the custom Loggie channel for direct message reception
##
## Creates and registers a LoggieConsoleChannel that provides the console with
## direct access to log messages including complete type information. The channel
## is automatically added to the default channels list so it receives all messages
## by default, without requiring explicit channel targeting in log calls.
func _setup_console_channel() -> void:
# Create and register our custom channel
_console_channel = LoggieConsoleChannel.new()
_console_channel.set_console_window(self)
Loggie.add_channel(_console_channel)
# Automatically add our channel to the default channels so console receives all messages
var current_defaults: Array = Loggie.settings.default_channels
if not current_defaults.has("loggie_console"):
var new_defaults: Array = current_defaults.duplicate()
new_defaults.append("loggie_console")
Loggie.settings.default_channels = new_defaults
func _setup_domain_filter() -> void:
# Get all domains dynamically from Loggie
var initial_domains: Array[String] = [""] # Always include default domain
# Get all registered domains from Loggie
for domain_name: String in Loggie.domains.keys():
if not initial_domains.has(domain_name):
initial_domains.append(domain_name)
# Add LoggieConsole internal domain (will appear unselected by default)
if not initial_domains.has(LoggieConsoleConstants.DOMAIN):
var all_domains: Array[String] = initial_domains.duplicate()
all_domains.append(LoggieConsoleConstants.DOMAIN)
# Initialize the domain selector with all domains (including console domain)
domain_filter.set_domains_with_console_unselected(all_domains, initial_domains, [LoggieConsoleConstants.DOMAIN])
else:
# Console domain already exists, use standard initialization
domain_filter.set_domains(initial_domains)
domain_filter.domains_changed.connect(_on_domains_changed)
# Apply initial filter state to buffer (only enabled domains, not internal ones)
_filter_state.enabled_domains = initial_domains
buffer.set_filters(_filter_state)
func _on_domains_changed(enabled_domains: Array[String]) -> void:
# Update filter state and apply to buffer
_filter_state.enabled_domains = enabled_domains
buffer.set_filters(_filter_state)
## New component event handlers
func _on_log_level_changed(new_level: LoggieEnums.LogLevel) -> void:
# Update filter state and apply to buffer
_filter_state.min_log_level = new_level
buffer.set_filters(_filter_state)
func _on_clear_requested() -> void:
# Clear the buffer
buffer.clear_all()
func _on_scroll_follow_changed(enabled: bool) -> void:
# Update buffer scroll following
buffer.scroll_following = enabled
func _on_text_search_changed(search_text: String) -> void:
# Update filter state and apply to buffer
_filter_state.text_search = search_text
buffer.set_filters(_filter_state)
func _on_stack_filter_changed(enabled: bool) -> void:
# Update filter state and apply to buffer
_filter_state.show_stack_only = enabled
buffer.set_filters(_filter_state)
func _on_buffer_filter_changed(visible_count: int, total_count: int) -> void:
# Update status display with current counts
var enabled_domains: Array[String] = domain_filter.get_enabled_domains()
var total_domains: int = enabled_domains.size()
if total_domains == 0:
total_domains = Loggie.domains.keys().size() + DEFAULT_DOMAIN_OFFSET
status_display.update_status(total_count, visible_count, enabled_domains, total_domains)
## Minimize/Restore System
## Minimizes the console window and shows a floating restore button
##
## This hides the console window and creates a small floating "LOG" button in the
## top-right corner of the main viewport. The button allows restoring the console
## to its previous position and size. All settings are saved before minimizing
## to ensure state persistence.
func minimize_console() -> void:
if _is_minimized:
Loggie.msg("Already minimized, ignoring request").domain(LoggieConsoleConstants.DOMAIN).warn()
return
# Save current settings including window state before minimizing
_capture_current_state()
_settings_manager.save_settings(_current_settings)
_is_minimized = true
hide() # Hide the console window
_show_restore_button()
## Creates and shows the restore button component
##
## Instantiates the RestoreButton scene and displays it in the root viewport.
## Connects the restore_requested signal to handle button clicks and restore the console.
func _show_restore_button() -> void:
_restore_button_component.show_restore_button(get_tree().root)
Loggie.msg("RestoreButton show_restore_button() called").domain(LoggieConsoleConstants.DOMAIN).debug()
## Handles restore button click events from the RestoreButton component
##
## Called when the RestoreButton component emits its restore_requested signal.
## Triggers the console restoration process.
func _on_restore_requested() -> void:
restore_console()
## Restores the console window from minimized state
##
## This shows the console window at its previously saved position and size,
## removes the floating restore button, and updates the minimized state.
## The window is restored exactly as it was when minimized.
func restore_console() -> void:
if not _is_minimized:
Loggie.msg("Not minimized, ignoring restore request").domain(LoggieConsoleConstants.DOMAIN).warn()
return
# Restore to saved position and size from settings
position = _current_settings.window_position
size = _current_settings.window_size
_is_minimized = false
show() # Show the console window
_hide_restore_button()
## Hides and cleans up the restore button component
##
## Removes the RestoreButton component from the scene tree and frees its resources.
## Ensures proper cleanup when the console is restored from minimized state.
func _hide_restore_button() -> void:
if _restore_button_component and is_instance_valid(_restore_button_component):
_restore_button_component.hide_restore_button()
Loggie.msg("Restore button component cleaned up").domain(LoggieConsoleConstants.DOMAIN).debug()
## Applies saved window properties to the console window
## Validates position to ensure window remains visible on current display setup
func _apply_window_settings() -> void:
# Check if we're using embedded subwindows
var subwindows_embedded: bool = ProjectSettings.get_setting("display/window/subwindows/embed_subwindows", true)
var validated_pos: Vector2i
if subwindows_embedded and _is_outside_viewport():
validated_pos = _center_window()
else:
# Use existing validation for separate windows or valid embedded positions
validated_pos = _current_settings.validate_window_position()
position = validated_pos
# Apply saved window dimensions
size = _current_settings.window_size
# Apply visibility state
visible = _current_settings.is_visible
# Apply minimized state (defer restore button creation)
_is_minimized = _current_settings.is_minimized
if _is_minimized:
hide()
# Defer restore button creation until scene tree is ready
_show_restore_button.call_deferred()
## Checks if console window is outside the main viewport (for embedded subwindows)
func _is_outside_viewport() -> bool:
var viewport_size: Vector2i = Vector2i(get_tree().root.get_visible_rect().size)
var window_rect: Rect2i = Rect2i(_current_settings.window_position, _current_settings.window_size)
var viewport_rect: Rect2i = Rect2i(Vector2i.ZERO, viewport_size)
# Return true if window is not completely within viewport or has invalid position
return _current_settings.window_position == Vector2i(-1, -1) or not viewport_rect.encloses(window_rect)
## Centers the console window in the main viewport
func _center_window() -> Vector2i:
var viewport_size: Vector2i = Vector2i(get_tree().root.get_visible_rect().size)
const MARGIN = 50
var centered_pos: Vector2i = (viewport_size - _current_settings.window_size) / 2
return Vector2i(maxi(MARGIN, centered_pos.x), maxi(MARGIN, centered_pos.y))
## Initializes all console components with loaded settings before connecting signals
## This prevents unwanted signal emissions during the initialization process
func _initialize_components() -> void:
# Initialize base filter state
_filter_state = LogBuffer.FilterState.new()
# Create domain color manager
_domain_color_manager = DomainColorManager.new()
# Pass color manager to components that need it
buffer.set_color_manager(_domain_color_manager)
domain_filter.set_color_manager(_domain_color_manager)
# Load persistent settings into each component
buffer.load_settings_from_resource(_current_settings)
domain_filter.load_settings_from_resource(_current_settings)
controls.load_settings_from_resource(_current_settings)
output_level.load_settings_from_resource(_current_settings)
# Connect component signals for runtime interaction
output_level.level_changed.connect(_on_log_level_changed)
controls.clear_requested.connect(_on_clear_requested)
controls.scroll_follow_changed.connect(_on_scroll_follow_changed)
controls.text_search_changed.connect(_on_text_search_changed)
controls.stack_filter_changed.connect(_on_stack_filter_changed)
# Configure buffer with loaded scroll following setting
buffer.filter_changed.connect(_on_buffer_filter_changed)
buffer.scroll_following = controls.is_scroll_follow_enabled()
# Set up remaining console systems
_setup_domain_filter()
# Initialize settings UI
_setup_settings_ui()
status_display.reset()
## Finalizes the loading process by applying loaded filter settings
## Called after component initialization to ensure filters work with existing messages
func _apply_loaded_settings() -> void:
# Apply the loaded filter state to any existing messages
buffer.apply_loaded_filters()
# Sync domain filter state to ensure consistency
domain_filter.domains_changed.emit(domain_filter.get_enabled_domains())
# Apply text size setting to theme
_apply_text_size_to_theme(_current_settings.text_size_multiplier)
# Apply restore button alignment
_apply_restore_button_alignment(_current_settings.restore_button_alignment)
## Placeholder for save triggers (not used in exit-tree persistence model)
func _setup_save_triggers() -> void:
pass # Settings saved only on exit tree
## Captures current state from window and all components into the settings resource
## This is called before each save operation to ensure all current state is preserved
func _capture_current_state() -> void:
# Capture window properties
_current_settings.window_position = position
_current_settings.window_size = size
_current_settings.is_visible = visible
_current_settings.is_minimized = _is_minimized
# Capture current display index (important when embed_subwindows=false)
_current_settings.display_index = _get_current_display_index()
# Capture current embed_subwindows setting for change detection
_current_settings.update_embed_setting()
# Let each component save its own state
buffer.save_settings_to_resource(_current_settings)
domain_filter.save_settings_to_resource(_current_settings)
controls.save_settings_to_resource(_current_settings)
output_level.save_settings_to_resource(_current_settings)
## Manually saves current console settings to disk
## Useful for forcing a save before potentially destructive operations
func save_settings() -> void:
_capture_current_state()
_settings_manager.save_settings(_current_settings)
## Handles window close requests by saving settings first, then minimizing
## This ensures settings are preserved even if the console is closed unexpectedly
func _on_close_requested() -> void:
# Preserve settings before minimizing
_capture_current_state()
_settings_manager.save_settings(_current_settings)
# Continue with normal minimize behavior
minimize_console()
## Automatically saves settings when console exits the scene tree
## This is the primary save mechanism ensuring settings persist between sessions
func _exit_tree() -> void:
if _settings_manager and _current_settings:
_capture_current_state()
_settings_manager.save_settings(_current_settings)
## Handles system-level window destruction notifications
## Provides additional safety net for settings preservation
func _notification(what: int) -> void:
if what == NOTIFICATION_WM_CLOSE_REQUEST:
if _settings_manager and _current_settings:
_capture_current_state()
_settings_manager.save_settings(_current_settings)
## Determines which display the console window is currently on
## Returns: Display index (0-based) where the window center is located
func _get_current_display_index() -> int:
# Calculate window center point
var window_center: Vector2i = position + size / 2
# Check which display contains the window center
var screens: int = DisplayServer.get_screen_count()
for i: int in screens:
var screen_rect: Rect2i = DisplayServer.screen_get_usable_rect(i)
if screen_rect.has_point(window_center):
return i
# Fallback: find display with most window overlap
var max_overlap: int = 0
var best_display: int = 0
var window_rect: Rect2i = Rect2i(position, size)
for i: int in screens:
var screen_rect: Rect2i = DisplayServer.screen_get_usable_rect(i)
var intersection: Rect2i = screen_rect.intersection(window_rect)
var overlap_area: int = intersection.size.x * intersection.size.y
if overlap_area > max_overlap:
max_overlap = overlap_area
best_display = i
return best_display
## Sets up the settings UI components and connects their signals
##
## Initializes the settings button and panel component, loads current settings,
## and connects the component's signals for handling settings changes.
func _setup_settings_ui() -> void:
# Get UI component references
_settings_button = get_node("PanelContainer/VBoxContainer/Controls/Settings")
_settings_panel = get_node("SettingsPanel")
# Connect signals
_settings_button.pressed.connect(_on_settings_button_pressed)
_settings_panel.settings_applied.connect(_on_settings_applied)
_settings_panel.settings_reset.connect(_on_settings_reset)
# Load current settings into the panel
_settings_panel.set_text_size_multiplier(_current_settings.text_size_multiplier)
_settings_panel.set_restore_button_alignment(_current_settings.restore_button_alignment)
## Called when the Settings button is pressed
##
## Shows the settings panel dialog with current values loaded.
func _on_settings_button_pressed() -> void:
_settings_panel.show_settings()
## Called when settings are applied via the settings panel
##
## Saves the new settings and applies them dynamically.
## @param text_size_multiplier: The new text size multiplier value
## @param restore_button_alignment: The new restore button alignment
func _on_settings_applied(text_size_multiplier: float, restore_button_alignment: RestoreButton.Alignment) -> void:
# Update settings
_current_settings.text_size_multiplier = text_size_multiplier
_current_settings.restore_button_alignment = restore_button_alignment as int
_settings_manager.save_settings(_current_settings)
# Apply text size change to theme
_apply_text_size_to_theme(text_size_multiplier)
# Apply restore button alignment
_apply_restore_button_alignment(restore_button_alignment as int)
Loggie.msg("Settings updated: text size %.1fx, alignment %s" % [text_size_multiplier, _get_alignment_name(restore_button_alignment)]).domain(LoggieConsoleConstants.DOMAIN).info()
## Called when the settings are reset to defaults
##
## Logs the reset action for user feedback.
func _on_settings_reset() -> void:
Loggie.msg("Settings reset to defaults").domain(LoggieConsoleConstants.DOMAIN).info()
## Dynamically applies text size multiplier to the current theme
##
## Modifies font sizes in the theme resource at runtime to immediately
## reflect the new text size setting without requiring a restart.
## @param multiplier: Text size multiplier (0.5 to 2.0)
func _apply_text_size_to_theme(multiplier: float) -> void:
var console_theme: Theme = get_theme()
if not console_theme:
Loggie.msg("No theme found, cannot apply text size").domain(LoggieConsoleConstants.DOMAIN).error()
return
# Define base font sizes (from theme_compact.tres)
var base_font_sizes: Dictionary = {
"Button/font_size": 11,
"CheckBox/font_size": 11,
"LineEdit/font_size": 12,
"MenuButton/font_size": 11,
"OptionButton/font_size": 11,
"PopupMenu/font_size": 11,
"RichTextLabel/bold_font_size": 10,
"RichTextLabel/bold_italics_font_size": 10,
"RichTextLabel/italics_font_size": 10,
"RichTextLabel/mono_font_size": 9,
"RichTextLabel/normal_font_size": 10,
"Window/title_font_size": 16
}
# Apply multiplier to each font size
for font_path: String in base_font_sizes:
var parts: PackedStringArray = font_path.split("/")
var control_type: String = parts[0]
var property: String = parts[1]
var base_size: int = base_font_sizes[font_path]
var new_size: int = int(base_size * multiplier)
console_theme.set_font_size(property, control_type, new_size)
# Theme changes are applied automatically
## Applies the restore button alignment setting
##
## Updates the restore button position based on the alignment preference.
## @param alignment: Alignment enum value (0=TOP_LEFT, 1=TOP_RIGHT, 2=BOTTOM_LEFT, 3=BOTTOM_RIGHT)
func _apply_restore_button_alignment(alignment: int) -> void:
if not _restore_button_component:
return
# Set position based on alignment using the RestoreButton's enum
var restore_alignment: RestoreButton.Alignment = alignment as RestoreButton.Alignment
_restore_button_component.set_alignment(restore_alignment)
## Gets a human-readable name for the alignment enum
##
## @param alignment: The alignment enum value
## @return: String name of the alignment
func _get_alignment_name(alignment: RestoreButton.Alignment) -> String:
match alignment:
RestoreButton.Alignment.TOP_LEFT:
return "Top Left"
RestoreButton.Alignment.TOP_RIGHT:
return "Top Right"
RestoreButton.Alignment.BOTTOM_LEFT:
return "Bottom Left"
RestoreButton.Alignment.BOTTOM_RIGHT:
return "Bottom Right"
_:
return "Unknown"
@@ -1 +0,0 @@
uid://64r2in67soep
-164
View File
@@ -1,164 +0,0 @@
[gd_scene format=3 uid="uid://d0sxs0is80byx"]
[ext_resource type="Script" uid="uid://64r2in67soep" path="res://addons/loggie-console/scenes/console.gd" id="1_yp485"]
[ext_resource type="PackedScene" uid="uid://dgok5l3frx53m" path="res://addons/loggie-console/scenes/panel_domain_selector.tscn" id="2_domain"]
[ext_resource type="StyleBox" uid="uid://b7k8yo7jfhpue" path="res://addons/loggie-console/assets/inner_panel.tres" id="3_fxnpl"]
[ext_resource type="Script" uid="uid://wah8efffydnu" path="res://addons/loggie-console/scripts/components/log_buffer.gd" id="3_logbuffer"]
[ext_resource type="Theme" uid="uid://c1lnnysnspc6v" path="res://addons/loggie-console/theme_compact.tres" id="4_2cgp1"]
[ext_resource type="Script" uid="uid://d3l2tmf5gym1d" path="res://addons/loggie-console/scripts/components/log_level_filter.gd" id="4_loglevel"]
[ext_resource type="Script" uid="uid://c4f6c3gs8venj" path="res://addons/loggie-console/scripts/components/console_controls.gd" id="5_controls"]
[ext_resource type="Script" uid="uid://whrim6lbiako" path="res://addons/loggie-console/scripts/components/status_display.gd" id="6_status"]
[ext_resource type="Texture2D" uid="uid://ivppb5cqrsak" path="res://addons/loggie-console/assets/NinePatchWindow-InnerRecess.png" id="7_56hvo"]
[ext_resource type="PackedScene" uid="uid://bw8k3yap2n4qv" path="res://addons/loggie-console/scenes/settings_panel.tscn" id="7_settings_panel"]
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_20rq3"]
content_margin_left = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
texture = ExtResource("7_56hvo")
texture_margin_left = 2.0
texture_margin_top = 2.0
texture_margin_right = 2.0
texture_margin_bottom = 2.0
axis_stretch_horizontal = 1
axis_stretch_vertical = 1
region_rect = Rect2(0, 0, 36, 36)
modulate_color = Color(0.60933, 0.60933, 0.60933, 1)
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_njst7"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ibivl"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_0yrjc"]
[node name="LoggieConsoleWindow" type="Window" unique_id=1024795111 node_paths=PackedStringArray("buffer", "output_level", "domain_filter", "controls", "status_display")]
oversampling_override = 1.0
title = "Loggie Console"
initial_position = 1
size = Vector2i(889, 905)
wrap_controls = true
sharp_corners = true
min_size = Vector2i(600, 300)
keep_title_visible = true
theme = ExtResource("4_2cgp1")
script = ExtResource("1_yp485")
buffer = NodePath("PanelContainer/VBoxContainer/PanelContainer/Buffer")
output_level = NodePath("PanelContainer/VBoxContainer/Controls/OutputLevel")
domain_filter = NodePath("PanelContainer/VBoxContainer/Controls/DomainFilter")
controls = NodePath("PanelContainer/VBoxContainer/Controls")
status_display = NodePath("PanelContainer/VBoxContainer/StatusContainer/HBoxContainer/StatusLabel")
[node name="PanelContainer" type="PanelContainer" parent="." unique_id=1949067581]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/panel = ExtResource("3_fxnpl")
[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer" unique_id=1611268808]
layout_mode = 2
[node name="Controls" type="HBoxContainer" parent="PanelContainer/VBoxContainer" unique_id=251197975 node_paths=PackedStringArray("clear_button", "scroll_follow_checkbox", "search_input", "stack_filter_checkbox")]
layout_mode = 2
script = ExtResource("5_controls")
clear_button = NodePath("Clear")
scroll_follow_checkbox = NodePath("ScrollFollow")
search_input = NodePath("../StatusContainer/HBoxContainer/SearchInput")
stack_filter_checkbox = NodePath("StackFilter")
[node name="OutputLevel" type="OptionButton" parent="PanelContainer/VBoxContainer/Controls" unique_id=751332603]
custom_minimum_size = Vector2(60, 20)
layout_mode = 2
size_flags_horizontal = 0
tooltip_text = "Filter by log level"
selected = 3
item_count = 5
popup/item_0/text = "ERROR"
popup/item_0/id = 0
popup/item_1/text = "WARN"
popup/item_1/id = 1
popup/item_2/text = "NOTICE"
popup/item_2/id = 2
popup/item_3/text = "INFO"
popup/item_3/id = 3
popup/item_4/text = "DEBUG"
popup/item_4/id = 4
script = ExtResource("4_loglevel")
[node name="ScrollFollow" type="CheckBox" parent="PanelContainer/VBoxContainer/Controls" unique_id=569696074]
custom_minimum_size = Vector2(50, 20)
layout_mode = 2
size_flags_horizontal = 0
tooltip_text = "Auto-scroll to newest messages"
button_pressed = true
text = "Follow"
[node name="StackFilter" type="CheckBox" parent="PanelContainer/VBoxContainer/Controls" unique_id=719019705]
custom_minimum_size = Vector2(80, 20)
layout_mode = 2
size_flags_horizontal = 0
tooltip_text = "Show only messages with stack traces"
text = "Stack Only"
[node name="Clear" type="Button" parent="PanelContainer/VBoxContainer/Controls" unique_id=1133245115]
custom_minimum_size = Vector2(45, 20)
layout_mode = 2
size_flags_horizontal = 0
tooltip_text = "Clear console buffer"
text = "Clear"
[node name="Settings" type="Button" parent="PanelContainer/VBoxContainer/Controls" unique_id=671578664]
custom_minimum_size = Vector2(60, 20)
layout_mode = 2
size_flags_horizontal = 0
tooltip_text = "Console settings"
text = "Settings"
[node name="DomainFilter" parent="PanelContainer/VBoxContainer/Controls" unique_id=582666238 instance=ExtResource("2_domain")]
custom_minimum_size = Vector2(300, 20)
layout_mode = 2
size_flags_horizontal = 10
[node name="PanelContainer" type="PanelContainer" parent="PanelContainer/VBoxContainer" unique_id=2068430600]
layout_mode = 2
size_flags_vertical = 3
theme_override_styles/panel = SubResource("StyleBoxTexture_20rq3")
[node name="Buffer" type="RichTextLabel" parent="PanelContainer/VBoxContainer/PanelContainer" unique_id=1261116881]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
focus_mode = 2
theme_override_styles/normal = SubResource("StyleBoxEmpty_njst7")
theme_override_styles/focus = SubResource("StyleBoxEmpty_ibivl")
bbcode_enabled = true
scroll_following = true
context_menu_enabled = true
selection_enabled = true
script = ExtResource("3_logbuffer")
[node name="StatusContainer" type="PanelContainer" parent="PanelContainer/VBoxContainer" unique_id=592277225]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxEmpty_0yrjc")
[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/VBoxContainer/StatusContainer" unique_id=958031751]
layout_mode = 2
[node name="SearchInput" type="LineEdit" parent="PanelContainer/VBoxContainer/StatusContainer/HBoxContainer" unique_id=526298685]
custom_minimum_size = Vector2(200, 20)
layout_mode = 2
size_flags_horizontal = 2
placeholder_text = "Filter messages..."
clear_button_enabled = true
[node name="StatusLabel" type="Label" parent="PanelContainer/VBoxContainer/StatusContainer/HBoxContainer" unique_id=848734114]
layout_mode = 2
size_flags_horizontal = 8
theme_override_font_sizes/font_size = 12
text = "Ready"
horizontal_alignment = 2
vertical_alignment = 1
script = ExtResource("6_status")
[node name="SettingsPanel" parent="." unique_id=1146821245 instance=ExtResource("7_settings_panel")]
@@ -1,55 +0,0 @@
[gd_scene load_steps=3 format=3 uid="uid://cjy8b6h1xrq2x"]
[ext_resource type="Script" uid="uid://dgkxvw0v5n6hq" path="res://addons/loggie-console/scripts/components/domain_item.gd" id="1_domain_item"]
[ext_resource type="Theme" uid="uid://c1lnnysnspc6v" path="res://addons/loggie-console/theme_compact.tres" id="2_theme"]
[node name="DomainItem" type="Control" node_paths=PackedStringArray("select_checkbox", "only_button", "color_indicator")]
custom_minimum_size = Vector2(280, 32)
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme = ExtResource("2_theme")
script = ExtResource("1_domain_item")
select_checkbox = NodePath("HBoxContainer/SelectCheckBox")
only_button = NodePath("OnlyButton")
color_indicator = NodePath("HBoxContainer/ColorIndicator")
[node name="HBoxContainer" type="HBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="ColorIndicator" type="ColorRect" parent="HBoxContainer"]
custom_minimum_size = Vector2(12, 12)
layout_mode = 2
[node name="SelectCheckBox" type="CheckBox" parent="HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
tooltip_text = "Select/deselect this domain"
theme_override_constants/h_separation = 20
text_overrun_behavior = 3
[node name="OnlyButton" type="Button" parent="."]
modulate = Color(1, 1, 1, 0)
custom_minimum_size = Vector2(45, 24)
layout_mode = 1
anchors_preset = 6
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -45.0
offset_top = -12.0
offset_bottom = 12.0
grow_horizontal = 0
grow_vertical = 2
tooltip_text = "Select only this domain"
mouse_filter = 2
text = "Only"
@@ -1,86 +0,0 @@
[gd_scene load_steps=4 format=3 uid="uid://dgok5l3frx53m"]
[ext_resource type="Script" uid="uid://bfmpjs4x3fhte" path="res://addons/loggie-console/scripts/components/panel_domain_selector.gd" id="1_panel_selector"]
[ext_resource type="Theme" uid="uid://c1lnnysnspc6v" path="res://addons/loggie-console/theme_compact.tres" id="2_theme"]
[ext_resource type="StyleBox" uid="uid://b7k8yo7jfhpue" path="res://addons/loggie-console/assets/inner_panel.tres" id="3_panel_style"]
[node name="PanelDomainSelector" type="Control" node_paths=PackedStringArray("trigger_button", "domain_panel", "select_all_button", "select_none_button", "scroll_container", "domain_list")]
custom_minimum_size = Vector2(100, 20)
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme = ExtResource("2_theme")
script = ExtResource("1_panel_selector")
trigger_button = NodePath("TriggerButton")
domain_panel = NodePath("DomainPanel")
select_all_button = NodePath("DomainPanel/PanelContainer/VBoxContainer/SelectionControls/SelectAllButton")
select_none_button = NodePath("DomainPanel/PanelContainer/VBoxContainer/SelectionControls/SelectNoneButton")
scroll_container = NodePath("DomainPanel/PanelContainer/VBoxContainer/ScrollContainer")
domain_list = NodePath("DomainPanel/PanelContainer/VBoxContainer/ScrollContainer/DomainList")
[node name="TriggerButton" type="Button" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
tooltip_text = "All domains selected (1)"
text = "Domains: All"
text_overrun_behavior = 3
[node name="DomainPanel" type="PopupPanel" parent="."]
initial_position = 1
size = Vector2i(300, 160)
visible = true
min_size = Vector2i(300, 150)
[node name="PanelContainer" type="PanelContainer" parent="DomainPanel"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 2.0
offset_top = 2.0
offset_right = -2.0
offset_bottom = -2.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/panel = ExtResource("3_panel_style")
[node name="VBoxContainer" type="VBoxContainer" parent="DomainPanel/PanelContainer"]
layout_mode = 2
[node name="SelectionControls" type="HBoxContainer" parent="DomainPanel/PanelContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 0
alignment = 2
[node name="SelectAllButton" type="Button" parent="DomainPanel/PanelContainer/VBoxContainer/SelectionControls"]
custom_minimum_size = Vector2(60, 20)
layout_mode = 2
size_flags_horizontal = 0
tooltip_text = "Select all domains"
text = "Select All"
[node name="SelectNoneButton" type="Button" parent="DomainPanel/PanelContainer/VBoxContainer/SelectionControls"]
custom_minimum_size = Vector2(60, 20)
layout_mode = 2
size_flags_horizontal = 0
tooltip_text = "Deselect all domains"
text = "None"
[node name="HSeparator" type="HSeparator" parent="DomainPanel/PanelContainer/VBoxContainer"]
layout_mode = 2
[node name="ScrollContainer" type="ScrollContainer" parent="DomainPanel/PanelContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="DomainList" type="VBoxContainer" parent="DomainPanel/PanelContainer/VBoxContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
@@ -1,29 +0,0 @@
[gd_scene load_steps=3 format=3 uid="uid://cb5mkvrnm3hxn"]
[ext_resource type="Script" uid="uid://dmyd1gquxy42u" path="res://addons/loggie-console/scripts/components/restore_button.gd" id="1_2h8k5"]
[ext_resource type="Theme" uid="uid://c1lnnysnspc6v" path="res://addons/loggie-console/theme_compact.tres" id="1_o01pp"]
[node name="LoggieConsoleOverlay" type="CanvasLayer" node_paths=PackedStringArray("restore_button")]
layer = 2147483647
script = ExtResource("1_2h8k5")
restore_button = NodePath("MarginContainer/Button")
[node name="MarginContainer" type="MarginContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
theme_override_constants/margin_left = 5
theme_override_constants/margin_top = 5
theme_override_constants/margin_right = 5
theme_override_constants/margin_bottom = 5
[node name="Button" type="Button" parent="MarginContainer"]
layout_mode = 2
size_flags_horizontal = 8
size_flags_vertical = 0
tooltip_text = "Show Loggie Console"
theme = ExtResource("1_o01pp")
text = " Loggie Console "
@@ -1,90 +0,0 @@
[gd_scene load_steps=4 format=3 uid="uid://bw8k3yap2n4qv"]
[ext_resource type="Theme" uid="uid://c1lnnysnspc6v" path="res://addons/loggie-console/theme_compact.tres" id="1_08jdj"]
[ext_resource type="Script" uid="uid://giuyu6vhhgam" path="res://addons/loggie-console/scripts/components/settings_panel.gd" id="1_settings_panel"]
[ext_resource type="StyleBox" uid="uid://b7k8yo7jfhpue" path="res://addons/loggie-console/assets/inner_panel.tres" id="2_08jdj"]
[node name="SettingsPanel" type="AcceptDialog" node_paths=PackedStringArray("_text_size_slider", "_text_size_value_label", "_reset_button", "_alignment_option")]
title = "Console Settings"
initial_position = 1
size = Vector2i(320, 210)
transient = false
sharp_corners = true
theme = ExtResource("1_08jdj")
theme_override_styles/panel = ExtResource("2_08jdj")
ok_button_text = " Apply Changes "
script = ExtResource("1_settings_panel")
_text_size_slider = NodePath("VBoxContainer/TextSizeContainer/TextSizeSlider")
_text_size_value_label = NodePath("VBoxContainer/TextSizeContainer/TextSizeValueLabel")
_reset_button = NodePath("VBoxContainer/ResetContainer/ResetButton")
_alignment_option = NodePath("VBoxContainer/AlignmentContainer/AlignmentOption")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 8.0
offset_top = 8.0
offset_right = -8.0
offset_bottom = -38.0
grow_horizontal = 2
grow_vertical = 2
[node name="TextSizeContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="TextSizeLabel" type="Label" parent="VBoxContainer/TextSizeContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Text Size:"
vertical_alignment = 1
[node name="TextSizeSlider" type="HSlider" parent="VBoxContainer/TextSizeContainer"]
layout_mode = 2
size_flags_horizontal = 3
min_value = 0.5
max_value = 2.0
step = 0.1
value = 1.0
tick_count = 16
ticks_on_borders = true
[node name="TextSizeValueLabel" type="Label" parent="VBoxContainer/TextSizeContainer"]
custom_minimum_size = Vector2(40, 0)
layout_mode = 2
size_flags_horizontal = 0
text = "1.0x"
horizontal_alignment = 2
vertical_alignment = 1
[node name="AlignmentContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="AlignmentLabel" type="Label" parent="VBoxContainer/AlignmentContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Restore Button:"
vertical_alignment = 1
[node name="AlignmentOption" type="OptionButton" parent="VBoxContainer/AlignmentContainer"]
layout_mode = 2
size_flags_horizontal = 3
selected = 1
item_count = 4
popup/item_0/text = "Top Left"
popup/item_0/id = 0
popup/item_1/text = "Top Right"
popup/item_1/id = 1
popup/item_2/text = "Bottom Left"
popup/item_2/id = 2
popup/item_3/text = "Bottom Right"
popup/item_3/id = 3
[node name="ResetContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
alignment = 1
[node name="ResetButton" type="Button" parent="VBoxContainer/ResetContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Reset to Default"
@@ -1,104 +0,0 @@
class_name ConsoleControls extends HBoxContainer
## Console controls toolbar component
## Manages clear button, scroll follow, and other control elements
const LoggieConsoleSettings = preload("res://addons/loggie-console/resources/loggie_console_settings.gd")
# Signals
signal clear_requested()
signal scroll_follow_changed(enabled: bool)
signal text_search_changed(search_text: String)
signal stack_filter_changed(enabled: bool)
# Exported references to child controls
@export var clear_button: Button
@export var scroll_follow_checkbox: CheckBox
@export var search_input: LineEdit
@export var stack_filter_checkbox: CheckBox
func _ready() -> void:
_setup_controls()
## Initialize the controls with default states
func initialize() -> void:
if scroll_follow_checkbox:
scroll_follow_checkbox.button_pressed = true
_on_scroll_follow_toggled(true)
## Get current scroll follow state
func is_scroll_follow_enabled() -> bool:
return scroll_follow_checkbox.button_pressed if scroll_follow_checkbox else false
## Set scroll follow state programmatically
func set_scroll_follow(enabled: bool) -> void:
if scroll_follow_checkbox:
scroll_follow_checkbox.button_pressed = enabled
## Get current text search
func get_search_text() -> String:
return search_input.text if search_input else ""
## Set search text programmatically
func set_search_text(text: String) -> void:
if search_input:
search_input.text = text
## Get current stack filter state
func is_stack_filter_enabled() -> bool:
return stack_filter_checkbox.button_pressed if stack_filter_checkbox else false
## Set stack filter state programmatically
func set_stack_filter(enabled: bool) -> void:
if stack_filter_checkbox:
stack_filter_checkbox.button_pressed = enabled
## Private methods
func _setup_controls() -> void:
# Connect clear button if available
if clear_button:
clear_button.pressed.connect(_on_clear_pressed)
clear_button.tooltip_text = "Clear console buffer"
# Connect scroll follow checkbox if available
if scroll_follow_checkbox:
scroll_follow_checkbox.toggled.connect(_on_scroll_follow_toggled)
scroll_follow_checkbox.tooltip_text = "Auto-scroll to newest messages"
# Connect search input if available
if search_input:
search_input.text_changed.connect(_on_search_text_changed)
search_input.placeholder_text = "Filter messages..."
# Connect stack filter checkbox if available
if stack_filter_checkbox:
stack_filter_checkbox.toggled.connect(_on_stack_filter_toggled)
stack_filter_checkbox.tooltip_text = "Show only messages with stack traces"
func _on_clear_pressed() -> void:
clear_requested.emit()
func _on_scroll_follow_toggled(enabled: bool) -> void:
scroll_follow_changed.emit(enabled)
func _on_search_text_changed(new_text: String) -> void:
text_search_changed.emit(new_text)
func _on_stack_filter_toggled(enabled: bool) -> void:
stack_filter_changed.emit(enabled)
## Saves current toolbar control states to persistent settings
## Param settings: The settings resource to populate with current control states
func save_settings_to_resource(settings: LoggieConsoleSettings) -> void:
settings.scroll_follow_enabled = is_scroll_follow_enabled()
settings.search_text = get_search_text()
settings.stack_filter_enabled = is_stack_filter_enabled()
## Loads toolbar control states from persistent settings and applies them
## Updates all UI controls to reflect the loaded state without emitting signals
## Param settings: The settings resource containing saved control states
func load_settings_from_resource(settings: LoggieConsoleSettings) -> void:
set_scroll_follow(settings.scroll_follow_enabled)
set_search_text(settings.search_text)
set_stack_filter(settings.stack_filter_enabled)
@@ -1 +0,0 @@
uid://c4f6c3gs8venj
@@ -1,166 +0,0 @@
class_name DomainItem extends Control
## Custom control for individual domain rows in PanelDomainSelector
## Provides checkbox selection, domain label, and hover-revealed "Only" button
const DomainColorManager = preload("res://addons/loggie-console/scripts/domain_color_manager.gd")
# Signals
signal selection_changed(domain_name: String, is_selected: bool)
signal only_button_pressed(domain_name: String)
# UI Components - exported for scene tree assignment
@export var select_checkbox: CheckBox
@export var only_button: Button
@export var color_indicator: ColorRect
# Domain state
var _domain_name: String = ""
var _is_hovered: bool = false
var _color_manager: DomainColorManager
# UI Layout constants
const ITEM_MIN_WIDTH: int = 280
const ITEM_MIN_HEIGHT: int = 32
# Visibility constants for only button hover effect
const ONLY_BUTTON_ALPHA_HIDDEN: float = 0.0
const ONLY_BUTTON_ALPHA_VISIBLE: float = 1.0
## Initializes the domain item control with UI connections and hover behavior
##
## Sets up consistent sizing, signal connections for checkbox and button interactions,
## and configures mouse hover detection for the "Only" button visibility toggle.
## The only button starts hidden and becomes visible on hover with smooth alpha transition.
## Requires select_checkbox and only_button to be properly assigned via @export.
func _ready() -> void:
if not select_checkbox:
return
# Set minimum size for consistent layout
custom_minimum_size = Vector2(ITEM_MIN_WIDTH, ITEM_MIN_HEIGHT)
# Connect signals
select_checkbox.toggled.connect(_on_checkbox_toggled)
only_button.pressed.connect(_on_only_button_pressed)
# Setup hover detection on checkbox (which now contains the text)
select_checkbox.mouse_entered.connect(_on_mouse_entered)
select_checkbox.mouse_exited.connect(_on_mouse_exited)
# Also setup hover detection on the main control for complete coverage
mouse_entered.connect(_on_mouse_entered)
mouse_exited.connect(_on_mouse_exited)
# Initialize only button as hidden
only_button.modulate.a = ONLY_BUTTON_ALPHA_HIDDEN
only_button.mouse_filter = Control.MOUSE_FILTER_IGNORE
## Set the domain color manager instance
func set_color_manager(color_manager: DomainColorManager) -> void:
_color_manager = color_manager
## Configures the domain item with a specific domain name and selection state
##
## Sets up the checkbox display text (shows "(default)" for empty domain names),
## configures tooltips for both checkbox and only button, and applies the initial
## selection state. This method should be called after the item is added to the scene tree.
##
## @param domain_name: The domain name to display and track
## @param is_selected: Whether the domain should start as selected
func setup_domain(domain_name: String, is_selected: bool = false) -> void:
_domain_name = domain_name
if select_checkbox:
var display_name = domain_name if not domain_name.is_empty() else "(default)"
# Update color indicator instead of modulating checkbox
if _color_manager and color_indicator:
var domain_color = _color_manager.get_domain_color(domain_name)
color_indicator.color = domain_color
select_checkbox.text = display_name
select_checkbox.tooltip_text = "Select/deselect domain: " + domain_name
select_checkbox.button_pressed = is_selected
if only_button:
only_button.text = "Only"
only_button.tooltip_text = "Select only this domain: " + domain_name
## Get the domain name for this item
func get_domain_name() -> String:
return _domain_name
## Updates the checkbox selection state without emitting signals
##
## Useful for programmatically setting the selection state during bulk operations
## like "Select All" or when loading from saved settings. Uses set_pressed_no_signal
## to avoid triggering selection_changed signals during these operations.
##
## @param is_selected: The new selection state for the checkbox
func set_selected(is_selected: bool) -> void:
if select_checkbox:
select_checkbox.set_pressed_no_signal(is_selected)
## Get current selection state
func is_selected() -> bool:
return select_checkbox.button_pressed if select_checkbox else false
## Private methods
func _on_checkbox_toggled(pressed: bool) -> void:
selection_changed.emit(_domain_name, pressed)
func _on_only_button_pressed() -> void:
only_button_pressed.emit(_domain_name)
func _on_mouse_entered() -> void:
_is_hovered = true
_set_only_button_visibility(true)
func _on_mouse_exited() -> void:
_is_hovered = false
_set_only_button_visibility(false)
## Controls the visibility and interaction of the "Only" button based on hover state
##
## Instantly toggles the button's alpha transparency and mouse filter to create
## a hover-reveal effect. When hidden, the button becomes completely transparent
## and ignores mouse input. When visible, it becomes fully opaque and interactive.
##
## @param show: Whether to show (true) or hide (false) the only button
func _set_only_button_visibility(show: bool) -> void:
if not only_button:
return
# Set visibility instantly - no animation
var target_alpha = ONLY_BUTTON_ALPHA_VISIBLE if show else ONLY_BUTTON_ALPHA_HIDDEN
var target_mouse_filter = Control.MOUSE_FILTER_PASS if show else Control.MOUSE_FILTER_IGNORE
only_button.modulate.a = target_alpha
only_button.mouse_filter = target_mouse_filter
func _gui_input(event: InputEvent) -> void:
# Handle keyboard navigation
if event is InputEventKey and event.pressed:
match event.keycode:
KEY_SPACE, KEY_ENTER:
if select_checkbox:
select_checkbox.button_pressed = not select_checkbox.button_pressed
_on_checkbox_toggled(select_checkbox.button_pressed)
accept_event()
KEY_O:
if event.ctrl_pressed: # Ctrl+O for "Only"
_on_only_button_pressed()
accept_event()
func _notification(what: int) -> void:
match what:
NOTIFICATION_FOCUS_ENTER:
# Add visual focus indication if needed
pass
NOTIFICATION_FOCUS_EXIT:
# Remove visual focus indication if needed
pass
@@ -1 +0,0 @@
uid://dgkxvw0v5n6hq
@@ -1,256 +0,0 @@
class_name DomainSelector extends MenuButton
## Standalone domain selector component for filtering log messages by domain
## Provides clean interface with proper signal-based communication
const LoggieConsoleSettings = preload("res://addons/loggie-console/resources/loggie_console_settings.gd")
const DomainColorManager = preload("res://addons/loggie-console/scripts/domain_color_manager.gd")
# Signals
signal domains_changed(enabled_domains: Array[String])
# Properties
var _enabled_domains: Array[String] = []
var _all_domains: Array[String] = []
var _domain_popup: PopupMenu
var _color_manager: DomainColorManager
# Constants
const ALL_ITEM_ID = 0
const SEPARATOR_ID = 1
const DOMAIN_ID_OFFSET = 2
func _ready() -> void:
_domain_popup = get_popup()
_domain_popup.id_pressed.connect(_on_domain_selected)
_initialize_empty_state()
## Set the domain color manager instance
func set_color_manager(color_manager: DomainColorManager) -> void:
_color_manager = color_manager
## Initialize the selector with available domains
func set_domains(domains: Array[String]) -> void:
_all_domains = domains.duplicate()
_enabled_domains = domains.duplicate()
_rebuild_popup()
_update_button_text()
domains_changed.emit(_enabled_domains)
## Initialize the selector with all domains, but only enable selected ones
## Console domains appear normally but are unselected by default
func set_domains_with_console_unselected(all_domains: Array[String], enabled_domains: Array[String], console_domains: Array[String]) -> void:
_all_domains = all_domains.duplicate()
_enabled_domains = enabled_domains.duplicate()
_rebuild_popup_with_console_unselected(console_domains)
_update_button_text()
domains_changed.emit(_enabled_domains)
## Add a new domain to the selector (for dynamic discovery)
func add_domain(domain_name: String) -> void:
if domain_name.is_empty() or _all_domains.has(domain_name):
return
_all_domains.append(domain_name)
_enabled_domains.append(domain_name)
# Add new item to popup with color icon
var display_name = _get_domain_display_name(domain_name)
var color_icon = _create_domain_color_icon(domain_name)
var item_id = _get_domain_item_id(_all_domains.size() - 1)
_domain_popup.add_check_item(display_name, item_id)
_domain_popup.set_item_icon(item_id, color_icon)
_domain_popup.set_item_checked(item_id, true)
_update_all_checkbox()
_update_button_text()
domains_changed.emit(_enabled_domains)
## Get currently enabled domains
func get_enabled_domains() -> Array[String]:
return _enabled_domains.duplicate()
## Check if a domain is enabled
func is_domain_enabled(domain_name: String) -> bool:
return _enabled_domains.has(domain_name)
## Private methods
## Create colored icon for domain display in popup
func _create_domain_color_icon(domain_name: String, size: int = 12) -> ImageTexture:
var color: Color = Color.WHITE # Default color
if _color_manager:
color = _color_manager.get_domain_color(domain_name)
# Create a small colored square image
var image = Image.create(size, size, false, Image.FORMAT_RGBA8)
image.fill(color)
# Convert to texture
var texture: ImageTexture = ImageTexture.create_from_image(image)
return texture
## Get display name for domain (without color formatting)
func _get_domain_display_name(domain_name: String) -> String:
return domain_name if not domain_name.is_empty() else "(default)"
func _initialize_empty_state() -> void:
_domain_popup.clear()
_domain_popup.add_item("No domains available")
_domain_popup.set_item_disabled(0, true)
text = "Domains: None"
func _rebuild_popup() -> void:
_domain_popup.clear()
# Add "All" toggle
_domain_popup.add_check_item("All", ALL_ITEM_ID)
_domain_popup.set_item_checked(ALL_ITEM_ID, _enabled_domains.size() == _all_domains.size())
# Add separator
_domain_popup.add_separator()
# Add individual domain items with color icons
for i in range(_all_domains.size()):
var domain_name = _all_domains[i]
var display_name = _get_domain_display_name(domain_name)
var color_icon = _create_domain_color_icon(domain_name)
var item_id = _get_domain_item_id(i)
_domain_popup.add_check_item(display_name, item_id)
_domain_popup.set_item_icon(item_id, color_icon)
_domain_popup.set_item_checked(item_id, _enabled_domains.has(domain_name))
## Builds popup with console domain unselected by default (no separator)
func _rebuild_popup_with_console_unselected(console_domains: Array[String]) -> void:
_domain_popup.clear()
# Add "All" toggle (only for non-console domains)
_domain_popup.add_check_item("All", ALL_ITEM_ID)
var non_console_domains = []
for domain in _all_domains:
if not console_domains.has(domain):
non_console_domains.append(domain)
_domain_popup.set_item_checked(ALL_ITEM_ID, _enabled_domains.size() == non_console_domains.size())
# Add separator
_domain_popup.add_separator()
# Add all domain items in order with color icons
for i in range(_all_domains.size()):
var domain_name = _all_domains[i]
var display_name: String
# Use friendly name for console domain
if console_domains.has(domain_name):
display_name = "LoggieConsole" # Simplified name for console domain
else:
display_name = domain_name if not domain_name.is_empty() else "(default)"
# Create color icon for the domain
var color_icon = _create_domain_color_icon(domain_name)
var item_id = _get_domain_item_id(i)
_domain_popup.add_check_item(display_name, item_id)
_domain_popup.set_item_icon(item_id, color_icon)
_domain_popup.set_item_checked(item_id, _enabled_domains.has(domain_name))
func _on_domain_selected(id: int) -> void:
if id == ALL_ITEM_ID:
_toggle_all_domains()
else:
_toggle_individual_domain(id)
_update_all_checkbox()
_update_button_text()
domains_changed.emit(_enabled_domains)
func _toggle_all_domains() -> void:
var current_all_state = _domain_popup.is_item_checked(ALL_ITEM_ID)
var new_all_state = not current_all_state
# Update all checkbox
_domain_popup.set_item_checked(ALL_ITEM_ID, new_all_state)
# Update all domain checkboxes and enabled list
_enabled_domains.clear()
for i in range(_all_domains.size()):
var item_id = _get_domain_item_id(i)
_domain_popup.set_item_checked(item_id, new_all_state)
if new_all_state:
_enabled_domains.append(_all_domains[i])
func _toggle_individual_domain(id: int) -> void:
var domain_index = _get_domain_index_from_id(id)
if domain_index < 0 or domain_index >= _all_domains.size():
Loggie.msg("Invalid domain index %d for ID %d" % [domain_index, id]).domain(LoggieConsoleConstants.DOMAIN).warn()
return
var domain_name = _all_domains[domain_index]
var current_state = _domain_popup.is_item_checked(id)
var new_state = not current_state
# Update checkbox
_domain_popup.set_item_checked(id, new_state)
# Update enabled domains list
if new_state and not _enabled_domains.has(domain_name):
_enabled_domains.append(domain_name)
elif not new_state and _enabled_domains.has(domain_name):
_enabled_domains.erase(domain_name)
func _update_all_checkbox() -> void:
var all_enabled = _enabled_domains.size() == _all_domains.size()
_domain_popup.set_item_checked(ALL_ITEM_ID, all_enabled)
func _update_button_text() -> void:
var enabled_count = _enabled_domains.size()
var total_count = _all_domains.size()
if total_count == 0:
text = "Domains: None"
elif enabled_count == total_count:
text = "Domains: All"
elif enabled_count == 0:
text = "Domains: None"
elif enabled_count == 1:
var domain_name = _enabled_domains[0]
var display_name = _get_domain_display_name(domain_name)
text = "Domains: " + display_name
else:
text = "Domains: %d selected" % enabled_count
func _get_domain_item_id(domain_index: int) -> int:
return DOMAIN_ID_OFFSET + domain_index
func _get_domain_index_from_id(item_id: int) -> int:
return item_id - DOMAIN_ID_OFFSET
## Saves current domain selection state to persistent settings
## Param settings: The settings resource to populate with current domain state
func save_settings_to_resource(settings: LoggieConsoleSettings) -> void:
settings.enabled_domains = _enabled_domains.duplicate()
settings.all_known_domains = _all_domains.duplicate()
## Loads domain selection state from persistent settings and rebuilds UI
## Automatically validates that enabled domains still exist in the known domains list
## Param settings: The settings resource containing saved domain state
func load_settings_from_resource(settings: LoggieConsoleSettings) -> void:
_all_domains = settings.all_known_domains.duplicate()
_enabled_domains = settings.enabled_domains.duplicate()
# Remove any enabled domains that no longer exist in all_domains
var valid_enabled_domains: Array[String] = []
for domain in _enabled_domains:
if _all_domains.has(domain):
valid_enabled_domains.append(domain)
_enabled_domains = valid_enabled_domains
# Rebuild UI to reflect loaded state
if _all_domains.size() > 0:
_rebuild_popup()
_update_button_text()
else:
_initialize_empty_state()
@@ -1 +0,0 @@
uid://bq6xlpjg4qoca
@@ -1,209 +0,0 @@
class_name LogBuffer extends RichTextLabel
## Enhanced message buffer with retroactive filtering capabilities
## Stores all messages and applies filters dynamically without losing data
const LoggieConsoleSettings = preload("res://addons/loggie-console/resources/loggie_console_settings.gd")
const DomainColorManager = preload("res://addons/loggie-console/scripts/domain_color_manager.gd")
# Signals
signal filter_changed(visible_count: int, total_count: int)
# Internal message storage
var _all_messages: Array[LogMessage] = []
var _filtered_messages: Array[LogMessage] = []
var _current_filters: FilterState
# Domain color manager reference
var _color_manager: DomainColorManager
# Memory management constants
const DEFAULT_MAX_MESSAGES: int = 5000
const CLEANUP_PERCENTAGE: float = 0.2 # Remove 20% of messages when limit reached
# Memory management
@export var max_messages: int = DEFAULT_MAX_MESSAGES
## Internal LogMessage class for storing complete message data
class LogMessage:
var msg: LoggieMsg
var preprocessed_content: String
var msg_type: LoggieEnums.MsgType
var log_level: LoggieEnums.LogLevel
var timestamp: float
var enhanced_content: String
func _init(p_msg: LoggieMsg, p_content: String, p_type: LoggieEnums.MsgType, p_level: LoggieEnums.LogLevel) -> void:
msg = p_msg
preprocessed_content = p_content
msg_type = p_type
log_level = p_level
timestamp = Time.get_unix_time_from_system()
## Filter state for retroactive filtering
class FilterState:
var enabled_domains: Array[String] = []
var min_log_level: LoggieEnums.LogLevel = LoggieEnums.LogLevel.DEBUG
var text_search: String = ""
var show_stack_only: bool = false
func _init() -> void:
# Initialize to show ERROR and above by default (lower numbers = higher priority)
min_log_level = LoggieEnums.LogLevel.DEBUG
func _ready() -> void:
_current_filters = FilterState.new()
bbcode_enabled = true
## Set the domain color manager instance
func set_color_manager(color_manager: DomainColorManager) -> void:
_color_manager = color_manager
## Add a new log message to the buffer
func add_message(msg: LoggieMsg, content: String, msg_type: LoggieEnums.MsgType, log_level: LoggieEnums.LogLevel) -> void:
# Create and store the message
var log_msg: LogMessage = LogMessage.new(msg, content, msg_type, log_level)
log_msg.enhanced_content = _enhance_with_metadata(log_msg)
_all_messages.append(log_msg)
# Manage memory if needed
_manage_memory()
# Apply current filters and rebuild display
_reapply_filters()
## Set new filter state and reapply filters
func set_filters(filter_state: FilterState) -> void:
_current_filters = filter_state
_reapply_filters()
## Set text search filter
func set_text_search(search: String) -> void:
if _current_filters.text_search == search:
return
_current_filters.text_search = search
_reapply_filters()
## Clear all messages
func clear_all() -> void:
_all_messages.clear()
_filtered_messages.clear()
text = ""
clear()
# Emit updated counts
filter_changed.emit(0, 0)
## Get current message counts
func get_message_counts() -> Dictionary:
return {
"total": _all_messages.size(),
"filtered": _filtered_messages.size()
}
## Saves current buffer state to persistent settings resource
## Param settings: The settings resource to populate with current state
func save_settings_to_resource(settings: LoggieConsoleSettings) -> void:
settings.filter_state.from_filter_state(_current_filters)
settings.max_messages = max_messages
## Loads buffer state from persistent settings resource
## Note: Does not immediately apply filters to avoid triggering reapply before messages exist
## Param settings: The settings resource containing saved state
func load_settings_from_resource(settings: LoggieConsoleSettings) -> void:
if settings.filter_state:
_current_filters = settings.filter_state.to_filter_state()
max_messages = settings.max_messages
## Applies previously loaded filter state to existing messages
## Call this after load_settings_from_resource() and message loading is complete
func apply_loaded_filters() -> void:
_reapply_filters()
## Private methods
func _reapply_filters() -> void:
_filtered_messages.clear()
for msg in _all_messages:
if msg != null and _message_passes_filters(msg, _current_filters):
_filtered_messages.append(msg)
_rebuild_display()
# Emit filter change signal
filter_changed.emit(_filtered_messages.size(), _all_messages.size())
func _message_passes_filters(msg: LogMessage, filters: FilterState) -> bool:
# Domain check - if no domains are enabled, show no messages
if filters.enabled_domains.size() == 0:
return false
# If domains are enabled, check if this message's domain is in the enabled list
if not filters.enabled_domains.has(msg.msg.domain_name):
return false
# Level check - show messages at or above the minimum level (lower numbers = higher priority)
if msg.log_level > filters.min_log_level:
return false
# Text search
if not filters.text_search.is_empty():
var search_lower: String = filters.text_search.to_lower()
if not msg.preprocessed_content.to_lower().contains(search_lower):
return false
# Stack filter
if filters.show_stack_only and not msg.msg.appends_stack:
return false
return true
func _rebuild_display() -> void:
# Clear current display
text = ""
clear()
# Add all filtered messages
for msg in _filtered_messages:
text += msg.enhanced_content + "\n"
func _enhance_with_metadata(msg: LogMessage) -> String:
var metadata_parts: Array[String] = []
metadata_parts.append("[color=%s]%s [/color]" % [Color.DIM_GRAY.to_html(false), Time.get_time_string_from_unix_time(int(msg.timestamp))])
if msg.msg.domain_name and not msg.msg.domain_name.is_empty():
var domain_color_html: String = _color_manager.get_domain_color_html(msg.msg.domain_name)
metadata_parts.append("[color=%s]%s[/color]" % [domain_color_html, msg.msg.domain_name])
if msg.msg.domain_name and not msg.msg.domain_name.is_empty():
metadata_parts.append("[color=%s][%s] [/color]" % [_get_color_for_log_level(msg.log_level).to_html(false), LoggieEnums.LogLevel.keys()[msg.log_level]])
return "%s %s" % [" ".join(metadata_parts), msg.msg.content[0]]
func _get_color_for_log_level(level: LoggieEnums.LogLevel) -> Color:
match level:
LoggieEnums.LogLevel.ERROR:
return Color.RED
LoggieEnums.LogLevel.WARN:
return Color.ORANGE
LoggieEnums.LogLevel.NOTICE:
return Color.GREEN
LoggieEnums.LogLevel.INFO:
return Color.WHITE
LoggieEnums.LogLevel.DEBUG, _:
return Color.AQUA
## Manages memory by removing oldest messages when buffer limit exceeded
## Uses CLEANUP_PERCENTAGE constant to determine how many messages to remove
func _manage_memory() -> void:
if _all_messages.size() > max_messages:
# Remove oldest messages based on cleanup percentage
var remove_count: int = int(max_messages * CLEANUP_PERCENTAGE)
_all_messages = _all_messages.slice(remove_count)
# Note: This will cause filtered_messages indices to be invalid,
# but _reapply_filters() will be called which rebuilds it
@@ -1 +0,0 @@
uid://wah8efffydnu
@@ -1,62 +0,0 @@
class_name LogLevelFilter extends OptionButton
## Standalone log level filter component
## Manages log level selection and syncs with Loggie settings
const LoggieConsoleSettings = preload("res://addons/loggie-console/resources/loggie_console_settings.gd")
# Signals
signal level_changed(new_level: LoggieEnums.LogLevel)
func _ready() -> void:
_setup_level_options()
item_selected.connect(_on_level_selected)
tooltip_text = "Filter by log level"
## Initialize with current Loggie settings
func initialize() -> void:
selected = Loggie.settings.log_level
level_changed.emit(Loggie.settings.log_level)
## Get currently selected log level
func get_current_level() -> LoggieEnums.LogLevel:
if selected >= 0 and selected < get_item_count():
return get_item_id(selected) as LoggieEnums.LogLevel
return LoggieEnums.LogLevel.DEBUG
## Set log level programmatically
func set_log_level(level: LoggieEnums.LogLevel) -> void:
for i in range(get_item_count()):
if get_item_id(i) == level:
selected = i
break
## Private methods
func _setup_level_options() -> void:
clear()
# Add all log levels as options
for value in LoggieEnums.LogLevel.values():
var label: String = LoggieEnums.LogLevel.keys()[value]
add_item(label, value)
func _on_level_selected(index: int) -> void:
var new_level = get_item_id(index) as LoggieEnums.LogLevel
# Update Loggie settings
Loggie.settings.log_level = new_level
# Emit change signal
level_changed.emit(new_level)
## Saves current log level selection to persistent settings
## Param settings: The settings resource to populate with current log level
func save_settings_to_resource(settings: LoggieConsoleSettings) -> void:
settings.set_log_level(get_current_level())
## Loads log level selection from persistent settings and applies it
## Updates the UI control to reflect the loaded level without emitting change signals
## Param settings: The settings resource containing saved log level
func load_settings_from_resource(settings: LoggieConsoleSettings) -> void:
set_log_level(settings.get_log_level())
@@ -1 +0,0 @@
uid://d3l2tmf5gym1d
@@ -1,381 +0,0 @@
class_name PanelDomainSelector extends Control
## Panel-based domain selector component for filtering log messages by domain
## Provides enhanced UI with vertical list, hover "Only" buttons, and Apply/Cancel workflow
## Drop-in replacement for MenuButton-based DomainSelector with identical API
const LoggieConsoleSettings = preload("res://addons/loggie-console/resources/loggie_console_settings.gd")
const LoggieConsoleConstants = preload("res://addons/loggie-console/scripts/loggie_console_constants.gd")
const DomainItemScene = preload("res://addons/loggie-console/scenes/domain_item.tscn")
const DomainColorManager = preload("res://addons/loggie-console/scripts/domain_color_manager.gd")
# Signals - maintains API compatibility with existing DomainSelector
signal domains_changed(enabled_domains: Array[String])
# UI Components - exported for scene tree assignment
@export var trigger_button: Button
@export var domain_panel: PopupPanel
@export var select_all_button: Button
@export var select_none_button: Button
@export var scroll_container: ScrollContainer
@export var domain_list: VBoxContainer
# Domain management
var _all_domains: Array[String] = []
var _enabled_domains: Array[String] = []
# Domain item tracking
var _domain_items: Array[DomainItem] = []
# Panel state tracking
var _panel_is_open: bool = false
# Color management
var _color_manager: DomainColorManager
# Panel sizing constants
const PANEL_WIDTH: int = 300
const PANEL_MAX_HEIGHT: int = 400
const PANEL_MIN_HEIGHT: int = 150
# Panel positioning constants
const PANEL_VERTICAL_GAP: int = 2
const PANEL_HORIZONTAL_MARGIN: int = 10
# Domain item layout constants
const DOMAIN_ITEM_HEIGHT: int = 32
const PANEL_HEADER_HEIGHT: int = 40
const PANEL_SELECTION_CONTROLS_HEIGHT: int = 28
const PANEL_FOOTER_HEIGHT: int = 40
const PANEL_PADDING: int = 20
## Initializes the panel domain selector component
##
## Sets up UI connections, panel sizing, and attempts to load domains from Loggie.
## If no domains are found, initializes an empty state. All UI components must be
## properly assigned via @export variables in the scene for initialization to succeed.
func _ready() -> void:
if not trigger_button:
_initialize_empty_state()
return
domain_panel.hide()
# Connect UI signals
trigger_button.pressed.connect(_on_trigger_button_pressed)
select_all_button.pressed.connect(_on_select_all_button_pressed)
select_none_button.pressed.connect(_on_select_none_button_pressed)
# Connect popup signals to track state
domain_panel.popup_hide.connect(_on_panel_hidden)
domain_panel.about_to_popup.connect(_on_panel_about_to_show)
# Set initial panel size
domain_panel.size = Vector2(PANEL_WIDTH, PANEL_MIN_HEIGHT)
# Try to initialize with real Loggie domains first
_initialize_domains_from_loggie()
# If no domains found, show empty state
if _all_domains.is_empty():
_initialize_empty_state()
## Set the domain color manager instance
func set_color_manager(color_manager: DomainColorManager) -> void:
_color_manager = color_manager
## Initialize the selector with available domains
func set_domains(domains: Array[String]) -> void:
_all_domains = domains.duplicate()
_enabled_domains = domains.duplicate()
_rebuild_domain_list()
_update_trigger_button_text()
domains_changed.emit(_enabled_domains)
## Initialize the selector with all domains, but only enable selected ones
## Console domains appear normally but are unselected by default
func set_domains_with_console_unselected(all_domains: Array[String], enabled_domains: Array[String], _console_domains: Array[String]) -> void:
_all_domains = all_domains.duplicate()
_enabled_domains = enabled_domains.duplicate()
_rebuild_domain_list()
_update_trigger_button_text()
domains_changed.emit(_enabled_domains)
## Add a new domain to the selector (for dynamic discovery)
func add_domain(domain_name: String) -> void:
if domain_name.is_empty() or _all_domains.has(domain_name):
return
_all_domains.append(domain_name)
_enabled_domains.append(domain_name)
_add_domain_item(domain_name, true)
_update_trigger_button_text()
_update_panel_size()
domains_changed.emit(_enabled_domains)
## Get currently enabled domains
func get_enabled_domains() -> Array[String]:
return _enabled_domains.duplicate()
## Check if a domain is enabled
func is_domain_enabled(domain_name: String) -> bool:
return _enabled_domains.has(domain_name)
## Saves current domain selection state to persistent settings
func save_settings_to_resource(settings: LoggieConsoleSettings) -> void:
settings.enabled_domains = _enabled_domains.duplicate()
settings.all_known_domains = _all_domains.duplicate()
## Loads domain selection state from persistent settings and rebuilds UI
func load_settings_from_resource(settings: LoggieConsoleSettings) -> void:
_all_domains = settings.all_known_domains.duplicate()
_enabled_domains = settings.enabled_domains.duplicate()
# Remove any enabled domains that no longer exist in all_domains
var valid_enabled_domains: Array[String] = []
for domain in _enabled_domains:
if _all_domains.has(domain):
valid_enabled_domains.append(domain)
_enabled_domains = valid_enabled_domains
# Rebuild UI to reflect loaded state
if _all_domains.size() > 0:
_rebuild_domain_list()
_update_trigger_button_text()
else:
_initialize_empty_state()
## Private methods
func _initialize_empty_state() -> void:
if trigger_button:
trigger_button.text = "Domains: None"
trigger_button.tooltip_text = "No domains available"
func _initialize_domains_from_loggie() -> void:
# Query Loggie for registered domains
if not Loggie:
return
var loggie_domains: Array[String] = []
# Get domains from Loggie's registered domains
for domain_name: String in Loggie.domains.keys():
if not domain_name.is_empty():
loggie_domains.append(domain_name)
# If we found domains, initialize with them
if loggie_domains.size() > 0:
set_domains(loggie_domains)
Loggie.msg("Initialized domain selector with %d domains from Loggie" % loggie_domains.size()).domain(LoggieConsoleConstants.DOMAIN).info()
else:
_initialize_empty_state()
## Refresh domains from Loggie (useful for dynamic discovery)
func refresh_domains_from_loggie() -> void:
if not Loggie:
return
var _current_domains: Array[String] = _all_domains.duplicate()
var loggie_domains: Array[String] = []
# Get current domains from Loggie
for domain_name: String in Loggie.domains.keys():
if not domain_name.is_empty():
loggie_domains.append(domain_name)
# Add any new domains we discovered
var new_domains_added: bool = false
for domain_name in loggie_domains:
if not _all_domains.has(domain_name):
add_domain(domain_name)
new_domains_added = true
if new_domains_added:
Loggie.msg("Discovered new domains from Loggie").domain(LoggieConsoleConstants.DOMAIN).info()
func _on_trigger_button_pressed() -> void:
if _all_domains.is_empty():
return
# Only open panel if not already open (button disabled when open)
if not _panel_is_open:
# Position and show panel
domain_panel.popup()
_position_panel()
func _on_select_all_button_pressed() -> void:
# Select all domains instantly
_enabled_domains = _all_domains.duplicate()
_update_domain_items_state()
_update_trigger_button_text()
domains_changed.emit(_enabled_domains)
func _on_select_none_button_pressed() -> void:
# Deselect all domains instantly
_enabled_domains.clear()
_update_domain_items_state()
_update_trigger_button_text()
domains_changed.emit(_enabled_domains)
## Intelligently positions the domain selection panel relative to the trigger button
##
## Calculates optimal panel placement considering viewport boundaries and trigger button
## position. Prefers positioning below and left-aligned to the trigger button, but will
## adjust to above or right-aligned if necessary to keep the panel fully visible.
## Ensures minimum margins from viewport edges for accessibility.
func _position_panel() -> void:
if not trigger_button:
return
# Get trigger button global position and size
var button_global_pos: Vector2 = trigger_button.global_position
var button_size: Vector2 = trigger_button.size
await get_tree().process_frame
var panel_size: Vector2 = domain_panel.size
# Get viewport rect for boundary checking
var viewport: Viewport = get_viewport()
var viewport_rect: Rect2 = viewport.get_visible_rect()
# Position directly below button, aligned to left edge
var panel_pos: Vector2i = Vector2i(
button_global_pos.x,
button_global_pos.y + button_size.y + PANEL_VERTICAL_GAP
)
# Check if panel would go off-screen horizontally
if panel_pos.x + panel_size.x > viewport_rect.size.x:
# Align to right edge of button instead
panel_pos.x = button_global_pos.x + button_size.x - panel_size.x
# Ensure still within bounds
panel_pos.x = max(PANEL_HORIZONTAL_MARGIN, panel_pos.x)
# Check if panel would go off-screen vertically
if panel_pos.y + panel_size.y > viewport_rect.size.y:
# Position above button instead
panel_pos.y = button_global_pos.y - panel_size.y - PANEL_VERTICAL_GAP
# Final boundary check
panel_pos.x = max(PANEL_HORIZONTAL_MARGIN, min(panel_pos.x, viewport_rect.size.x - panel_size.x - PANEL_HORIZONTAL_MARGIN))
panel_pos.y = max(PANEL_HORIZONTAL_MARGIN, min(panel_pos.y, viewport_rect.size.y - panel_size.y - PANEL_HORIZONTAL_MARGIN))
domain_panel.position = get_window().position + panel_pos
func _rebuild_domain_list() -> void:
# Clear existing domain items
_clear_domain_items()
# Create new domain items
for domain_name in _all_domains:
var is_enabled: bool = _enabled_domains.has(domain_name)
_add_domain_item(domain_name, is_enabled)
_update_panel_size()
func _clear_domain_items() -> void:
for item in _domain_items:
if is_instance_valid(item):
item.queue_free()
_domain_items.clear()
# Clear any remaining children
for child in domain_list.get_children():
child.queue_free()
func _add_domain_item(domain_name: String, is_enabled: bool) -> void:
var item: DomainItem = DomainItemScene.instantiate()
# Add to scene first
domain_list.add_child(item)
_domain_items.append(item)
# Set color manager if available
if _color_manager:
item.set_color_manager(_color_manager)
# Setup domain after adding to scene tree (ensures _ready is called first)
item.setup_domain(domain_name, is_enabled)
# Connect signals
item.selection_changed.connect(_on_domain_item_selection_changed)
item.only_button_pressed.connect(_on_domain_item_only_pressed)
## Dynamically adjusts panel size based on the number of domain items
##
## Calculates the optimal panel height by summing all component heights (header,
## selection controls, domain items, footer, and padding). Clamps the result between
## minimum and maximum height constraints to ensure usability and prevent overflow.
func _update_panel_size() -> void:
if not domain_panel or _domain_items.is_empty():
return
# Calculate required height based on content
var required_height: int = PANEL_HEADER_HEIGHT + PANEL_SELECTION_CONTROLS_HEIGHT + (_domain_items.size() * DOMAIN_ITEM_HEIGHT) + PANEL_FOOTER_HEIGHT + PANEL_PADDING
var final_height: int = clamp(required_height, PANEL_MIN_HEIGHT, PANEL_MAX_HEIGHT)
domain_panel.size = Vector2(PANEL_WIDTH, final_height)
func _update_trigger_button_text() -> void:
if not trigger_button:
return
var enabled_count: int = _enabled_domains.size()
var total_count: int = _all_domains.size()
if total_count == 0:
trigger_button.text = "Domains: None"
trigger_button.tooltip_text = "No domains available"
elif enabled_count == total_count:
trigger_button.text = "Domains: All"
trigger_button.tooltip_text = "All domains selected (%d)" % total_count
elif enabled_count == 0:
trigger_button.text = "Domains: None"
trigger_button.tooltip_text = "No domains selected"
elif enabled_count == 1:
var domain_name: String = _enabled_domains[0]
var display_name: String = domain_name if not domain_name.is_empty() else "(default)"
trigger_button.text = "Domains: " + display_name
trigger_button.tooltip_text = "Selected: " + display_name
else:
trigger_button.text = "Domains: %d selected" % enabled_count
trigger_button.tooltip_text = "%d of %d domains selected" % [enabled_count, total_count]
func _update_domain_items_state() -> void:
for item in _domain_items:
if is_instance_valid(item):
var domain_name: String = item.get_domain_name()
var is_enabled: bool = _enabled_domains.has(domain_name)
item.set_selected(is_enabled)
func _on_domain_item_selection_changed(domain_name: String, is_selected: bool) -> void:
if is_selected and not _enabled_domains.has(domain_name):
_enabled_domains.append(domain_name)
elif not is_selected and _enabled_domains.has(domain_name):
_enabled_domains.erase(domain_name)
# Update UI and emit change immediately
_update_trigger_button_text()
domains_changed.emit(_enabled_domains)
func _on_domain_item_only_pressed(domain_name: String) -> void:
# Clear all selections and select only this domain instantly
_enabled_domains.clear()
_enabled_domains.append(domain_name)
_update_domain_items_state()
_update_trigger_button_text()
domains_changed.emit(_enabled_domains)
func _on_panel_about_to_show() -> void:
_panel_is_open = true
if trigger_button:
trigger_button.disabled = true
func _on_panel_hidden() -> void:
_panel_is_open = false
if trigger_button:
trigger_button.disabled = false
@@ -1 +0,0 @@
uid://bfmpjs4x3fhte
@@ -1,123 +0,0 @@
class_name RestoreButton extends CanvasLayer
## Floating restore button component for minimized LoggieConsole
##
## Self-contained overlay button that appears when the console is minimized.
## Handles its own positioning, styling, and lifecycle management with proper
## cleanup when the console is restored. Designed to be added to the root viewport
## for maximum visibility across all scenes.
const LoggieConsoleConstants = preload("res://addons/loggie-console/scripts/loggie_console_constants.gd")
# Alignment constants
enum Alignment {
TOP_LEFT = 0,
TOP_RIGHT = 1,
BOTTOM_LEFT = 2,
BOTTOM_RIGHT = 3
}
# Signals
signal restore_requested() ## Emitted when user clicks the restore button
@export var restore_button: Button
## Initializes the restore button component and sets up styling
##
## Configures the canvas layer for maximum rendering priority, sets up the
## container to fill the viewport, and applies professional styling to the
## restore button. All UI components must be properly assigned via @export.
func _ready() -> void:
Loggie.msg("RestoreButton _ready() called").domain(LoggieConsoleConstants.DOMAIN).debug()
# Connect button signal
restore_button.pressed.connect(_on_restore_button_pressed)
Loggie.msg("RestoreButton initialization complete").domain(LoggieConsoleConstants.DOMAIN).debug()
## Shows the restore button by adding it to the provided scene tree root
##
## This method expects to be called from a parent that has access to the scene tree.
## The parent should pass the root viewport so this component can add itself properly.
##
## @param root_viewport: The root viewport where the button should be added
func show_restore_button(root_viewport: Window = null) -> void:
Loggie.msg("RestoreButton show_restore_button() method called").domain(LoggieConsoleConstants.DOMAIN).debug()
if not root_viewport:
Loggie.msg("No root viewport available for restore button").domain(LoggieConsoleConstants.DOMAIN).error()
return
root_viewport.add_child(self)
Loggie.msg("Restore button added to root viewport: %s" % root_viewport.name).domain(LoggieConsoleConstants.DOMAIN).debug()
await get_tree().process_frame
# Ensure visibility
visible = true
Loggie.msg("Restore button visibility set to true").domain(LoggieConsoleConstants.DOMAIN).debug()
# Force positioning update and debug info
await get_tree().process_frame
## Hides and removes the restore button from the scene tree
##
## Safely removes the button from its parent and hides it. Handles cleanup
## and ensures proper resource management when the console is restored.
func hide_restore_button() -> void:
Loggie.msg("Hiding restore button").domain(LoggieConsoleConstants.DOMAIN).debug()
visible = false
# Remove from parent if attached
if get_parent():
get_parent().remove_child(self)
Loggie.msg("Restore button removed from parent").domain(LoggieConsoleConstants.DOMAIN).debug()
## Sets the alignment position of the restore button
##
## Updates the button's position flags to align it to the specified corner of the screen.
## @param alignment: The alignment enum value (TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT)
func set_alignment(alignment: Alignment) -> void:
if not restore_button:
Loggie.msg("RestoreButton not found, cannot set alignment").domain(LoggieConsoleConstants.DOMAIN).warn()
return
match alignment:
Alignment.TOP_LEFT:
restore_button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN # 0 = left align
restore_button.size_flags_vertical = Control.SIZE_SHRINK_BEGIN # 0 = top align
Alignment.TOP_RIGHT:
restore_button.size_flags_horizontal = Control.SIZE_SHRINK_END # 8 = right align
restore_button.size_flags_vertical = Control.SIZE_SHRINK_BEGIN # 0 = top align
Alignment.BOTTOM_LEFT:
restore_button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN # 0 = left align
restore_button.size_flags_vertical = Control.SIZE_SHRINK_END # 8 = bottom align
Alignment.BOTTOM_RIGHT:
restore_button.size_flags_horizontal = Control.SIZE_SHRINK_END # 8 = right align
restore_button.size_flags_vertical = Control.SIZE_SHRINK_END # 8 = bottom align
Loggie.msg("Restore button alignment set to: %s" % _get_alignment_name(alignment)).domain(LoggieConsoleConstants.DOMAIN).debug()
## Gets a human-readable name for the alignment enum
##
## @param alignment: The alignment enum value
## @return: String name of the alignment
func _get_alignment_name(alignment: Alignment) -> String:
match alignment:
Alignment.TOP_LEFT:
return "Top Left"
Alignment.TOP_RIGHT:
return "Top Right"
Alignment.BOTTOM_LEFT:
return "Bottom Left"
Alignment.BOTTOM_RIGHT:
return "Bottom Right"
_:
return "Unknown"
## Handles restore button click events
##
## Emits the restore_requested signal when the button is pressed, allowing
## the parent console to handle the actual restore logic while keeping
## this component focused solely on UI concerns.
func _on_restore_button_pressed() -> void:
Loggie.msg("Restore button pressed").domain(LoggieConsoleConstants.DOMAIN).debug()
restore_requested.emit()
@@ -1 +0,0 @@
uid://dmyd1gquxy42u
@@ -1,125 +0,0 @@
class_name LoggieConsoleSettingsPanel extends AcceptDialog
## Standalone settings panel component for LoggieConsole
##
## This component provides a clean, reusable interface for adjusting console settings
## such as text size multiplier. It emits signals when settings are changed and provides
## methods to get/set current values.
##
## Signals:
## - settings_applied(text_size_multiplier: float): Emitted when Apply is clicked
## - settings_reset(): Emitted when Reset to Default is clicked
##
## Public Methods:
## - set_text_size_multiplier(value: float): Sets the current text size value
## - get_text_size_multiplier() -> float: Gets the current text size value
## - reset_to_defaults(): Resets all settings to default values
## Emitted when the user clicks Apply with current settings
signal settings_applied(text_size_multiplier: float, restore_button_alignment: RestoreButton.Alignment)
## Emitted when the user clicks Reset to Default
signal settings_reset()
# UI component references - assigned via editor
@export var _text_size_slider: HSlider
@export var _text_size_value_label: Label
@export var _reset_button: Button
@export var _alignment_option: OptionButton ## Optional - may be null in simplified versions
# Constants
const DEFAULT_TEXT_SIZE_MULTIPLIER: float = 1.0
const MIN_TEXT_SIZE_MULTIPLIER: float = 0.5
const MAX_TEXT_SIZE_MULTIPLIER: float = 2.0
const DEFAULT_RESTORE_BUTTON_ALIGNMENT: RestoreButton.Alignment = RestoreButton.Alignment.TOP_RIGHT
## Initializes the settings panel and connects internal signals
func _ready() -> void:
# Connect internal UI signals
_text_size_slider.value_changed.connect(_on_text_size_slider_changed)
_reset_button.pressed.connect(_on_reset_button_pressed)
confirmed.connect(_on_apply_pressed)
# Initialize display
_update_text_size_label(_text_size_slider.value)
## Sets the text size multiplier value
##
## Updates the slider position and display label to reflect the new value.
## The value is automatically clamped to the valid range (0.5 to 2.0).
##
## @param value: The text size multiplier (will be clamped to valid range)
func set_text_size_multiplier(value: float) -> void:
var clamped_value: float = clampf(value, MIN_TEXT_SIZE_MULTIPLIER, MAX_TEXT_SIZE_MULTIPLIER)
_text_size_slider.value = clamped_value
_update_text_size_label(clamped_value)
## Gets the current text size multiplier value
##
## @return: The current text size multiplier value from the slider
func get_text_size_multiplier() -> float:
return _text_size_slider.value
## Sets the restore button alignment
##
## Updates the alignment option button to reflect the new value.
## If the alignment option doesn't exist, the setting is ignored.
## @param alignment: The restore button alignment enum value
func set_restore_button_alignment(alignment: RestoreButton.Alignment) -> void:
if _alignment_option:
_alignment_option.selected = alignment as int
## Gets the current restore button alignment
##
## @return: The current restore button alignment as an enum value, or default if option doesn't exist
func get_restore_button_alignment() -> RestoreButton.Alignment:
if _alignment_option:
return _alignment_option.selected as RestoreButton.Alignment
else:
return DEFAULT_RESTORE_BUTTON_ALIGNMENT
## Resets all settings to their default values
##
## This method resets both text size multiplier and alignment to defaults and emits the settings_reset signal.
func reset_to_defaults() -> void:
set_text_size_multiplier(DEFAULT_TEXT_SIZE_MULTIPLIER)
set_restore_button_alignment(DEFAULT_RESTORE_BUTTON_ALIGNMENT)
settings_reset.emit()
## Shows the settings panel centered on screen
##
## Convenience method that wraps popup_centered() for easier API usage.
func show_settings() -> void:
popup_centered()
## Called when the text size slider value changes
##
## Updates the value display label to show the current multiplier.
## @param value: The new slider value
func _on_text_size_slider_changed(value: float) -> void:
_update_text_size_label(value)
## Called when the Reset to Default button is pressed
##
## Resets settings to defaults and emits the settings_reset signal.
func _on_reset_button_pressed() -> void:
reset_to_defaults()
## Called when the Apply button is pressed
##
## Emits the settings_applied signal with the current settings values.
func _on_apply_pressed() -> void:
settings_applied.emit(get_text_size_multiplier(), get_restore_button_alignment())
## Updates the text size value label to show the current multiplier
##
## @param value: The text size multiplier to display
func _update_text_size_label(value: float) -> void:
_text_size_value_label.text = "%.1fx" % value
## Validates that a text size multiplier value is within acceptable bounds
##
## @param value: The value to validate
## @return: True if the value is valid, false otherwise
func is_valid_text_size_multiplier(value: float) -> bool:
return value >= MIN_TEXT_SIZE_MULTIPLIER and value <= MAX_TEXT_SIZE_MULTIPLIER
@@ -1 +0,0 @@
uid://giuyu6vhhgam
@@ -1,55 +0,0 @@
class_name StatusDisplay extends Label
## Standalone status display component for console information
## Shows message counts, filter status, and real-time feedback
func _ready() -> void:
text = "Ready"
horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
## Update status with message and domain information
func update_status(total_messages: int, filtered_messages: int, enabled_domains: Array[String], total_domains: int) -> void:
var status_text = ""
if total_messages == 0:
status_text = "Ready"
else:
# Message count display
if filtered_messages == total_messages:
status_text = "%d msgs" % total_messages
else:
status_text = "%d/%d msgs" % [filtered_messages, total_messages]
# Domain filter info
if total_domains > 0 and enabled_domains.size() < total_domains:
status_text += " | %d/%d domains" % [enabled_domains.size(), total_domains]
text = status_text
## Update status when only message counts change (simpler version)
func update_message_counts(total_messages: int, filtered_messages: int) -> void:
if total_messages == 0:
text = "Ready"
elif filtered_messages == total_messages:
text = "%d msgs" % total_messages
else:
text = "%d/%d msgs" % [filtered_messages, total_messages]
## Show search status when text filtering is active
## @param search_term: Current search filter text
## @param results_count: Number of messages matching the search
## @param total_count: Total number of messages in buffer
func update_search_status(search_term: String, results_count: int, total_count: int) -> void:
if search_term.is_empty():
update_message_counts(total_count, results_count)
else:
text = "Search: \"%s\" - %d/%d msgs" % [search_term, results_count, total_count]
## Show loading/processing status with operation name
## @param operation: Name of the operation being performed
func show_processing_status(operation: String) -> void:
text = "%s..." % operation
## Reset to ready state
func reset() -> void:
text = "Ready"
@@ -1 +0,0 @@
uid://whrim6lbiako
@@ -1,167 +0,0 @@
class_name DomainColorManager extends RefCounted
## Centralized domain color management for LoggieConsole
## Provides consistent color mapping across all console components
## Domain color mapping information
class DomainColorInfo:
var domain_name: String
var color: Color
var palette_index: int
var cycle_count: int
var is_variation: bool
func _init(p_domain_name: String, p_color: Color, p_palette_index: int, p_cycle_count: int) -> void:
domain_name = p_domain_name
color = p_color
palette_index = p_palette_index
cycle_count = p_cycle_count
is_variation = p_cycle_count > 0
## Color usage statistics and diagnostic information
class ColorUsageInfo:
var total_domains: int
var palette_size: int
var current_cycle: int
var next_color_index: int
var remaining_in_current_cycle: int
var has_color_variations: bool
func _init(p_total_domains: int, p_palette_size: int, p_current_cycle: int, p_next_color_index: int, p_remaining_in_current_cycle: int, p_has_color_variations: bool) -> void:
total_domains = p_total_domains
palette_size = p_palette_size
current_cycle = p_current_cycle
next_color_index = p_next_color_index
remaining_in_current_cycle = p_remaining_in_current_cycle
has_color_variations = p_has_color_variations
# Domain color mapping storage
var _domain_colors: Dictionary = {} # String -> DomainColorInfo
var _color_index: int = 0
# Predefined color palette for domains
const DOMAIN_COLOR_PALETTE: Array[Color] = [
Color("#FF6B6B"), # Red
Color("#4ECDC4"), # Teal
Color("#45B7D1"), # Blue
Color("#96CEB4"), # Green
Color("#FECA57"), # Yellow
Color("#FF9FF3"), # Pink
Color("#54A0FF"), # Light Blue
Color("#5F27CD"), # Purple
Color("#00D2D3"), # Cyan
Color("#FF9F43"), # Orange
Color("#10AC84"), # Mint
Color("#EE5A24"), # Dark Orange
Color("#0984E3"), # Royal Blue
Color("#6C5CE7"), # Violet
Color("#A29BFE"), # Light Purple
Color("#FD79A8"), # Rose
Color("#FDCB6E"), # Light Yellow
Color("#6C5CE7"), # Indigo
Color("#00B894"), # Emerald
Color("#E84393") # Magenta
]
## Get or assign a color for a domain
func get_domain_color(domain: String) -> Color:
if domain.is_empty():
return Color.WHITE
if not _domain_colors.has(domain):
# Assign a new color from the palette with potential variations
var palette_index: int = _color_index % DOMAIN_COLOR_PALETTE.size()
var cycle_count: int = _color_index / DOMAIN_COLOR_PALETTE.size()
var base_color: Color = DOMAIN_COLOR_PALETTE[palette_index]
var color: Color = _get_color_variation(base_color, cycle_count)
var new_color_info: DomainColorInfo = DomainColorInfo.new(domain, color, palette_index, cycle_count)
_domain_colors[domain] = new_color_info
_color_index += 1
var existing_color_info: DomainColorInfo = _domain_colors[domain] as DomainColorInfo
return existing_color_info.color
## Get HTML color string for domain (for BBCode)
func get_domain_color_html(domain: String) -> String:
return get_domain_color(domain).to_html(false)
## Check if domain has assigned color
func has_domain_color(domain: String) -> bool:
return _domain_colors.has(domain)
## Get domain color information for a specific domain
func get_domain_color_info(domain: String) -> DomainColorInfo:
if not _domain_colors.has(domain):
# Trigger color assignment by calling get_domain_color
get_domain_color(domain)
return _domain_colors[domain] as DomainColorInfo
## Get all registered domains with their color information
func get_all_domain_color_infos() -> Array[DomainColorInfo]:
var infos: Array[DomainColorInfo] = []
for domain_color_info: DomainColorInfo in _domain_colors.values():
infos.append(domain_color_info)
return infos
## Get diagnostic information about color usage and cycles
func get_color_usage_info() -> ColorUsageInfo:
var current_cycle: int = _color_index / DOMAIN_COLOR_PALETTE.size()
var remaining_in_cycle: int = DOMAIN_COLOR_PALETTE.size() - (_color_index % DOMAIN_COLOR_PALETTE.size())
return ColorUsageInfo.new(
_domain_colors.size(),
DOMAIN_COLOR_PALETTE.size(),
current_cycle,
_color_index % DOMAIN_COLOR_PALETTE.size(),
remaining_in_cycle,
current_cycle > 0
)
## Clear all color assignments (for testing/reset)
func clear_all_colors() -> void:
_domain_colors.clear()
_color_index = 0
## Get the color palette for reference
func get_color_palette() -> Array[Color]:
return DOMAIN_COLOR_PALETTE
## Create color variations for domains beyond the initial palette
## Uses brightness and saturation adjustments to create distinguishable variations
func _get_color_variation(base_color: Color, cycle_count: int) -> Color:
if cycle_count == 0:
# First cycle - use original colors
return base_color
# Get HSV components directly from Color
var hue: float = base_color.h
var saturation: float = base_color.s
var value: float = base_color.v
match cycle_count:
1:
# Second cycle - Increase brightness
value = minf(value * 1.3, 1.0)
2:
# Third cycle - Decrease saturation (more pastel)
saturation = maxf(saturation * 0.6, 0.3)
3:
# Fourth cycle - Decrease brightness
value = maxf(value * 0.7, 0.4)
4:
# Fifth cycle - High saturation, medium brightness
saturation = minf(saturation * 1.2, 1.0)
value = maxf(minf(value * 0.85, 1.0), 0.5)
_:
# Beyond fifth cycle - Use mathematical variations
var brightness_mod: float = 1.0 + (cycle_count % 3) * 0.25 - 0.25 # -0.25, 0, +0.25
var saturation_mod: float = 1.0 + ((cycle_count + 1) % 3) * 0.2 - 0.2 # -0.2, 0, +0.2
value = maxf(minf(value * brightness_mod, 1.0), 0.3)
saturation = maxf(minf(saturation * saturation_mod, 1.0), 0.2)
return Color.from_hsv(hue, saturation, value)
@@ -1 +0,0 @@
uid://x0ut5e0gpyu1
@@ -1,66 +0,0 @@
class_name LoggieConsoleChannel extends LoggieMsgChannel
## Custom Loggie channel that feeds messages directly to the LoggieConsole
## This channel captures all message metadata (level, type, domain, channels, stack info)
## without requiring string parsing
# Reference to the console window that will display messages
var _console_window: LoggieConsole
func _init() -> void:
ID = "loggie_console"
# Enable all preprocessing steps to get complete information
preprocess_flags = (
LoggieEnums.PreprocessStep.APPEND_TIMESTAMPS |
LoggieEnums.PreprocessStep.APPEND_DOMAIN_NAME |
LoggieEnums.PreprocessStep.APPEND_CLASS_NAME
)
## Sets the console window that will receive messages from this channel
## @param window: The console window instance, must not be null
func set_console_window(window: LoggieConsole) -> void:
if window == null:
Loggie.msg("Cannot set null console window").domain(LoggieConsoleConstants.DOMAIN).error()
return
_console_window = window
## Override send method to pass complete message data to console
## @param msg: The Loggie message object containing all metadata
## @param msg_type: The message type enum from Loggie
func send(msg: LoggieMsg, msg_type: LoggieEnums.MsgType) -> void:
if _console_window == null:
Loggie.msg("No console window set, message dropped").domain(LoggieConsoleConstants.DOMAIN).warn()
return
if msg == null:
Loggie.msg("Received null message").domain(LoggieConsoleConstants.DOMAIN).error()
return
# Get the preprocessed text (already formatted by Loggie)
var preprocessed_text = msg.last_preprocess_result
# Determine log level from the message content patterns or message type
var log_level = _determine_log_level(msg, msg_type, preprocessed_text)
# Send complete message data to console
_console_window.receive_log_message(msg, preprocessed_text, msg_type, log_level)
## Determine the log level based on message type and content
func _determine_log_level(_msg: LoggieMsg, msg_type: LoggieEnums.MsgType, content: String) -> LoggieEnums.LogLevel:
# Use message type to infer log level
match msg_type:
LoggieEnums.MsgType.ERROR:
return LoggieEnums.LogLevel.ERROR
LoggieEnums.MsgType.WARN:
return LoggieEnums.LogLevel.WARN
LoggieEnums.MsgType.DEBUG:
return LoggieEnums.LogLevel.DEBUG
LoggieEnums.MsgType.INFO:
# For standard messages, check content patterns to determine level
if content.contains("[NOTICE]"):
return LoggieEnums.LogLevel.NOTICE
else:
return LoggieEnums.LogLevel.INFO
_:
return LoggieEnums.LogLevel.INFO
@@ -1 +0,0 @@
uid://bcehgenffbmr3
@@ -1,9 +0,0 @@
class_name LoggieConsoleConstants extends RefCounted
## Centralized constants for the LoggieConsole addon
##
## Contains all shared constants used across LoggieConsole components to ensure
## consistency and avoid duplication. Use StringName for efficient string operations.
## Domain for all LoggieConsole logging messages
const DOMAIN: StringName = &"LOGGIE_CONSOLE"
@@ -1 +0,0 @@
uid://7iexr4yxwqfg
@@ -1,96 +0,0 @@
class_name ConsoleSettingsManager extends RefCounted
## Manages persistent storage of LoggieConsole settings
##
## Provides robust save/load functionality with automatic backup creation and
## comprehensive error handling. Settings are saved immediately when requested
## (no debouncing) and loaded with fallback to backup file or defaults.
const LoggieConsoleSettings = preload("res://addons/loggie-console/resources/loggie_console_settings.gd")
# File paths and configuration
const SETTINGS_PATH = "user://loggie_console_settings.tres"
const BACKUP_PATH = "user://loggie_console_settings_backup.tres"
const SETTINGS_RESOURCE_TYPE = "LoggieConsoleSettings"
## Loads console settings with comprehensive error handling and fallback chain
##
## Load order: primary file → backup file → create defaults
## All loaded settings are validated and sanitized before returning.
## Returns: A valid LoggieConsoleSettings instance (never null)
func load_settings() -> LoggieConsoleSettings:
var settings: LoggieConsoleSettings = null
# Attempt to load primary settings file
settings = _try_load_file(SETTINGS_PATH, "main settings")
# Fall back to backup file if primary failed
if settings == null:
settings = _try_load_file(BACKUP_PATH, "backup settings")
# Create default settings if both files failed
if settings == null:
Loggie.msg("Creating default console settings").domain(LoggieConsoleConstants.DOMAIN).info()
settings = LoggieConsoleSettings.new()
# Always validate to ensure data integrity
settings.validate()
Loggie.msg("Console settings loaded successfully").domain(LoggieConsoleConstants.DOMAIN).debug()
return settings
## Saves console settings to disk immediately with backup creation
##
## Creates a backup of the existing file before saving, then validates
## the settings and writes them using Godot's ResourceSaver.
## Param settings: The settings to save (must not be null)
## Returns: true if save succeeded, false if it failed
func save_settings(settings: LoggieConsoleSettings) -> bool:
return _save_with_backup(settings)
## Attempts to load a settings file with error handling
## Param path: File path to load from
## Param description: Human-readable description for logging
## Returns: Loaded settings or null if loading failed
func _try_load_file(path: String, description: String) -> LoggieConsoleSettings:
if not FileAccess.file_exists(path):
Loggie.msg("Settings file not found: %s" % path).domain(LoggieConsoleConstants.DOMAIN).debug()
return null
var resource: Resource = ResourceLoader.load(path, SETTINGS_RESOURCE_TYPE)
if resource == null:
Loggie.msg("Failed to load %s from: %s" % [description, path]).domain(LoggieConsoleConstants.DOMAIN).warn()
return null
if not resource is LoggieConsoleSettings:
Loggie.msg("Invalid resource type in %s: %s" % [description, path]).domain(LoggieConsoleConstants.DOMAIN).error()
return null
Loggie.msg("Loaded %s from: %s" % [description, path]).domain(LoggieConsoleConstants.DOMAIN).debug()
return resource as LoggieConsoleSettings
## Saves settings with automatic backup creation
## Param settings: The settings to save
## Returns: true if save succeeded, false otherwise
func _save_with_backup(settings: LoggieConsoleSettings) -> bool:
if not settings:
Loggie.msg("Cannot save null settings").domain(LoggieConsoleConstants.DOMAIN).error()
return false
# Validate settings before saving
settings.validate()
# Create backup of existing file before overwriting
if FileAccess.file_exists(SETTINGS_PATH):
var dir: DirAccess = DirAccess.open("user://")
if dir:
dir.copy(SETTINGS_PATH, BACKUP_PATH)
# Save the validated settings
var error: Error = ResourceSaver.save(settings, SETTINGS_PATH)
if error != OK:
Loggie.msg("Failed to save console settings: %s" % error_string(error)).domain(LoggieConsoleConstants.DOMAIN).error()
return false
Loggie.msg("Console settings saved successfully").domain(LoggieConsoleConstants.DOMAIN).debug()
return true
@@ -1 +0,0 @@
uid://bntgmwyauc4h6
-141
View File
@@ -1,141 +0,0 @@
[gd_resource type="Theme" format=3 uid="uid://c1lnnysnspc6v"]
[ext_resource type="StyleBox" uid="uid://dolgdjpxlvt8f" path="res://addons/loggie-console/assets/inner_panel_recess.tres" id="1_nubd1"]
[ext_resource type="Texture2D" uid="uid://bhps6csrl6lef" path="res://addons/loggie-console/assets/close.png" id="1_rapoa"]
[ext_resource type="StyleBox" uid="uid://bpkqcbx15ejak" path="res://addons/loggie-console/assets/outer_panel.tres" id="2_rapoa"]
[ext_resource type="StyleBox" uid="uid://cfafk3ybc15uo" path="res://addons/loggie-console/assets/outer_window_panel.tres" id="3_f200k"]
[ext_resource type="StyleBox" uid="uid://b7k8yo7jfhpue" path="res://addons/loggie-console/assets/inner_panel.tres" id="3_p21dw"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_button_normal"]
content_margin_left = 3.0
content_margin_top = 1.0
content_margin_right = 3.0
content_margin_bottom = 1.0
bg_color = Color(0.25, 0.25, 0.25, 1)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color(0.4, 0.4, 0.4, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_checkbox_normal"]
bg_color = Color(0.15, 0.15, 0.15, 1)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color(0.4, 0.4, 0.4, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_panel"]
bg_color = Color(0.15, 0.15, 0.15, 0.95)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color(0.3, 0.3, 0.3, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_richtextlabel"]
content_margin_left = 1.0
content_margin_top = 1.0
content_margin_right = 1.0
content_margin_bottom = 1.0
bg_color = Color(0.1, 0.1, 0.1, 1)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color(0.3, 0.3, 0.3, 1)
[sub_resource type="SystemFont" id="SystemFont_f200k"]
font_names = PackedStringArray("Monospace")
font_weight = 800
generate_mipmaps = true
multichannel_signed_distance_field = true
[resource]
Button/colors/font_color = Color(0.9, 0.9, 0.9, 1)
Button/colors/font_disabled_color = Color(0.5, 0.5, 0.5, 1)
Button/colors/font_hover_color = Color(1, 1, 1, 1)
Button/colors/font_pressed_color = Color(0.8, 0.8, 0.8, 1)
Button/font_sizes/font_size = 11
Button/styles/disabled = SubResource("StyleBoxFlat_button_normal")
Button/styles/hover = ExtResource("2_rapoa")
Button/styles/normal = ExtResource("2_rapoa")
Button/styles/pressed = ExtResource("1_nubd1")
CheckBox/colors/font_color = Color(0.9, 0.9, 0.9, 1)
CheckBox/colors/font_disabled_color = Color(0.5, 0.5, 0.5, 1)
CheckBox/colors/font_hover_color = Color(1, 1, 1, 1)
CheckBox/colors/font_pressed_color = Color(0.8, 0.8, 0.8, 1)
CheckBox/font_sizes/font_size = 11
CheckBox/styles/disabled = SubResource("StyleBoxFlat_checkbox_normal")
CheckBox/styles/hover_pressed = ExtResource("2_rapoa")
CheckBox/styles/normal = ExtResource("2_rapoa")
CheckBox/styles/pressed = ExtResource("2_rapoa")
HBoxContainer/constants/separation = 2
LineEdit/colors/caret_color = Color(1, 1, 1, 1)
LineEdit/colors/clear_button_color = Color(0, 0, 0, 1)
LineEdit/colors/clear_button_color_pressed = Color(0, 0, 0, 1)
LineEdit/colors/font_color = Color(1, 1, 1, 1)
LineEdit/colors/font_outline_color = Color(0, 0, 0, 1)
LineEdit/colors/font_placeholder_color = Color(0.447904, 0.509153, 0.407339, 1)
LineEdit/colors/font_selected_color = Color(0.792157, 0.901961, 0.85098, 1)
LineEdit/colors/font_uneditable_color = Color(0, 0, 0, 1)
LineEdit/colors/selection_color = Color(0.188235, 0.219608, 0.262745, 1)
LineEdit/constants/caret_width = 0
LineEdit/constants/minimum_character_width = 0
LineEdit/constants/outline_size = 0
LineEdit/font_sizes/font_size = 12
LineEdit/fonts/font = null
LineEdit/icons/clear = null
LineEdit/styles/focus = ExtResource("1_nubd1")
LineEdit/styles/normal = ExtResource("1_nubd1")
LineEdit/styles/read_only = ExtResource("3_p21dw")
MarginContainer/constants/margin_bottom = 1
MarginContainer/constants/margin_left = 1
MarginContainer/constants/margin_right = 1
MarginContainer/constants/margin_top = 1
MenuButton/colors/font_color = Color(0.9, 0.9, 0.9, 1)
MenuButton/colors/font_disabled_color = Color(0.5, 0.5, 0.5, 1)
MenuButton/colors/font_hover_color = Color(1, 1, 1, 1)
MenuButton/colors/font_pressed_color = Color(0.8, 0.8, 0.8, 1)
MenuButton/font_sizes/font_size = 11
MenuButton/styles/hover = ExtResource("1_nubd1")
MenuButton/styles/normal = ExtResource("2_rapoa")
MenuButton/styles/pressed = ExtResource("2_rapoa")
OptionButton/colors/font_color = Color(0.9, 0.9, 0.9, 1)
OptionButton/colors/font_disabled_color = Color(0.5, 0.5, 0.5, 1)
OptionButton/colors/font_hover_color = Color(1, 1, 1, 1)
OptionButton/colors/font_pressed_color = Color(0.8, 0.8, 0.8, 1)
OptionButton/font_sizes/font_size = 11
OptionButton/styles/disabled = ExtResource("3_f200k")
OptionButton/styles/hover = ExtResource("1_nubd1")
OptionButton/styles/normal = ExtResource("1_nubd1")
OptionButton/styles/pressed = ExtResource("1_nubd1")
Panel/styles/panel = SubResource("StyleBoxFlat_panel")
PopupMenu/colors/font_color = Color(0.9, 0.9, 0.9, 1)
PopupMenu/colors/font_hover_color = Color(1, 1, 1, 1)
PopupMenu/colors/font_separator_color = Color(0.5, 0.5, 0.5, 1)
PopupMenu/font_sizes/font_size = 11
PopupMenu/styles/hover = ExtResource("1_nubd1")
PopupMenu/styles/normal = ExtResource("1_nubd1")
PopupMenu/styles/panel = ExtResource("2_rapoa")
PopupPanel/styles/panel = ExtResource("2_rapoa")
RichTextLabel/colors/default_color = Color(0.9, 0.9, 0.9, 1)
RichTextLabel/colors/selection_color = Color(0.4, 0.6, 0.8, 0.4)
RichTextLabel/font_sizes/bold_font_size = 10
RichTextLabel/font_sizes/bold_italics_font_size = 10
RichTextLabel/font_sizes/italics_font_size = 10
RichTextLabel/font_sizes/mono_font_size = 9
RichTextLabel/font_sizes/normal_font_size = 10
RichTextLabel/styles/normal = SubResource("StyleBoxFlat_richtextlabel")
VBoxContainer/constants/separation = 2
Window/colors/title_color = Color(0.175119, 0.187776, 0.176406, 1)
Window/constants/close_h_offset = 18
Window/constants/close_v_offset = 20
Window/constants/title_height = 22
Window/font_sizes/title_font_size = 16
Window/fonts/title_font = SubResource("SystemFont_f200k")
Window/icons/close = ExtResource("1_rapoa")
Window/icons/close_pressed = ExtResource("1_rapoa")
Window/styles/embedded_border = ExtResource("3_f200k")
Window/styles/embedded_unfocused_border = ExtResource("3_f200k")
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

-40
View File
@@ -1,40 +0,0 @@
[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/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

-40
View File
@@ -1,40 +0,0 @@
[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/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

@@ -1,40 +0,0 @@
[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/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

@@ -1,40 +0,0 @@
[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/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

@@ -1,40 +0,0 @@
[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/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

@@ -1,40 +0,0 @@
[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/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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
@@ -1,45 +0,0 @@
Font: Patrick Hand SC
Source: https://fonts.google.com/specimen/Patrick+Hand+SC/license
Designed by Patrick Wagesreiter
License
Copyright (c) 2010-2012 Patrick Wagesreiter (mail@patrickwagesreiter.at)
This Font Software is licensed under the SIL Open Font License, Version 1.1 . This license is copied below, and is also available with a FAQ at: https://openfontlicense.org
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the copyright statement(s).
"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -1,36 +0,0 @@
[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
modulate_color_glyphs=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={}
@@ -1,30 +0,0 @@
Font: Coffee Soda
Source: https://www.creativefabrica.com/product/coffee-soda/ref/53/
--------- AUTHOR'S NOTE ---------
First of All,
Thank you for download my product.
This product 100% free for personal use & commercial use
File include :
1. Coffee Soda.ttf
2. Coffee Soda.otf
More Info :
khurasantype@gmail.com
For Donation:
paypal.me/khurasantype
My OnlineShop:
https://www.creativefabrica.com/ref/53/
My Portofolio:
https://www.behance.net/khurasan
My web:
www.khurasanstudio.com
www.mbkaos.com
Binary file not shown.
@@ -1,36 +0,0 @@
[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
modulate_color_glyphs=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={}
@@ -1,14 +0,0 @@
[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
@@ -1,95 +0,0 @@
[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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 685 KiB

@@ -1,40 +0,0 @@
[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/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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
@@ -1,28 +0,0 @@
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
View File
@@ -1,75 +0,0 @@
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
View File
@@ -1 +0,0 @@
uid://byj005gxsloio
-65
View File
@@ -1,65 +0,0 @@
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
View File
@@ -1 +0,0 @@
uid://dj1jdugf244ho
-21
View File
@@ -1,21 +0,0 @@
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.WARN and loggie.settings.print_warnings_to_console:
push_warning(LoggieTools.convert_string_to_format_mode(msg.string(), LoggieEnums.MsgFormatMode.PLAIN))
-1
View File
@@ -1 +0,0 @@
uid://cd82p0vffosco
-52
View File
@@ -1,52 +0,0 @@
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" : "│",
}
-310
View File
@@ -1,310 +0,0 @@
@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(3,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 msg_string] is a shortcut providing the string content of that message.
## [br][param result] describes the final result of the attempt to log that message.
signal log_attempted(msg : LoggieMsg, msg_string : 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()
## Stores a reference to each [LoggiePreset] that was created.
## The key is the ID (string) of the preset, and the value is the [LoggieMsg] that was saved as a preset.
var presets : Dictionary = {}
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():
apply_production_optimal_settings()
# 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
}))
## Gets a [LoggiePreset] message with the given ID, or creates one if it doesn't exist and returns it.
## The ID cannot be an empty string.
func preset(id : String) -> LoggiePreset:
if id.is_empty():
push_error("Attempt to create or get a LoggiePreset with an empty ID - not allowed.")
return null
if presets.has(id):
return presets[id]
else:
var newPreset : LoggiePreset = LoggiePreset.new()
newPreset.use_logger(self)
presets[id] = newPreset
return newPreset
## 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
## Changes the values of certain members of [member settings] to values which are optimal for
## use in production builds. Used when [member LoggieSettings.enforce_optimal_settings_in_release_build]
## needs to be enforced.
func apply_production_optimal_settings():
self.settings.msg_format_mode = LoggieEnums.MsgFormatMode.PLAIN
self.settings.box_characters_mode = LoggieEnums.BoxCharactersMode.COMPATIBLE
-1
View File
@@ -1 +0,0 @@
uid://bmh2auo806k0k
-566
View File
@@ -1,566 +0,0 @@
@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 = ""
## Stores the integer value of a [enum LoggieEnums.LogLevel], matching the log level
## this message was most recently outputted at. If value is `-1`, the message was never
## outputted yet.
var last_outputted_at_log_level : int = -1
## Whether this message should append the stack trace during preprocessing.
var appends_stack : bool = false
## Controls whether the [enum LoggieEnums.MsgType] of this message, during [method output],
## will be decided dynamically, based on the [enum LoggieEnums.LogLevel] the message is being output at.
## If disabled, the [member strict_type] will be used instead.
var dynamic_type : bool = true
## The [LoggieEnums.MsgType] this message will be considered typed as, if [member dynamic_type] is set
## to [param false].
var strict_type : LoggieEnums.MsgType = LoggieEnums.MsgType.INFO
## The environment in which this message should be outputted based on the context the output is being requested from.
## (e.g. only in-engine (during tool scripts), or only ingame, or both).
var environment_mode : LoggieEnums.MsgEnvironment = LoggieEnums.MsgEnvironment.BOTH
## If set to true, the [signal Loggie.log_attempted] signal will not be emitted when this message attempts to be outputted.
var dont_emit_log_attempted_signal : 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
if converter_fn != LoggieTools.convert_to_string:
self.content[0] = LoggieTools.concatenate_args(initial_args, converter_fn)
self.remove_meta("initial_args") # Clean up.
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) -> LoggieMsg:
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 type] 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, type : LoggieEnums.MsgType) -> String:
var loggie = get_logger()
var message = self.string()
match type:
LoggieEnums.MsgType.ERROR:
message = loggie.settings.format_error_msg.format({"msg": message})
LoggieEnums.MsgType.WARN:
message = loggie.settings.format_warning_msg.format({"msg": message})
LoggieEnums.MsgType.NOTICE:
message = loggie.settings.format_notice_msg.format({"msg": message})
LoggieEnums.MsgType.INFO:
message = loggie.settings.format_info_msg.format({"msg": message})
LoggieEnums.MsgType.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 type == LoggieEnums.MsgType.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 message type can be provided via [param msg_type], but it will be ignored if [member dynamic_type] is false,
## in which case, the [member strict_type] will be used.
## 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.INFO) -> 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:
_emit_log_attempted_signal(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):
_emit_log_attempted_signal(LoggieEnums.LogAttemptResult.DOMAIN_DISABLED)
return
# We don't output the message if the environment the output is being requested from is not compatible with
# the environment this message is configured to be outputted in.
match environment_mode:
LoggieEnums.MsgEnvironment.ENGINE:
if not Engine.is_editor_hint():
_emit_log_attempted_signal(LoggieEnums.LogAttemptResult.WRONG_ENVIRONMENT)
return
LoggieEnums.MsgEnvironment.RUNTIME:
if Engine.is_editor_hint():
_emit_log_attempted_signal(LoggieEnums.LogAttemptResult.WRONG_ENVIRONMENT)
return
# Enforce the strict type of this message if it is configured not to allow dynamic type.
if !dynamic_type:
msg_type = strict_type
# 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:
_emit_log_attempted_signal(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, msg_type)
else:
self.last_preprocess_result = self.string()
self.last_outputted_at_log_level = int(level)
channel.send(self, msg_type)
# Emit signal deferred so if this is called from a thread, it doesn't cry about it.
_emit_log_attempted_signal(LoggieEnums.LogAttemptResult.SUCCESS, true)
## 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.WARN)
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, LoggieEnums.MsgType.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, LoggieEnums.MsgType.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
## Makes the current segment of this message a hyperlink to the given [param url].
## Optionally, you can provide a [param color], as the same type of parameter that [method color] uses,
## to colorize the link. This is a shortcut for additionally calling [method color] on the link.
## [br][br]If the link doesn't work when rendered inside of a [RichTextLabel], you may need to connect to the [signal RichTextLabel.meta_clicked] signal,
## and handle the opening of the link there. Read the docs of that signal for more info.
## [br][br][WARNING]: Appending URLs to messages is potentially dangerous.
## Please read this article for more info: https://docs.godotengine.org/en/latest/tutorials/ui/bbcode_in_richtextlabel.html#handling-user-input-safely
func link(url : String, _color : Variant = null) -> LoggieMsg:
self.content[current_segment_index] = "[url={url}]{msg}[/url]".format({"url": url, "msg": self.content[current_segment_index]})
if _color:
self.color(_color)
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) -> LoggieMsg:
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
## Sets the environment in which this message should be outputted based on the context the output is being requested from.
## (e.g. only in-engine (during tool scripts), or only ingame, or both).
func env(mode : LoggieEnums.MsgEnvironment) -> LoggieMsg:
self.environment_mode = mode
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
## Sets this message's [LoggieEnums.MsgType] to be strictly the provided value,
## instead of dynamically decided by the log level at which it is being outputted.
## [br][param loggie_enums_msgtype_key_or_value] should either be provided as a direct value of the enum,
## (e.g. [enum LoggieEnum.MsgType.INFO]) or as a string matching one of the keys in that enum (e.g.
## `"info"` or `"INFO"`, case-insensitive).
func type(loggie_enums_msgtype_key_or_value : Variant) -> LoggieMsg:
var isValid = false
var _type : LoggieEnums.MsgType
if loggie_enums_msgtype_key_or_value is LoggieEnums.MsgType:
_type = loggie_enums_msgtype_key_or_value
isValid = true
elif loggie_enums_msgtype_key_or_value is String:
var uppercase_key = loggie_enums_msgtype_key_or_value.to_upper()
if LoggieEnums.MsgType.keys().has(uppercase_key):
_type = LoggieEnums.MsgType[uppercase_key]
isValid = true
if isValid:
dynamic_type = false
strict_type = _type
else:
push_error("Attempt to set LoggieMsg type to {value} - could not be converted to a proper [LoggieEnums.MsgType]. Either provide a [LoggieEnums.MsgType], or a string that matches a key in that enum (case-insensitive).".format({"value": str(loggie_enums_msgtype_key_or_value)}))
return self
## If [param enabled], the [signal Loggie.log_attempted] signal will not be emitted when this message attempts to be outputted.
func no_signal(enabled : bool = true) -> LoggieMsg:
dont_emit_log_attempted_signal = enabled
return self
## Applies the [LoggiePreset] with the given [param id] to this message.
## [br]If [param apply_only_to_current_segment] is [param true], the styles from the preset will only be
## applied to the current content segment of this message. Otherwise, the entire content of this message
## will be collapsed into a single segment, and the styles will be applied to that.
func preset(id : String, apply_only_to_current_segment : bool = false) -> LoggieMsg:
var loggie = get_logger()
var preset_to_use : LoggiePreset = loggie.preset(id)
if preset_to_use:
preset_to_use.apply_to(self, apply_only_to_current_segment)
else:
push_error("Attempt to obtain LoggiePreset with ID {id} returned null. Something went terribly wrong, as this should usually be impossible.")
return self
## Internal method. Emits the [signal Loggie.log_attempted] signal (unless that feature is disabled).
## Used during [method output]. If [param call_deferred] is true, the string of the message's content will
## be prepared immediately, but the emission of the signal will be deferred.
func _emit_log_attempted_signal(result : LoggieEnums.LogAttemptResult, call_deferred : bool = false) -> void:
if dont_emit_log_attempted_signal:
return
var loggie = get_logger()
var string_content = self.string()
if call_deferred:
loggie.call_deferred("emit_signal", "log_attempted", self, string_content, result)
else:
loggie.log_attempted.emit(self, string_content, result)
## 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]
# Add the millisecond
var unix_time: float = Time.get_unix_time_from_system()
var millisecond: int = int((unix_time - int(unix_time)) * 1000.0)
format_dict["millisecond"] = "%03d" % millisecond
# Add the startup time to the format dictionary.
var elapsed_millisecond: int = Time.get_ticks_msec()
var startup_hour: int = elapsed_millisecond / 3_600_000
var startup_minute: int = (elapsed_millisecond % 3_600_000) / 60_000
var startup_second: int = (elapsed_millisecond % 60_000) / 1000
var startup_millisecond: int = elapsed_millisecond % 1000
format_dict["startup_hour"] = "%02d" % startup_hour
format_dict["startup_minute"] = "%02d" % startup_minute
format_dict["startup_second"] = "%02d" % startup_second
format_dict["startup_millisecond"] = "%03d" % startup_millisecond
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
View File
@@ -1 +0,0 @@
uid://bd3hgca3nslui
-24
View File
@@ -1,24 +0,0 @@
@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 +0,0 @@
uid://bunns8okgi81c
-83
View File
@@ -1,83 +0,0 @@
## An extension of the [LoggieMsg] class, meant to clearly differentiate between a message
## that was created with the sole purpose of being used as a Preset.
## Future behaviors of the message may diverge from regular behaviors of LoggieMsg, hence
## being separated into a different class.
class_name LoggiePreset extends LoggieMsg
## A placeholder that will be placed into the content of this preset as soon as it's initialized.
## This placeholder then gets wrapped with various styles as you call methods (e.g. [method bold],
## [method italic], etc.) on the preset. Later, only this string part of the content
## gets replaced by the actual content of a message this preset is being applied to, keeping the
## wrapped styles around it.
const content_placeholder = "{content}"
func _init(message = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> void:
self.content = [content_placeholder]
## Applies the settings and styles from this preset onto the given [LoggieMsg], modifying that message
## (does not create a duplicate) and returning that same message after it has been modified.
##
## [br][br]If any styles are applied to this [LoggiePreset], the entire content of the given [LoggieMsg] will
## be collapsed into a single segment, then have this preset's styles wrapped around that segment.
## [b]This directly resizes the [member LoggieMsg.content] array on the modified message.[/b]
##
## [br][br]However, if [param only_to_current_segment] is true, only the [member LoggieMsg.content] of
## the message found at its [member LoggieMsg.current_segment_index] will be wrapped in this preset's styles and modified,
## hence no resizing of the [member LoggieMsg.content] array will occur.
##
## [br]Neither of the above described actions will occur if this preset's content was not modified by any styles in any way.
func apply_to(msg : LoggieMsg, only_to_current_segment : bool = false) -> LoggieMsg:
if msg is LoggiePreset:
push_error("Attempt to apply a [LoggiePreset] to another [LoggiePreset] - unsupported behavior.")
return
# Apply the settings from this setting on the given message.
msg.domain_name = self.domain_name
msg.used_channels = self.used_channels.duplicate()
msg.preprocess = self.preprocess
msg.custom_preprocess_flags = self.custom_preprocess_flags
msg.appends_stack = self.appends_stack
msg.dynamic_type = self.dynamic_type
msg.strict_type = self.strict_type
msg.environment_mode = self.environment_mode
msg.dont_emit_log_attempted_signal = self.dont_emit_log_attempted_signal
# If no modifications were done to this preset's content (no styles applied),
# we don't need to go further.
if self.content[0] == content_placeholder:
return msg
# Otherwise,
# Based on the value of [param only_to_current_segment], either:
# 1. (if true): Replace the content of the [param msg] on its current segment to be that same content, but wrapped in the styles of this preset.
# 2. (if false): Smush the entire content of the [param msg] into a single segment, then wrap that segment in the styles of this preset.
var content_to_apply_preset_to : String
if only_to_current_segment:
content_to_apply_preset_to = msg.string(msg.current_segment_index)
var new_segment_content = self.content[0].format({"content": content_to_apply_preset_to})
msg.content[msg.current_segment_index] = new_segment_content
else:
content_to_apply_preset_to = msg.string()
var new_message_content = self.content[0].format({"content": content_to_apply_preset_to})
msg.content = [new_message_content]
return msg
## Overrides the parent class method by the same name to disable its functionality.
## Presets cannot have additional segments added.
func endseg() -> LoggieMsg:
push_warning("Attempt to call LoggieMsg.endseg on a LoggiePreset. This is not allowed. Presets shouldn't have more than 1 string content segment.")
return self
## Overrides the parent class method by the same name to disable its functionality.
## Presets cannot have their content modified.
func msg(message = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg:
push_warning("Attempt to call LoggieMsg.msg on a LoggiePreset. This is not allowed. Presets cannot have string content.")
return self
## Overrides the parent class method by the same name to disable its functionality.
## Presets cannot be outputted, they are only meant to be applied to other [LoggieMsg]s.
func output(level : LoggieEnums.LogLevel, msg_type : LoggieEnums.MsgType = LoggieEnums.MsgType.INFO) -> void:
push_warning("Attempt to call LoggieMsg.output on a LoggiePreset. This is not allowed. Presets should only be applied to other [LoggieMsg] instances, not used directly.")
return
-1
View File
@@ -1 +0,0 @@
uid://b2oby5841g2bw
-521
View File
@@ -1,521 +0,0 @@
@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
View File
@@ -1 +0,0 @@
uid://cs4ybo1axktlw
-7
View File
@@ -1,7 +0,0 @@
[plugin]
name="Loggie"
description="Simple functional stylish logger for your basic logging needs."
author="Shiva Shadowsong"
version="3.0"
script="plugin.gd"
-50
View File
@@ -1,50 +0,0 @@
@tool
class_name LoggieEditorPlugin extends EditorPlugin
func _enter_tree():
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_autoload_singleton(LoggieSettings.loggie_singleton_name, "res://addons/loggie/loggie.gd")
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
View File
@@ -1 +0,0 @@
uid://ct2hpdyg7gd81

Some files were not shown because too many files have changed in this diff Show More