195 lines
8.4 KiB
Python
195 lines
8.4 KiB
Python
"""Right sidebar - node property editor panel."""
|
|
|
|
import tkinter as tk
|
|
from utils.constants import NODE_TYPES
|
|
from node_editor.nodes import get_property_editor
|
|
|
|
|
|
class PropertiesPanel(tk.Frame):
|
|
"""Dynamic property editor for the selected node."""
|
|
|
|
def __init__(self, parent, on_change=None):
|
|
super().__init__(parent, bg="#252535", width=250)
|
|
self.on_change = on_change
|
|
self.current_node = None
|
|
self.current_editor = None
|
|
self._node_canvas = None
|
|
self._profile_manager = None
|
|
self._project = None
|
|
|
|
self.pack_propagate(False)
|
|
self._build_ui()
|
|
|
|
def set_node_canvas(self, canvas):
|
|
self._node_canvas = canvas
|
|
|
|
def set_macro_list(self, macro_list):
|
|
self._macro_list = macro_list
|
|
|
|
def set_profile_manager(self, profile_manager):
|
|
self._profile_manager = profile_manager
|
|
|
|
def set_project(self, project):
|
|
"""Wire the Project so editors can read live settings (pause-text margins, etc.)."""
|
|
self._project = project
|
|
|
|
def _build_ui(self):
|
|
self.header = tk.Frame(self, bg="#1E1E2E")
|
|
self.header.pack(fill="x")
|
|
|
|
self.title_label = tk.Label(self.header, text="Properties", bg="#1E1E2E",
|
|
fg="white", font=("Segoe UI", 11, "bold"), pady=8)
|
|
self.title_label.pack(side="left", padx=10)
|
|
|
|
content_frame = tk.Frame(self, bg="#252535")
|
|
content_frame.pack(fill="both", expand=True)
|
|
|
|
self.content_canvas = tk.Canvas(content_frame, bg="#252535", highlightthickness=0)
|
|
scrollbar = tk.Scrollbar(content_frame, orient="vertical",
|
|
command=self.content_canvas.yview)
|
|
self.content_inner = tk.Frame(self.content_canvas, bg="#252535")
|
|
|
|
self.content_inner.bind("<Configure>",
|
|
lambda e: self.content_canvas.configure(
|
|
scrollregion=self.content_canvas.bbox("all")))
|
|
self.content_canvas.create_window((0, 0), window=self.content_inner, anchor="nw",
|
|
tags="inner")
|
|
self.content_canvas.configure(yscrollcommand=scrollbar.set)
|
|
self.content_canvas.bind("<Configure>",
|
|
lambda e: self.content_canvas.itemconfig("inner", width=e.width))
|
|
|
|
# Bind wheel scrolling only while the cursor is inside the panel so
|
|
# we don't fight other widgets for the global <MouseWheel> event
|
|
def _on_wheel(event):
|
|
self.content_canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
|
|
return "break"
|
|
|
|
def _bind_wheel(_):
|
|
self.content_canvas.bind_all("<MouseWheel>", _on_wheel)
|
|
|
|
def _unbind_wheel(_):
|
|
self.content_canvas.unbind_all("<MouseWheel>")
|
|
|
|
for w in (self.content_canvas, self.content_inner):
|
|
w.bind("<Enter>", _bind_wheel)
|
|
w.bind("<Leave>", _unbind_wheel)
|
|
|
|
self.content_canvas.pack(side="left", fill="both", expand=True)
|
|
scrollbar.pack(side="right", fill="y")
|
|
|
|
self.empty_label = tk.Label(self.content_inner,
|
|
text="Select a node to\nedit its properties",
|
|
bg="#252535", fg="#666666",
|
|
font=("Segoe UI", 10), justify="center")
|
|
self.empty_label.pack(expand=True, pady=40)
|
|
|
|
def show_node(self, node_widget):
|
|
"""Display properties for the selected node (or multi-select summary)."""
|
|
self._clear_editor()
|
|
|
|
if node_widget is None:
|
|
self.current_node = None
|
|
self.title_label.config(text="Properties")
|
|
|
|
canvas = self._node_canvas
|
|
if canvas and len(canvas.selected_nodes) > 1:
|
|
count = len(canvas.selected_nodes)
|
|
tk.Label(self.content_inner,
|
|
text=f"{count} nodes selected",
|
|
bg="#252535", fg="#AAAAAA",
|
|
font=("Segoe UI", 11, "bold"), justify="center").pack(pady=(30, 10))
|
|
tk.Button(self.content_inner, text="Delete All Selected",
|
|
bg="#D94A4A", fg="white", font=("Segoe UI", 9), relief="flat",
|
|
command=self._delete_current).pack(padx=20, pady=4, fill="x")
|
|
return
|
|
|
|
self.empty_label = tk.Label(self.content_inner,
|
|
text="Select a node to\nedit its properties",
|
|
bg="#252535", fg="#666666",
|
|
font=("Segoe UI", 10), justify="center")
|
|
self.empty_label.pack(expand=True, pady=40)
|
|
return
|
|
|
|
self.current_node = node_widget
|
|
node_data = node_widget.data
|
|
type_info = NODE_TYPES.get(node_data.type, {"label": "Unknown"})
|
|
self.title_label.config(text=type_info["label"])
|
|
|
|
type_frame = tk.Frame(self.content_inner, bg="#2D2D3D")
|
|
type_frame.pack(fill="x", padx=4, pady=4)
|
|
color = type_info.get("color", "#555555")
|
|
tk.Frame(type_frame, bg=color, width=4).pack(side="left", fill="y")
|
|
tk.Label(type_frame, text=f" {type_info['label']} Node",
|
|
bg="#2D2D3D", fg="white", font=("Segoe UI", 10, "bold"),
|
|
pady=6).pack(side="left")
|
|
|
|
tk.Button(type_frame, text="Delete", bg="#D94A4A", fg="white",
|
|
font=("Segoe UI", 8), relief="flat",
|
|
command=self._delete_current).pack(side="right", padx=8, pady=4)
|
|
|
|
editor_factory = get_property_editor(node_data.type)
|
|
if editor_factory:
|
|
def on_prop_change():
|
|
if self.current_node and self._node_canvas:
|
|
self._node_canvas.refresh_node(self.current_node.data.id)
|
|
if self.on_change:
|
|
self.on_change()
|
|
|
|
if node_data.type == "start" and self._node_canvas and self._node_canvas.macro:
|
|
def on_rename():
|
|
if hasattr(self, '_macro_list') and self._macro_list:
|
|
self._macro_list.refresh()
|
|
|
|
editor = editor_factory(
|
|
self.content_inner, node_data.data, on_prop_change,
|
|
macro=self._node_canvas.macro, on_rename=on_rename,
|
|
)
|
|
elif node_data.type == "subroutine" and self._profile_manager:
|
|
sub_names = self._profile_manager.get_subroutine_names()
|
|
editor = editor_factory(
|
|
self.content_inner, node_data.data, on_prop_change,
|
|
subroutine_names=sub_names,
|
|
)
|
|
elif node_data.type == "iteration_branch" and self._node_canvas:
|
|
editor = editor_factory(
|
|
self.content_inner, node_data.data, on_prop_change,
|
|
macro=self._node_canvas.macro,
|
|
node_canvas=self._node_canvas,
|
|
widget=node_widget,
|
|
)
|
|
elif node_data.type == "aggregator" and self._node_canvas:
|
|
editor = editor_factory(
|
|
self.content_inner, node_data.data, on_prop_change,
|
|
node_canvas=self._node_canvas,
|
|
widget=node_widget,
|
|
)
|
|
elif node_data.type in ("branch", "bluetooth") and self._node_canvas:
|
|
editor = editor_factory(
|
|
self.content_inner, node_data.data, on_prop_change,
|
|
node_canvas=self._node_canvas,
|
|
widget=node_widget,
|
|
)
|
|
elif node_data.type == "pause":
|
|
editor = editor_factory(
|
|
self.content_inner, node_data.data, on_prop_change,
|
|
project=self._project,
|
|
)
|
|
elif node_data.type == "text":
|
|
editor = editor_factory(
|
|
self.content_inner, node_data.data, on_prop_change,
|
|
project=self._project,
|
|
)
|
|
else:
|
|
editor = editor_factory(self.content_inner, node_data.data, on_prop_change)
|
|
editor.pack(fill="x", padx=4, pady=4)
|
|
self.current_editor = editor
|
|
|
|
def _clear_editor(self):
|
|
for widget in self.content_inner.winfo_children():
|
|
widget.destroy()
|
|
self.current_editor = None
|
|
|
|
def _delete_current(self):
|
|
if self._node_canvas:
|
|
self._node_canvas.delete_selected()
|