clean up imports and switch to absolute imports
This commit is contained in:
1 parent
fb8f4dc1d7
commit
dbb2146b2f
27 files changed
+82
-731
No files matched your search
+7
-1
@@ -69,6 +69,10 @@ instance/
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
#vscode settings
|
||||
.vscode/
|
||||
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
@@ -174,6 +178,8 @@ cython_debug/
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
prompt.md
|
||||
|
||||
Powered by Gitea
|
||||
Version:
|
||||
1.24.4
|
||||
@@ -182,4 +188,4 @@ Page:
|
||||
Template:
|
||||
3ms
|
||||
Licenses
|
||||
API
|
||||
API
|
||||
@@ -1,3 +1,15 @@
|
||||
# synodetik
|
||||
|
||||
Modular audiovisual synoptic node editor.
|
||||
Modular audiovisual synoptic node editor.
|
||||
|
||||
## installation
|
||||
|
||||
Just git clone the project. Open a terminal in the cloned folder.
|
||||
|
||||
``` shell
|
||||
|
||||
|
||||
|
||||
```
|
||||
|
||||
## module development
|
||||
@@ -4,7 +4,7 @@ Synodetik - Video/Audio synoptic editor.
|
||||
A node-based visual editor for designing and documenting
|
||||
signal routing in professional AV environments.
|
||||
"""
|
||||
from .core import (
|
||||
from synodetik.core import (
|
||||
# Models
|
||||
FluxCategory, create_flux_category,
|
||||
Flux, create_flux_type,
|
||||
@@ -22,11 +22,9 @@ from .core import (
|
||||
# Events
|
||||
EventType, Event, EventDispatcher,
|
||||
get_dispatcher,
|
||||
|
||||
|
||||
)
|
||||
|
||||
from .modules import (
|
||||
from synodetik.modules import (
|
||||
Module, ModuleInfo, MenuItem, ModuleError,
|
||||
ModuleManager
|
||||
)
|
||||
@@ -53,9 +51,6 @@ __all__ = [
|
||||
'EventType', 'Event', 'EventDispatcher',
|
||||
'get_dispatcher',
|
||||
|
||||
# Registry
|
||||
'Registry', 'get_registry',
|
||||
|
||||
# Modules
|
||||
'Module', 'ModuleInfo', 'MenuItem', 'ModuleError',
|
||||
'ModuleManager',
|
||||
|
||||
@@ -15,27 +15,25 @@ Library:
|
||||
Services:
|
||||
- ConfigManager: Display and editor settings
|
||||
- EventDispatcher: Inter-module communication
|
||||
- Registry: Service locator
|
||||
"""
|
||||
# Models
|
||||
from .model import (
|
||||
from synodetik.core.model import (
|
||||
FluxCategory, create_flux_category,
|
||||
Flux, create_flux_type,
|
||||
Machine, create_machine
|
||||
)
|
||||
from .port import Port, PortDirection, create_input, create_output, create_full_duplex
|
||||
from .connection import Connection
|
||||
from .synoptik import Synoptik
|
||||
from synodetik.core.port import Port, PortDirection, create_input, create_output, create_full_duplex
|
||||
from synodetik.core.connection import Connection
|
||||
from synodetik.core.synoptik import Synoptik
|
||||
|
||||
# Library
|
||||
from .library import Library, get_library
|
||||
from synodetik.core.library import Library, get_library
|
||||
|
||||
# Config
|
||||
from .config import ConfigManager, AppConfig, get_config_manager
|
||||
from synodetik.core.config import ConfigManager, AppConfig, get_config_manager
|
||||
|
||||
# Events
|
||||
from .events import EventDispatcher, EventType, Event, get_dispatcher
|
||||
|
||||
from synodetik.core.events import EventDispatcher, EventType, Event, get_dispatcher
|
||||
|
||||
|
||||
__all__ = [
|
||||
@@ -54,7 +52,4 @@ __all__ = [
|
||||
|
||||
# Events
|
||||
"EventDispatcher", "EventType", "Event", "get_dispatcher",
|
||||
|
||||
# Registry
|
||||
"Registry", "get_registry",
|
||||
]
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Event system for decoupled component communication.
|
||||
"""
|
||||
from .dispatcher import (
|
||||
from synodetik.core.events.dispatcher import (
|
||||
EventType,
|
||||
Event,
|
||||
EventHandler,
|
||||
|
||||
@@ -7,7 +7,7 @@ All items are stored as JSON files in ~/.synodetik/
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from .model import Machine, Flux, FluxCategory
|
||||
from synodetik.core.model import Machine, Flux, FluxCategory
|
||||
|
||||
|
||||
class Library:
|
||||
|
||||
@@ -6,9 +6,9 @@ Provides the main domain objects:
|
||||
- Flux: Signal/connection type definition
|
||||
- Machine: Equipment with input/output ports
|
||||
"""
|
||||
from .flux_category import FluxCategory, create_flux_category
|
||||
from .flux import Flux, create_flux_type
|
||||
from .machine import Machine, create_machine
|
||||
from synodetik.core.model.flux_category import FluxCategory, create_flux_category
|
||||
from synodetik.core.model.flux import Flux, create_flux_type
|
||||
from synodetik.core.model.machine import Machine, create_machine
|
||||
|
||||
__all__ = [
|
||||
"FluxCategory", "create_flux_category",
|
||||
|
||||
@@ -11,8 +11,8 @@ import uuid
|
||||
import json
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..port import Port
|
||||
from .flux import Flux
|
||||
from synodetik.core.port import Port
|
||||
from synodetik.core.model.flux import Flux
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -58,7 +58,7 @@ class Machine:
|
||||
|
||||
def add_input(self, name: str, flux_type: "Flux") -> "Port":
|
||||
"""Add an input port and return it."""
|
||||
from ..port import create_input
|
||||
from synodetik.core.port import create_input
|
||||
port = create_input(name, flux_type, index=len(self.inputs))
|
||||
port.machine_id = self.id
|
||||
self.inputs.append(port)
|
||||
@@ -66,7 +66,7 @@ class Machine:
|
||||
|
||||
def add_output(self, name: str, flux_type: "Flux") -> "Port":
|
||||
"""Add an output port and return it."""
|
||||
from ..port import create_output
|
||||
from synodetik.core.port import create_output
|
||||
port = create_output(name, flux_type, index=len(self.outputs))
|
||||
port.machine_id = self.id
|
||||
self.outputs.append(port)
|
||||
@@ -133,7 +133,7 @@ class Machine:
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "Machine":
|
||||
"""Deserialize from dictionary."""
|
||||
from ..port import Port
|
||||
from synodetik.core.port import Port
|
||||
|
||||
machine = cls(
|
||||
id=data.get("id", str(uuid.uuid4())),
|
||||
|
||||
@@ -10,7 +10,7 @@ from typing import Optional, TYPE_CHECKING
|
||||
import uuid
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .model.flux import Flux
|
||||
from synodetik.core.model.flux import Flux
|
||||
|
||||
|
||||
class PortDirection(Enum):
|
||||
@@ -69,7 +69,7 @@ class Port:
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "Port":
|
||||
"""Deserialize from dictionary."""
|
||||
from .model.flux import Flux
|
||||
from synodetik.core.model.flux import Flux
|
||||
|
||||
return cls(
|
||||
id=data.get("id", str(uuid.uuid4())),
|
||||
|
||||
@@ -9,10 +9,10 @@ from typing import Optional
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
|
||||
from .model.machine import Machine
|
||||
from .connection import Connection
|
||||
from .port import Port
|
||||
from .model.flux import Flux
|
||||
from synodetik.core.model.machine import Machine
|
||||
from synodetik.core.connection import Connection
|
||||
from synodetik.core.port import Port
|
||||
from synodetik.core.model.flux import Flux
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ This module re-exports the event system from core.events
|
||||
to maintain backward compatibility with imports like:
|
||||
from synodetik.events import EventType, get_dispatcher
|
||||
"""
|
||||
from .core.events import (
|
||||
from synodetik.core.events import (
|
||||
EventType,
|
||||
Event,
|
||||
EventHandler,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
"""
|
||||
GUI module - DearPyGui interface components.
|
||||
"""
|
||||
from .app import SynoptikApp, create_app
|
||||
from .themes import (
|
||||
from synodetik.gui.app import SynoptikApp, create_app
|
||||
from synodetik.gui.themes import (
|
||||
DarkTheme, LightTheme, CATEGORY_COLORS,
|
||||
hex_to_rgba, color_with_opacity
|
||||
)
|
||||
from .components import PalettePanel, PropertiesPanel, NodeEditorPanel
|
||||
from .dialogs import (
|
||||
from synodetik.gui.components import PalettePanel, PropertiesPanel, NodeEditorPanel
|
||||
from synodetik.gui.dialogs import (
|
||||
MachineEditor,
|
||||
FluxEditor, FluxManager, CategoryEditor
|
||||
)
|
||||
|
||||
@@ -8,19 +8,19 @@ import dearpygui.dearpygui as dpg
|
||||
from pathlib import Path
|
||||
from typing import Optional, List
|
||||
|
||||
from ..core import (
|
||||
from synodetik.core import (
|
||||
Synoptik, Machine,
|
||||
Library, get_library,
|
||||
get_config_manager
|
||||
)
|
||||
from ..utils import serialization
|
||||
from ..events import EventType, get_dispatcher
|
||||
from ..modules import ModuleManager
|
||||
from synodetik.utils import serialization
|
||||
from synodetik.events import EventType, get_dispatcher
|
||||
from synodetik.modules import ModuleManager
|
||||
from synodetik.modules.base import MenuItem
|
||||
|
||||
from .themes import DarkTheme, hex_to_rgba
|
||||
from .components import PalettePanel, PropertiesPanel, NodeEditorPanel
|
||||
from .dialogs import MachineEditor, FluxManager, CategoryEditor
|
||||
from synodetik.gui.themes import DarkTheme, hex_to_rgba
|
||||
from synodetik.gui.components import PalettePanel, PropertiesPanel, NodeEditorPanel
|
||||
from synodetik.gui.dialogs import MachineEditor, FluxManager, CategoryEditor
|
||||
|
||||
|
||||
class SynoptikApp:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""
|
||||
GUI components package.
|
||||
"""
|
||||
from .palette import PalettePanel
|
||||
from .properties import PropertiesPanel
|
||||
from .node_editor import NodeEditorPanel
|
||||
from synodetik.gui.components.palette import PalettePanel
|
||||
from synodetik.gui.components.properties import PropertiesPanel
|
||||
from synodetik.gui.components.node_editor import NodeEditorPanel
|
||||
|
||||
__all__ = ['PalettePanel', 'PropertiesPanel', 'NodeEditorPanel']
|
||||
@@ -7,11 +7,11 @@ connections are drawn as links between ports.
|
||||
import dearpygui.dearpygui as dpg
|
||||
from typing import TYPE_CHECKING, Optional, Callable
|
||||
|
||||
from ..themes import DarkTheme, hex_to_rgba, color_with_opacity
|
||||
from synodetik.gui.themes import DarkTheme, hex_to_rgba, color_with_opacity
|
||||
from synodetik.events import get_dispatcher, EventType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...core import Machine, Synoptik, Connection
|
||||
from synodetik.core import Machine, Synoptik, Connection
|
||||
|
||||
|
||||
class NodeEditorPanel:
|
||||
|
||||
@@ -7,10 +7,10 @@ allowing users to add new machines to the synoptik.
|
||||
import dearpygui.dearpygui as dpg
|
||||
from typing import TYPE_CHECKING, Callable, Optional
|
||||
|
||||
from ..themes import DarkTheme, hex_to_rgba
|
||||
from synodetik.gui.themes import DarkTheme, hex_to_rgba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...core import Library
|
||||
from synodetik.core import Library
|
||||
|
||||
|
||||
class PalettePanel:
|
||||
|
||||
@@ -6,10 +6,10 @@ Displays and allows editing of selected machine/connection properties.
|
||||
import dearpygui.dearpygui as dpg
|
||||
from typing import TYPE_CHECKING, Optional, Callable
|
||||
|
||||
from ..themes import DarkTheme
|
||||
from synodetik.gui.themes import DarkTheme
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...core import Machine, Synoptik
|
||||
from synodetik.core import Machine, Synoptik
|
||||
|
||||
|
||||
class PropertiesPanel:
|
||||
|
||||
@@ -7,8 +7,8 @@ Provides editor dialogs for:
|
||||
- CategoryEditor: Manage flux categories
|
||||
- FluxManager: Browse and manage all flux types
|
||||
"""
|
||||
from .template_editor import MachineEditor
|
||||
from .flux_editor import FluxEditor, CategoryEditor, FluxManager
|
||||
from synodetik.gui.dialogs.template_editor import MachineEditor
|
||||
from synodetik.gui.dialogs.flux_editor import FluxEditor, CategoryEditor, FluxManager
|
||||
|
||||
__all__ = [
|
||||
'MachineEditor',
|
||||
|
||||
@@ -7,10 +7,10 @@ and their categories (video, audio, network, etc.).
|
||||
import dearpygui.dearpygui as dpg
|
||||
from typing import TYPE_CHECKING, Optional, Callable
|
||||
|
||||
from ..themes import DarkTheme, hex_to_rgba
|
||||
from synodetik.gui.themes import DarkTheme, hex_to_rgba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...core import Flux, FluxCategory, Library
|
||||
from synodetik.core import Flux, FluxCategory, Library
|
||||
|
||||
|
||||
class FluxEditor:
|
||||
@@ -130,7 +130,7 @@ class FluxEditor:
|
||||
|
||||
def _on_save(self, sender=None, app_data=None):
|
||||
"""Save the flux type."""
|
||||
from ...core import Flux
|
||||
from synodetik.core import Flux
|
||||
|
||||
name = dpg.get_value(f"{self.WINDOW_TAG}_name")
|
||||
if not name:
|
||||
@@ -269,7 +269,7 @@ class CategoryEditor:
|
||||
|
||||
def _on_add_category(self, sender=None, app_data=None):
|
||||
"""Add a new category."""
|
||||
from ...core import FluxCategory
|
||||
from synodetik.core import FluxCategory
|
||||
|
||||
cat_id = dpg.get_value(f"{self.WINDOW_TAG}_new_id")
|
||||
cat_name = dpg.get_value(f"{self.WINDOW_TAG}_new_name")
|
||||
|
||||
@@ -7,10 +7,10 @@ including port configuration and visual properties.
|
||||
import dearpygui.dearpygui as dpg
|
||||
from typing import TYPE_CHECKING, Optional, Callable
|
||||
|
||||
from ..themes import DarkTheme, hex_to_rgba
|
||||
from synodetik.gui.themes import DarkTheme, hex_to_rgba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...core import Machine, Library, Port
|
||||
from synodetik.core import Machine, Library, Port
|
||||
|
||||
|
||||
# Default machine categories
|
||||
@@ -209,7 +209,7 @@ class MachineEditor:
|
||||
|
||||
def _collect_ports(self, direction: str) -> list['Port']:
|
||||
"""Collect port data from the UI."""
|
||||
from ...core import create_input, create_output
|
||||
from synodetik.core import create_input, create_output
|
||||
|
||||
ports = []
|
||||
parent = f"{self.WINDOW_TAG}_{direction}s"
|
||||
@@ -239,7 +239,7 @@ class MachineEditor:
|
||||
|
||||
def _on_save(self, sender=None, app_data=None):
|
||||
"""Save the machine."""
|
||||
from ...core import Machine
|
||||
from synodetik.core import Machine
|
||||
|
||||
name = dpg.get_value(f"{self.WINDOW_TAG}_name")
|
||||
if not name:
|
||||
|
||||
@@ -1,407 +0,0 @@
|
||||
"""
|
||||
Flux and category editor windows.
|
||||
|
||||
Provides UI for managing flux types and categories.
|
||||
"""
|
||||
import dearpygui.dearpygui as dpg
|
||||
from typing import Optional, Callable
|
||||
|
||||
from ..core import FluxTemplate, FluxLibrary, get_config_manager, FluxCategoryConfig
|
||||
from .themes import DarkTheme, hex_to_rgba
|
||||
|
||||
|
||||
class FluxTemplateEditor:
|
||||
"""Editor window for flux templates."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
flux_library: FluxLibrary,
|
||||
on_save: Optional[Callable[[FluxTemplate], None]] = None,
|
||||
template: Optional[FluxTemplate] = None
|
||||
):
|
||||
self.flux_library = flux_library
|
||||
self.config = get_config_manager()
|
||||
self.on_save = on_save
|
||||
self.template = template
|
||||
self._window_tag = f"flux_editor_{id(self)}"
|
||||
|
||||
def show(self):
|
||||
"""Display the editor window."""
|
||||
if dpg.does_item_exist(self._window_tag):
|
||||
dpg.focus_item(self._window_tag)
|
||||
return
|
||||
|
||||
is_edit = self.template is not None
|
||||
title = "Edit Flux" if is_edit else "New Flux Type"
|
||||
|
||||
categories = self.config.get_category_names()
|
||||
category_ids = self.config.get_category_ids()
|
||||
|
||||
# Find current category index
|
||||
current_cat_idx = 0
|
||||
if is_edit and self.template.category_id in category_ids:
|
||||
current_cat_idx = category_ids.index(self.template.category_id)
|
||||
|
||||
with dpg.window(
|
||||
label=title,
|
||||
tag=self._window_tag,
|
||||
width=400,
|
||||
height=400,
|
||||
on_close=self._on_close,
|
||||
pos=(150, 150)
|
||||
):
|
||||
dpg.add_input_text(
|
||||
label="Name",
|
||||
tag=f"{self._window_tag}_name",
|
||||
default_value=self.template.name if is_edit else "",
|
||||
width=250
|
||||
)
|
||||
|
||||
dpg.add_combo(
|
||||
label="Category",
|
||||
tag=f"{self._window_tag}_category",
|
||||
items=categories,
|
||||
default_value=categories[current_cat_idx] if categories else "",
|
||||
width=200
|
||||
)
|
||||
|
||||
# Color picker
|
||||
default_color = hex_to_rgba(self.template.color if is_edit else "#FFFFFF")
|
||||
dpg.add_color_edit(
|
||||
label="Color",
|
||||
tag=f"{self._window_tag}_color",
|
||||
default_value=(
|
||||
default_color[0] / 255.0,
|
||||
default_color[1] / 255.0,
|
||||
default_color[2] / 255.0,
|
||||
1.0
|
||||
),
|
||||
no_alpha=True,
|
||||
width=200
|
||||
)
|
||||
|
||||
dpg.add_input_text(
|
||||
label="Connector Type",
|
||||
tag=f"{self._window_tag}_connector",
|
||||
default_value=self.template.connector_type if is_edit else "",
|
||||
width=200
|
||||
)
|
||||
|
||||
dpg.add_combo(
|
||||
label="Signal Type",
|
||||
tag=f"{self._window_tag}_signal",
|
||||
items=["digital", "analog", "hybrid"],
|
||||
default_value=self.template.signal_type if is_edit else "digital",
|
||||
width=150
|
||||
)
|
||||
|
||||
dpg.add_checkbox(
|
||||
label="Bidirectional",
|
||||
tag=f"{self._window_tag}_bidir",
|
||||
default_value=self.template.bidirectional if is_edit else False
|
||||
)
|
||||
|
||||
dpg.add_input_text(
|
||||
label="Description",
|
||||
tag=f"{self._window_tag}_desc",
|
||||
default_value=self.template.description if is_edit else "",
|
||||
multiline=True,
|
||||
height=60,
|
||||
width=350
|
||||
)
|
||||
|
||||
dpg.add_separator()
|
||||
|
||||
with dpg.group(horizontal=True):
|
||||
dpg.add_button(label="Save", callback=self._on_save, width=100)
|
||||
dpg.add_button(label="Cancel", callback=self._on_close, width=80)
|
||||
|
||||
def _on_save(self, sender=None, app_data=None):
|
||||
"""Save the flux template."""
|
||||
name = dpg.get_value(f"{self._window_tag}_name")
|
||||
if not name:
|
||||
return
|
||||
|
||||
# Get category
|
||||
cat_name = dpg.get_value(f"{self._window_tag}_category")
|
||||
category_ids = self.config.get_category_ids()
|
||||
category_names = self.config.get_category_names()
|
||||
cat_idx = category_names.index(cat_name) if cat_name in category_names else 0
|
||||
category_id = category_ids[cat_idx] if category_ids else "data"
|
||||
|
||||
# Get color
|
||||
color_value = dpg.get_value(f"{self._window_tag}_color")
|
||||
color_hex = (
|
||||
f"#{int(color_value[0]*255):02x}"
|
||||
f"{int(color_value[1]*255):02x}"
|
||||
f"{int(color_value[2]*255):02x}"
|
||||
)
|
||||
|
||||
if self.template:
|
||||
template = self.template
|
||||
template.name = name
|
||||
else:
|
||||
template = FluxTemplate(name=name, category_id=category_id)
|
||||
|
||||
template.category_id = category_id
|
||||
template.color = color_hex
|
||||
template.connector_type = dpg.get_value(f"{self._window_tag}_connector")
|
||||
template.signal_type = dpg.get_value(f"{self._window_tag}_signal")
|
||||
template.bidirectional = dpg.get_value(f"{self._window_tag}_bidir")
|
||||
template.description = dpg.get_value(f"{self._window_tag}_desc")
|
||||
|
||||
self.flux_library.save_template(template)
|
||||
|
||||
if self.on_save:
|
||||
self.on_save(template)
|
||||
|
||||
self._on_close()
|
||||
|
||||
def _on_close(self, sender=None, app_data=None):
|
||||
"""Close the window."""
|
||||
if dpg.does_item_exist(self._window_tag):
|
||||
dpg.delete_item(self._window_tag)
|
||||
|
||||
|
||||
class CategoryEditor:
|
||||
"""Editor window for flux categories."""
|
||||
|
||||
def __init__(self, on_save: Optional[Callable[[], None]] = None):
|
||||
self.config = get_config_manager()
|
||||
self.on_save = on_save
|
||||
self._window_tag = "category_editor"
|
||||
|
||||
def show(self):
|
||||
"""Display the editor window."""
|
||||
if dpg.does_item_exist(self._window_tag):
|
||||
dpg.focus_item(self._window_tag)
|
||||
return
|
||||
|
||||
with dpg.window(
|
||||
label="Manage Categories",
|
||||
tag=self._window_tag,
|
||||
width=450,
|
||||
height=400,
|
||||
on_close=self._on_close,
|
||||
pos=(200, 100)
|
||||
):
|
||||
dpg.add_text("Flux Categories:")
|
||||
dpg.add_separator()
|
||||
|
||||
with dpg.group(tag=f"{self._window_tag}_list"):
|
||||
self._refresh_list()
|
||||
|
||||
dpg.add_separator()
|
||||
dpg.add_text("New Category:", color=DarkTheme.TEXT_SECONDARY)
|
||||
|
||||
with dpg.group(horizontal=True):
|
||||
dpg.add_input_text(
|
||||
hint="ID (unique)",
|
||||
tag=f"{self._window_tag}_new_id",
|
||||
width=100
|
||||
)
|
||||
dpg.add_input_text(
|
||||
hint="Name",
|
||||
tag=f"{self._window_tag}_new_name",
|
||||
width=120
|
||||
)
|
||||
dpg.add_color_edit(
|
||||
tag=f"{self._window_tag}_new_color",
|
||||
default_value=(0.5, 0.5, 0.5, 1.0),
|
||||
no_alpha=True,
|
||||
no_inputs=True,
|
||||
width=50
|
||||
)
|
||||
dpg.add_button(label="Add", callback=self._on_add_category)
|
||||
|
||||
def _refresh_list(self):
|
||||
"""Refresh the category list."""
|
||||
parent = f"{self._window_tag}_list"
|
||||
if dpg.does_item_exist(parent):
|
||||
dpg.delete_item(parent, children_only=True)
|
||||
|
||||
for cat in self.config.get_categories():
|
||||
with dpg.group(horizontal=True, parent=parent):
|
||||
dpg.add_color_button(
|
||||
default_value=(
|
||||
*[c/255 for c in hex_to_rgba(cat.color)[:3]],
|
||||
1.0
|
||||
),
|
||||
width=20, height=20,
|
||||
no_border=True
|
||||
)
|
||||
dpg.add_text(f"{cat.name} ({cat.id})")
|
||||
dpg.add_button(
|
||||
label="Delete",
|
||||
callback=self._on_delete_category,
|
||||
user_data=cat.id,
|
||||
width=50
|
||||
)
|
||||
|
||||
def _on_add_category(self, sender=None, app_data=None):
|
||||
"""Add a new category."""
|
||||
cat_id = dpg.get_value(f"{self._window_tag}_new_id")
|
||||
cat_name = dpg.get_value(f"{self._window_tag}_new_name")
|
||||
|
||||
if not cat_id or not cat_name:
|
||||
return
|
||||
|
||||
color_value = dpg.get_value(f"{self._window_tag}_new_color")
|
||||
color_hex = (
|
||||
f"#{int(color_value[0]*255):02x}"
|
||||
f"{int(color_value[1]*255):02x}"
|
||||
f"{int(color_value[2]*255):02x}"
|
||||
)
|
||||
|
||||
category = FluxCategoryConfig(
|
||||
id=cat_id.lower().replace(" ", "_"),
|
||||
name=cat_name,
|
||||
color=color_hex
|
||||
)
|
||||
|
||||
self.config.add_category(category)
|
||||
self._refresh_list()
|
||||
|
||||
# Reset inputs
|
||||
dpg.set_value(f"{self._window_tag}_new_id", "")
|
||||
dpg.set_value(f"{self._window_tag}_new_name", "")
|
||||
|
||||
if self.on_save:
|
||||
self.on_save()
|
||||
|
||||
def _on_delete_category(self, sender, app_data, user_data):
|
||||
"""Delete a category."""
|
||||
self.config.remove_category(user_data)
|
||||
self._refresh_list()
|
||||
|
||||
if self.on_save:
|
||||
self.on_save()
|
||||
|
||||
def _on_close(self, sender=None, app_data=None):
|
||||
"""Close the window."""
|
||||
if dpg.does_item_exist(self._window_tag):
|
||||
dpg.delete_item(self._window_tag)
|
||||
|
||||
|
||||
class FluxManager:
|
||||
"""Manager window for flux types."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
flux_library: FluxLibrary,
|
||||
on_change: Optional[Callable[[], None]] = None
|
||||
):
|
||||
self.flux_library = flux_library
|
||||
self.config = get_config_manager()
|
||||
self.on_change = on_change
|
||||
self._window_tag = "flux_manager"
|
||||
|
||||
def show(self):
|
||||
"""Display the manager window."""
|
||||
if dpg.does_item_exist(self._window_tag):
|
||||
dpg.focus_item(self._window_tag)
|
||||
return
|
||||
|
||||
with dpg.window(
|
||||
label="Manage Flux Types",
|
||||
tag=self._window_tag,
|
||||
width=500,
|
||||
height=500,
|
||||
on_close=self._on_close,
|
||||
pos=(100, 80)
|
||||
):
|
||||
with dpg.group(horizontal=True):
|
||||
dpg.add_button(label="+ New Flux", callback=self._on_new_flux)
|
||||
dpg.add_button(label="Manage Categories", callback=self._on_manage_categories)
|
||||
|
||||
dpg.add_separator()
|
||||
|
||||
with dpg.group(tag=f"{self._window_tag}_list"):
|
||||
self._refresh_list()
|
||||
|
||||
def _refresh_list(self):
|
||||
"""Refresh the flux list."""
|
||||
parent = f"{self._window_tag}_list"
|
||||
if dpg.does_item_exist(parent):
|
||||
dpg.delete_item(parent, children_only=True)
|
||||
|
||||
# Group by category
|
||||
for cat in self.config.get_categories():
|
||||
flux_list = self.flux_library.get_by_category(cat.id)
|
||||
if flux_list:
|
||||
with dpg.tree_node(
|
||||
label=f"{cat.name} ({len(flux_list)})",
|
||||
parent=parent,
|
||||
default_open=True
|
||||
):
|
||||
for flux in flux_list:
|
||||
with dpg.group(horizontal=True):
|
||||
dpg.add_color_button(
|
||||
default_value=(
|
||||
*[c/255 for c in hex_to_rgba(flux.color)[:3]],
|
||||
1.0
|
||||
),
|
||||
width=16, height=16,
|
||||
no_border=True
|
||||
)
|
||||
dpg.add_text(flux.name)
|
||||
dpg.add_text(
|
||||
f"[{flux.connector_type}]",
|
||||
color=DarkTheme.TEXT_SECONDARY
|
||||
)
|
||||
dpg.add_button(
|
||||
label="Edit",
|
||||
callback=self._on_edit_flux,
|
||||
user_data=flux.id,
|
||||
width=50
|
||||
)
|
||||
dpg.add_button(
|
||||
label="X",
|
||||
callback=self._on_delete_flux,
|
||||
user_data=flux.id,
|
||||
width=25
|
||||
)
|
||||
|
||||
def _on_new_flux(self, sender=None, app_data=None):
|
||||
"""Create a new flux."""
|
||||
editor = FluxTemplateEditor(
|
||||
flux_library=self.flux_library,
|
||||
on_save=self._on_flux_saved
|
||||
)
|
||||
editor.show()
|
||||
|
||||
def _on_edit_flux(self, sender, app_data, user_data):
|
||||
"""Edit a flux."""
|
||||
template = self.flux_library.get_template(user_data)
|
||||
if template:
|
||||
editor = FluxTemplateEditor(
|
||||
flux_library=self.flux_library,
|
||||
on_save=self._on_flux_saved,
|
||||
template=template
|
||||
)
|
||||
editor.show()
|
||||
|
||||
def _on_delete_flux(self, sender, app_data, user_data):
|
||||
"""Delete a flux."""
|
||||
self.flux_library.delete_template(user_data)
|
||||
self._refresh_list()
|
||||
|
||||
if self.on_change:
|
||||
self.on_change()
|
||||
|
||||
def _on_flux_saved(self, template: FluxTemplate):
|
||||
"""Handle flux save."""
|
||||
self._refresh_list()
|
||||
|
||||
if self.on_change:
|
||||
self.on_change()
|
||||
|
||||
def _on_manage_categories(self, sender=None, app_data=None):
|
||||
"""Open category manager."""
|
||||
editor = CategoryEditor(on_save=self._refresh_list)
|
||||
editor.show()
|
||||
|
||||
def _on_close(self, sender=None, app_data=None):
|
||||
"""Close the window."""
|
||||
if dpg.does_item_exist(self._window_tag):
|
||||
dpg.delete_item(self._window_tag)
|
||||
@@ -1,250 +0,0 @@
|
||||
"""
|
||||
Fenêtre d'édition de template de machine.
|
||||
"""
|
||||
import dearpygui.dearpygui as dpg
|
||||
from typing import Optional, Callable
|
||||
|
||||
from ..core import MachineTemplate, PortTemplate, MachineLibrary, get_config_manager
|
||||
from .themes import DarkTheme, hex_to_rgba
|
||||
|
||||
|
||||
class MachineTemplateEditor:
|
||||
"""Éditeur de template de machine dans une fenêtre séparée."""
|
||||
|
||||
# Catégories de machines prédéfinies
|
||||
MACHINE_CATEGORIES = [
|
||||
"Source", "Affichage", "Mélangeur", "Distribution",
|
||||
"Audio", "Réseau", "Contrôle", "Enregistrement", "Conversion", "Autre"
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
library: MachineLibrary,
|
||||
on_save: Optional[Callable[[MachineTemplate], None]] = None,
|
||||
template: Optional[MachineTemplate] = None
|
||||
):
|
||||
self.library = library
|
||||
self.on_save = on_save
|
||||
self.template = template
|
||||
self._input_count = 0
|
||||
self._output_count = 0
|
||||
self._window_tag = f"template_editor_{id(self)}"
|
||||
|
||||
def show(self):
|
||||
"""Affiche la fenêtre d'édition."""
|
||||
if dpg.does_item_exist(self._window_tag):
|
||||
dpg.focus_item(self._window_tag)
|
||||
return
|
||||
|
||||
is_edit = self.template is not None
|
||||
title = "Modifier Machine" if is_edit else "Nouvelle Machine"
|
||||
|
||||
with dpg.window(
|
||||
label=title,
|
||||
tag=self._window_tag,
|
||||
width=550,
|
||||
height=650,
|
||||
on_close=self._on_close,
|
||||
pos=(100, 50)
|
||||
):
|
||||
# Informations générales
|
||||
with dpg.collapsing_header(label="Informations", default_open=True):
|
||||
dpg.add_input_text(
|
||||
label="Nom",
|
||||
tag=f"{self._window_tag}_name",
|
||||
default_value=self.template.name if is_edit else "",
|
||||
width=300
|
||||
)
|
||||
dpg.add_combo(
|
||||
label="Catégorie",
|
||||
tag=f"{self._window_tag}_category",
|
||||
items=self.MACHINE_CATEGORIES,
|
||||
default_value=self.template.category if is_edit else self.MACHINE_CATEGORIES[0],
|
||||
width=200
|
||||
)
|
||||
dpg.add_input_text(
|
||||
label="Fabricant",
|
||||
tag=f"{self._window_tag}_manufacturer",
|
||||
default_value=self.template.manufacturer if is_edit else "",
|
||||
width=200
|
||||
)
|
||||
dpg.add_input_text(
|
||||
label="Modèle",
|
||||
tag=f"{self._window_tag}_model",
|
||||
default_value=self.template.model if is_edit else "",
|
||||
width=200
|
||||
)
|
||||
|
||||
# Couleur
|
||||
default_color = hex_to_rgba(self.template.color if is_edit else "#2D2D2D")
|
||||
default_color_normalized = (
|
||||
default_color[0],
|
||||
default_color[1],
|
||||
default_color[2],
|
||||
1.0
|
||||
)
|
||||
dpg.add_color_edit(
|
||||
label="Couleur",
|
||||
tag=f"{self._window_tag}_color",
|
||||
default_value=default_color_normalized,
|
||||
no_alpha=True,
|
||||
width=200
|
||||
)
|
||||
dpg.add_input_text(
|
||||
label="Description",
|
||||
tag=f"{self._window_tag}_description",
|
||||
default_value=self.template.description if is_edit else "",
|
||||
multiline=True,
|
||||
height=60,
|
||||
width=450
|
||||
)
|
||||
|
||||
# Ports d'entrée
|
||||
with dpg.collapsing_header(label="Entrées", default_open=True):
|
||||
with dpg.group(tag=f"{self._window_tag}_inputs"):
|
||||
if is_edit:
|
||||
for port in self.template.inputs:
|
||||
self._add_port_row("input", port.name, port.flux_type_name)
|
||||
dpg.add_button(
|
||||
label="+ Ajouter Entrée",
|
||||
callback=lambda: self._add_port_row("input")
|
||||
)
|
||||
|
||||
# Ports de sortie
|
||||
with dpg.collapsing_header(label="Sorties", default_open=True):
|
||||
with dpg.group(tag=f"{self._window_tag}_outputs"):
|
||||
if is_edit:
|
||||
for port in self.template.outputs:
|
||||
self._add_port_row("output", port.name, port.flux_type_name)
|
||||
dpg.add_button(
|
||||
label="+ Ajouter Sortie",
|
||||
callback=lambda: self._add_port_row("output")
|
||||
)
|
||||
|
||||
dpg.add_separator()
|
||||
|
||||
# Boutons d'action
|
||||
with dpg.group(horizontal=True):
|
||||
dpg.add_button(
|
||||
label="Sauvegarder",
|
||||
callback=self._on_save,
|
||||
width=120
|
||||
)
|
||||
dpg.add_button(
|
||||
label="Annuler",
|
||||
callback=self._on_close,
|
||||
width=100
|
||||
)
|
||||
|
||||
def _add_port_row(
|
||||
self,
|
||||
direction: str,
|
||||
name: str = "",
|
||||
flux_name: str = ""
|
||||
):
|
||||
"""Ajoute une ligne de port."""
|
||||
parent = f"{self._window_tag}_{direction}s"
|
||||
|
||||
if direction == "input":
|
||||
idx = self._input_count
|
||||
self._input_count += 1
|
||||
else:
|
||||
idx = self._output_count
|
||||
self._output_count += 1
|
||||
|
||||
row_tag = f"{self._window_tag}_{direction}_{idx}"
|
||||
|
||||
# Récupère les noms de flux depuis la bibliothèque
|
||||
flux_names = self.library.get_flux_names()
|
||||
|
||||
with dpg.group(horizontal=True, parent=parent, tag=row_tag):
|
||||
dpg.add_input_text(
|
||||
hint="Nom du port",
|
||||
default_value=name,
|
||||
width=180,
|
||||
tag=f"{row_tag}_name"
|
||||
)
|
||||
dpg.add_combo(
|
||||
items=flux_names,
|
||||
default_value=flux_name if flux_name in flux_names else (flux_names[0] if flux_names else ""),
|
||||
width=140,
|
||||
tag=f"{row_tag}_flux"
|
||||
)
|
||||
dpg.add_button(
|
||||
label="X",
|
||||
callback=self._on_remove_port,
|
||||
user_data=row_tag,
|
||||
width=30
|
||||
)
|
||||
|
||||
def _on_remove_port(self, sender, app_data, user_data):
|
||||
"""Supprime une ligne de port."""
|
||||
row_tag = user_data
|
||||
if dpg.does_item_exist(row_tag):
|
||||
dpg.delete_item(row_tag)
|
||||
|
||||
def _collect_ports(self, direction: str) -> list[PortTemplate]:
|
||||
"""Collecte les ports depuis l'interface."""
|
||||
ports = []
|
||||
parent = f"{self._window_tag}_{direction}s"
|
||||
|
||||
if not dpg.does_item_exist(parent):
|
||||
return ports
|
||||
|
||||
children = dpg.get_item_children(parent, 1)
|
||||
if not children:
|
||||
return ports
|
||||
|
||||
for child in children:
|
||||
row_children = dpg.get_item_children(child, 1)
|
||||
if row_children and len(row_children) >= 2:
|
||||
name = dpg.get_value(row_children[0])
|
||||
flux = dpg.get_value(row_children[1])
|
||||
|
||||
if name and flux:
|
||||
ports.append(PortTemplate(name=name, flux_type_name=flux))
|
||||
|
||||
return ports
|
||||
|
||||
def _on_save(self, sender=None, app_data=None):
|
||||
"""Sauvegarde le template."""
|
||||
name = dpg.get_value(f"{self._window_tag}_name")
|
||||
|
||||
if not name:
|
||||
print("Erreur: Le nom est requis")
|
||||
return
|
||||
|
||||
# Récupère la couleur
|
||||
color_value = dpg.get_value(f"{self._window_tag}_color")
|
||||
color_hex = f"#{int(color_value[0]*255):02x}{int(color_value[1]*255):02x}{int(color_value[2]*255):02x}"
|
||||
|
||||
# Crée ou met à jour le template
|
||||
if self.template is not None:
|
||||
template = self.template
|
||||
template.name = name
|
||||
else:
|
||||
template = MachineTemplate(name=name)
|
||||
|
||||
template.category = dpg.get_value(f"{self._window_tag}_category")
|
||||
template.manufacturer = dpg.get_value(f"{self._window_tag}_manufacturer")
|
||||
template.model = dpg.get_value(f"{self._window_tag}_model")
|
||||
template.color = color_hex
|
||||
template.description = dpg.get_value(f"{self._window_tag}_description")
|
||||
|
||||
# Ports
|
||||
template.inputs = self._collect_ports("input")
|
||||
template.outputs = self._collect_ports("output")
|
||||
|
||||
# Sauvegarde dans la bibliothèque
|
||||
self.library.save_template(template)
|
||||
|
||||
# Callback
|
||||
if self.on_save:
|
||||
self.on_save(template)
|
||||
|
||||
self._on_close()
|
||||
|
||||
def _on_close(self, sender=None, app_data=None):
|
||||
"""Ferme la fenêtre."""
|
||||
if dpg.does_item_exist(self._window_tag):
|
||||
dpg.delete_item(self._window_tag)
|
||||
@@ -20,8 +20,8 @@ Example:
|
||||
def on_unload(self):
|
||||
pass
|
||||
"""
|
||||
from .base import Module, ModuleInfo, MenuItem, ModuleError
|
||||
from .manager import ModuleManager
|
||||
from synodetik.modules.base import Module, ModuleInfo, MenuItem, ModuleError
|
||||
from synodetik.modules.manager import ModuleManager
|
||||
|
||||
__all__ = [
|
||||
'Module',
|
||||
|
||||
@@ -37,7 +37,7 @@ import dearpygui.dearpygui as dpg
|
||||
from synodetik.core.events import EventDispatcher, EventType, get_dispatcher
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from synodetik.core.gui.app import SynoptikApp
|
||||
from synodetik.gui.app import SynoptikApp
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
@@ -10,7 +10,7 @@ import sys
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Optional, List
|
||||
|
||||
from .base import Module, ModuleInfo, ModuleError, MenuItem
|
||||
from synodetik.modules.base import Module, ModuleInfo, ModuleError, MenuItem
|
||||
from synodetik.core.events import get_dispatcher, EventType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@@ -5,7 +5,7 @@ This module re-exports utilities from core.utils
|
||||
to maintain backward compatibility with imports like:
|
||||
from synodetik.utils import serialization
|
||||
"""
|
||||
from ..core.utils import (
|
||||
from synodetik.core.utils import (
|
||||
save_json, load_json, to_json_string, from_json_string,
|
||||
save, load
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ This module re-exports serialization utilities from core.utils.serialization
|
||||
to maintain backward compatibility with imports like:
|
||||
from synodetik.utils import serialization
|
||||
"""
|
||||
from ..core.utils.serialization import (
|
||||
from synodetik.core.utils.serialization import (
|
||||
save_json, load_json, to_json_string, from_json_string,
|
||||
save, load
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user