initial commit
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 167 B |
@@ -0,0 +1,40 @@
|
||||
[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.
|
After Width: | Height: | Size: 167 B |
@@ -0,0 +1,40 @@
|
||||
[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.
|
After Width: | Height: | Size: 166 B |
@@ -0,0 +1,40 @@
|
||||
[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.
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,40 @@
|
||||
[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
|
||||
@@ -0,0 +1,34 @@
|
||||
[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
|
||||
@@ -0,0 +1,17 @@
|
||||
[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)
|
||||
@@ -0,0 +1,16 @@
|
||||
[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.
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,40 @@
|
||||
[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
|
||||
@@ -0,0 +1,13 @@
|
||||
[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)
|
||||
@@ -0,0 +1,21 @@
|
||||
[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)
|
||||
@@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="Loggie Console"
|
||||
description="Interactive debug console built upon the Loggie logging framework"
|
||||
author="Smitner Studio"
|
||||
version="1.0"
|
||||
script="plugin.gd"
|
||||
@@ -0,0 +1,16 @@
|
||||
@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)
|
||||
@@ -0,0 +1 @@
|
||||
uid://b8ul8pbaeqx2l
|
||||
@@ -0,0 +1,55 @@
|
||||
@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 = ""
|
||||
@@ -0,0 +1 @@
|
||||
uid://b6iuus8kvu3y3
|
||||
@@ -0,0 +1,294 @@
|
||||
@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
|
||||
@@ -0,0 +1 @@
|
||||
uid://w2lnu7heu47v
|
||||
@@ -0,0 +1,563 @@
|
||||
## 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"
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://64r2in67soep
|
||||
@@ -0,0 +1,164 @@
|
||||
[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")]
|
||||
@@ -0,0 +1,55 @@
|
||||
[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"
|
||||
@@ -0,0 +1,86 @@
|
||||
[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
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
[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 "
|
||||
@@ -0,0 +1,90 @@
|
||||
[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"
|
||||
@@ -0,0 +1,104 @@
|
||||
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)
|
||||
@@ -0,0 +1 @@
|
||||
uid://c4f6c3gs8venj
|
||||
@@ -0,0 +1,166 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
uid://dgkxvw0v5n6hq
|
||||
@@ -0,0 +1,256 @@
|
||||
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()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bq6xlpjg4qoca
|
||||
@@ -0,0 +1,209 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
uid://wah8efffydnu
|
||||
@@ -0,0 +1,62 @@
|
||||
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())
|
||||
@@ -0,0 +1 @@
|
||||
uid://d3l2tmf5gym1d
|
||||
@@ -0,0 +1,381 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
uid://bfmpjs4x3fhte
|
||||
@@ -0,0 +1,123 @@
|
||||
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()
|
||||
@@ -0,0 +1 @@
|
||||
uid://dmyd1gquxy42u
|
||||
@@ -0,0 +1,125 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
uid://giuyu6vhhgam
|
||||
@@ -0,0 +1,55 @@
|
||||
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"
|
||||
@@ -0,0 +1 @@
|
||||
uid://whrim6lbiako
|
||||
@@ -0,0 +1,167 @@
|
||||
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)
|
||||
@@ -0,0 +1 @@
|
||||
uid://x0ut5e0gpyu1
|
||||
@@ -0,0 +1,66 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
uid://bcehgenffbmr3
|
||||
@@ -0,0 +1,9 @@
|
||||
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"
|
||||
@@ -0,0 +1 @@
|
||||
uid://7iexr4yxwqfg
|
||||
@@ -0,0 +1,96 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
uid://bntgmwyauc4h6
|
||||
@@ -0,0 +1,141 @@
|
||||
[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")
|
||||
Reference in New Issue
Block a user