initial commit

This commit is contained in:
2026-07-04 18:22:23 -06:00
commit 55522fc6fc
272 changed files with 12391 additions and 0 deletions
@@ -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