initial commit
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user