Initial public release
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user