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