122 lines
5.2 KiB
Python
122 lines
5.2 KiB
Python
import dearpygui.dearpygui as dpg
|
|
import os
|
|
import json
|
|
import logging
|
|
import importlib
|
|
import inspect
|
|
from collections import deque
|
|
|
|
from widgets.base_widget import BaseWidget
|
|
|
|
class DpgLogHandler(logging.Handler):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.log_queue = deque(maxlen=200)
|
|
def emit(self, record):
|
|
msg = self.format(record)
|
|
self.log_queue.append(msg)
|
|
print(msg)
|
|
def get_all_logs(self):
|
|
return "\n".join(self.log_queue)
|
|
|
|
log_handler = DpgLogHandler()
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[log_handler])
|
|
|
|
INI_PATH = "negstation_layout.ini"
|
|
WIDGET_DATA_PATH = "negstation_widgets.json"
|
|
|
|
class LayoutManager:
|
|
def __init__(self):
|
|
self.active_widgets = {}
|
|
self.widget_classes = {}
|
|
|
|
def discover_and_register_widgets(self, directory="widgets"):
|
|
"""Dynamically discovers and registers widgets from a given directory."""
|
|
logging.info(f"Discovering widgets in '{directory}' directory...")
|
|
for filename in os.listdir(directory):
|
|
if filename.endswith(".py") and not filename.startswith("__"):
|
|
module_name = f"{directory}.{filename[:-3]}"
|
|
try:
|
|
# Dynamically import the module
|
|
module = importlib.import_module(module_name)
|
|
|
|
# Find all classes in the module that are subclasses of BaseWidget
|
|
for name, cls in inspect.getmembers(module, inspect.isclass):
|
|
if issubclass(cls, BaseWidget) and cls is not BaseWidget:
|
|
logging.info(f" -> Found and registered widget: {name}")
|
|
self.register_widget(name, cls)
|
|
except ImportError as e:
|
|
logging.error(f"Failed to import widget module {module_name}: {e}")
|
|
|
|
def register_widget(self, name: str, widget_class: object):
|
|
"""Adds a widget class to the registry."""
|
|
if name in self.widget_classes:
|
|
logging.warning(f"Widget '{name}' is already registered. Overwriting.")
|
|
self.widget_classes[name] = widget_class
|
|
|
|
def add_widget(self, widget_type: str):
|
|
if widget_type not in self.widget_classes: logging.error(f"Unknown widget type '{widget_type}'"); return
|
|
if widget_type in self.active_widgets:
|
|
widget_tag = self.active_widgets[widget_type].window_tag
|
|
if dpg.does_item_exist(widget_tag):
|
|
logging.info(f"Showing existing widget: {widget_type}"); dpg.configure_item(widget_tag, show=True); dpg.focus_item(widget_tag)
|
|
return
|
|
config = {"label": widget_type}
|
|
WidgetClass = self.widget_classes[widget_type]
|
|
widget_instance = WidgetClass(widget_type, config, self)
|
|
logging.info(f"Creating new widget of type: {widget_type}")
|
|
self.active_widgets[widget_type] = widget_instance
|
|
widget_instance.create()
|
|
|
|
def save_layout(self):
|
|
logging.info("Saving layout..."); dpg.save_init_file(INI_PATH)
|
|
widget_data = [{"widget_type": w_type, "config": w.get_config()} for w_type, w in self.active_widgets.items()]
|
|
with open(WIDGET_DATA_PATH, 'w') as f: json.dump(widget_data, f, indent=4)
|
|
logging.info("Layout saved successfully.")
|
|
|
|
def load_layout(self):
|
|
logging.info("Loading layout...");
|
|
if not os.path.exists(WIDGET_DATA_PATH): return
|
|
with open(WIDGET_DATA_PATH, 'r') as f: widget_data = json.load(f)
|
|
for data in widget_data:
|
|
if data.get("widget_type") in self.widget_classes: self.add_widget(widget_type=data.get("widget_type"))
|
|
if os.path.exists(INI_PATH): dpg.configure_app(init_file=INI_PATH); logging.info(f"Applied UI layout from {INI_PATH}")
|
|
|
|
def update_all_widgets(self):
|
|
"""Calls per-frame update methods on widgets that need it."""
|
|
if "LogWidget" in self.active_widgets:
|
|
# We need to pass the handler to the update method
|
|
self.active_widgets["LogWidget"].update_logs(log_handler)
|
|
|
|
@staticmethod
|
|
def run():
|
|
dpg.create_context()
|
|
dpg.create_viewport(title='Dynamic Docking Layout with Menu', width=1280, height=720)
|
|
|
|
layout_manager = LayoutManager()
|
|
layout_manager.discover_and_register_widgets()
|
|
|
|
with dpg.viewport_menu_bar():
|
|
with dpg.menu(label="File"):
|
|
dpg.add_menu_item(label="Save Layout", callback=layout_manager.save_layout)
|
|
|
|
with dpg.menu(label="View"):
|
|
for widget_name in sorted(layout_manager.widget_classes.keys()):
|
|
dpg.add_menu_item(
|
|
label=f"Show {widget_name}",
|
|
callback=lambda s, a, ud: layout_manager.add_widget(ud),
|
|
user_data=widget_name
|
|
)
|
|
|
|
dpg.configure_app(docking=True, docking_space=True)
|
|
dpg.setup_dearpygui()
|
|
|
|
layout_manager.load_layout()
|
|
|
|
dpg.show_viewport()
|
|
|
|
while dpg.is_dearpygui_running():
|
|
layout_manager.update_all_widgets()
|
|
dpg.render_dearpygui_frame()
|
|
|
|
dpg.destroy_context() |