rewrite layout

jotpluggler
Quantizr (Jimmy) 2 weeks ago
parent 1cfa906201
commit 628e19ffb0
  1. 430
      tools/jotpluggler/layout.py
  2. 19
      tools/jotpluggler/pluggle.py
  3. 14
      tools/jotpluggler/views.py

@ -1,282 +1,198 @@
import uuid
import dearpygui.dearpygui as dpg import dearpygui.dearpygui as dpg
from abc import ABC, abstractmethod
from openpilot.tools.jotpluggler.data import DataManager from openpilot.tools.jotpluggler.data import DataManager
from openpilot.tools.jotpluggler.views import ViewPanel, TimeSeriesPanel from openpilot.tools.jotpluggler.views import TimeSeriesPanel
class LayoutNode(ABC): class PlotLayoutManager:
def __init__(self, node_id: str | None = None): def __init__(self, data_manager: DataManager, playback_manager, scale: float = 1.0):
self.node_id = node_id or str(uuid.uuid4()) self.data_manager = data_manager
self.tag: str | None = None self.playback_manager = playback_manager
@abstractmethod
def create_ui(self, parent_tag: str, width: int = -1, height: int = -1):
pass
@abstractmethod
def destroy_ui(self):
pass
class LeafNode(LayoutNode):
"""Leaf node that contains a single ViewPanel with controls"""
def __init__(self, panel: ViewPanel, layout_manager=None, scale: float = 1.0, node_id: str = None):
super().__init__(node_id)
self.panel = panel
self.layout_manager = layout_manager
self.scale = scale self.scale = scale
self.container_tag = "plot_layout_container"
self.active_panels = []
def create_ui(self, parent_tag: str, width: int = -1, height: int = -1): initial_panel = TimeSeriesPanel(data_manager, playback_manager)
"""Create UI container with controls and panel""" self.active_panels.append(initial_panel)
self.tag = f"leaf_{self.node_id}" self.layout = {"type": "panel", "panel": initial_panel}
with dpg.child_window(tag=self.tag, parent=parent_tag, border=True, width=-1, height=-1, no_scrollbar=True):
# Control bar
with dpg.group(horizontal=True):
dpg.add_input_text(tag=f"title_{self.node_id}", default_value=self.panel.title, width=int(100 * self.scale), callback=self._on_title_change)
dpg.add_combo(
items=["Time Series"], # "Camera", "Text Log", "Map View"],
tag=f"type_{self.node_id}",
default_value="Time Series",
width=int(100 * self.scale),
callback=self._on_type_change,
)
dpg.add_button(label="Clear", callback=self._clear, width=int(50 * self.scale))
dpg.add_button(label="Delete", callback=self._delete, width=int(50 * self.scale))
dpg.add_button(label="Split H", callback=lambda: self._split("horizontal"), width=int(50 * self.scale))
dpg.add_button(label="Split V", callback=lambda: self._split("vertical"), width=int(50 * self.scale))
dpg.add_separator()
# Panel content area
panel_area_tag = f"panel_area_{self.node_id}"
with dpg.child_window(tag=panel_area_tag, border=False, height=-1, width=-1, no_scrollbar=True):
self.panel.create_ui(panel_area_tag)
def destroy_ui(self):
if self.panel:
self.panel.destroy_ui()
if self.tag and dpg.does_item_exist(self.tag):
dpg.delete_item(self.tag)
def _on_title_change(self, sender, app_data):
self.panel.title = app_data
def _on_type_change(self, sender, app_data):
print(f"Panel type change requested: {app_data}")
def _split(self, orientation: str): def create_ui(self, parent_tag: str):
if self.layout_manager: if dpg.does_item_exist(self.container_tag):
self.layout_manager.split_node(self, orientation) dpg.delete_item(self.container_tag)
def _clear(self): with dpg.child_window(tag=self.container_tag, parent=parent_tag, border=False, width=-1, height=-1, no_scrollbar=True):
if hasattr(self.panel, 'clear_all_series'): container_width, container_height = dpg.get_item_rect_size(self.container_tag)
self.panel.clear_all_series() self._create_ui_recursive(self.layout, self.container_tag, [], container_width, container_height)
def _delete(self): def on_viewport_resize(self):
if self.layout_manager: self._resize_splits_recursive(self.layout, [])
self.layout_manager.delete_node(self)
def split_panel(self, panel_path: list[int], orientation: str):
current_layout = self._get_layout_at_path(panel_path)
existing_panel = current_layout["panel"]
new_panel = TimeSeriesPanel(self.data_manager, self.playback_manager)
self.active_panels.append(new_panel)
parent, child_index = self._get_parent_and_index(panel_path)
if parent is None: # Root split
self.layout = {
"type": "split",
"orientation": orientation,
"children": [{"type": "panel", "panel": existing_panel}, {"type": "panel", "panel": new_panel}],
"proportions": [0.5, 0.5],
}
self._rebuild_ui_at_path([])
elif parent["type"] == "split" and parent["orientation"] == orientation:
parent["children"].insert(child_index + 1, {"type": "panel", "panel": new_panel})
parent["proportions"] = [1.0 / len(parent["children"])] * len(parent["children"])
self._rebuild_ui_at_path(panel_path[:-1])
else:
new_split = {"type": "split", "orientation": orientation, "children": [current_layout, {"type": "panel", "panel": new_panel}], "proportions": [0.5, 0.5]}
self._replace_layout_at_path(panel_path, new_split)
self._rebuild_ui_at_path(panel_path)
def delete_panel(self, panel_path: list[int]):
if not panel_path: # Root deletion
old_panel = self.layout["panel"]
old_panel.destroy_ui()
self.active_panels.remove(old_panel)
new_panel = TimeSeriesPanel(self.data_manager, self.playback_manager)
self.active_panels.append(new_panel)
self.layout = {"type": "panel", "panel": new_panel}
self._rebuild_ui_at_path([])
return
parent, child_index = self._get_parent_and_index(panel_path)
layout_to_delete = parent["children"][child_index]
self._cleanup_ui_recursive(layout_to_delete)
parent["children"].pop(child_index)
parent["proportions"].pop(child_index)
if len(parent["children"]) == 1: # remove parent and collapse
remaining_child = parent["children"][0]
if len(panel_path) == 1: # parent is at root level - promote remaining child to root
self.layout = remaining_child
self._rebuild_ui_at_path([])
else: # replace parent with remaining child in grandparent
grandparent_path = panel_path[:-2]
parent_index = panel_path[-2]
self._replace_layout_at_path(grandparent_path + [parent_index], remaining_child)
self._rebuild_ui_at_path(grandparent_path + [parent_index])
else: # redistribute proportions
equal_prop = 1.0 / len(parent["children"])
parent["proportions"] = [equal_prop] * len(parent["children"])
self._rebuild_ui_at_path(panel_path[:-1])
def update_all_panels(self):
for panel in self.active_panels:
panel.update()
def _get_layout_at_path(self, path: list[int]) -> dict:
current = self.layout
for index in path:
current = current["children"][index]
return current
def _get_parent_and_index(self, path: list[int]) -> tuple:
return (None, -1) if not path else (self._get_layout_at_path(path[:-1]), path[-1])
def _replace_layout_at_path(self, path: list[int], new_layout: dict):
if not path:
self.layout = new_layout
else:
parent, index = self._get_parent_and_index(path)
parent["children"][index] = new_layout
class SplitterNode(LayoutNode): def _path_to_tag(self, path: list[int], prefix: str = "") -> str:
def __init__(self, children: list[LayoutNode], orientation: str = "horizontal", node_id: str | None = None): path_str = "_".join(map(str, path)) if path else "root"
super().__init__(node_id) return f"{prefix}_{path_str}" if prefix else path_str
self.children = children if children else []
self.orientation = orientation
self.child_proportions = [1.0 / len(self.children) for _ in self.children] if self.children else []
self.child_container_tags: list[str] = [] # Track container tags for resizing
def add_child(self, child: LayoutNode, index: int = None): def _create_ui_recursive(self, layout: dict, parent_tag: str, path: list[int], width: int, height: int):
if index is None: if layout["type"] == "panel":
self.children.append(child) self._create_panel_ui(layout, parent_tag, path)
self.child_proportions.append(0.0)
else: else:
self.children.insert(index, child) self._create_split_ui(layout, parent_tag, path, width, height)
self.child_proportions.insert(index, 0.0)
self._redistribute_proportions()
def remove_child(self, child: LayoutNode):
if child in self.children:
index = self.children.index(child)
self.children.remove(child)
self.child_proportions.pop(index)
child.destroy_ui()
if self.children:
self._redistribute_proportions()
def replace_child(self, old_child: LayoutNode, new_child: LayoutNode):
try:
index = self.children.index(old_child)
self.children[index] = new_child
return index
except ValueError:
return None
def _redistribute_proportions(self):
if self.children:
equal_proportion = 1.0 / len(self.children)
self.child_proportions = [equal_proportion for _ in self.children]
def resize_children(self):
if not self.tag or not dpg.does_item_exist(self.tag):
return
available_width, available_height = dpg.get_item_rect_size(dpg.get_item_parent(self.tag)) def _create_panel_ui(self, layout: dict, parent_tag: str, path: list[int]):
panel_tag = self._path_to_tag(path, "panel")
for i, container_tag in enumerate(self.child_container_tags): with dpg.child_window(tag=panel_tag, parent=parent_tag, border=True, width=-1, height=-1, no_scrollbar=True):
if not dpg.does_item_exist(container_tag): with dpg.group(horizontal=True):
continue dpg.add_input_text(default_value=layout["panel"].title, width=int(100 * self.scale), callback=lambda s, v: setattr(layout["panel"], "title", v))
dpg.add_combo(items=["Time Series"], default_value="Time Series", width=int(100 * self.scale))
dpg.add_button(label="Clear", callback=lambda: self._clear_panel(layout["panel"]), width=int(50 * self.scale))
dpg.add_button(label="Delete", callback=lambda: self.delete_panel(path), width=int(50 * self.scale))
dpg.add_button(label="Split H", callback=lambda: self.split_panel(path, "horizontal"), width=int(50 * self.scale))
dpg.add_button(label="Split V", callback=lambda: self.split_panel(path, "vertical"), width=int(50 * self.scale))
proportion = self.child_proportions[i] if i < len(self.child_proportions) else (1.0 / len(self.children)) dpg.add_separator()
if self.orientation == "horizontal": content_tag = self._path_to_tag(path, "content")
new_width = max(100, int(available_width * proportion)) with dpg.child_window(tag=content_tag, border=False, height=-1, width=-1, no_scrollbar=True):
dpg.configure_item(container_tag, width=new_width) layout["panel"].create_ui(content_tag)
else:
new_height = max(100, int(available_height * proportion))
dpg.configure_item(container_tag, height=new_height)
child = self.children[i] if i < len(self.children) else None def _create_split_ui(self, layout: dict, parent_tag: str, path: list[int], width: int, height: int):
if child and isinstance(child, SplitterNode): split_tag = self._path_to_tag(path, "split")
child.resize_children() is_horizontal = layout["orientation"] == "horizontal"
def create_ui(self, parent_tag: str, width: int = -1, height: int = -1): with dpg.group(tag=split_tag, parent=parent_tag, horizontal=is_horizontal):
self.tag = f"splitter_{self.node_id}" for i, (child_layout, proportion) in enumerate(zip(layout["children"], layout["proportions"], strict=True)):
self.child_container_tags = [] child_path = path + [i]
container_tag = self._path_to_tag(child_path, "container")
if self.orientation == "horizontal": if is_horizontal:
with dpg.group(tag=self.tag, parent=parent_tag, horizontal=True):
for i, child in enumerate(self.children):
proportion = self.child_proportions[i]
child_width = max(100, int(width * proportion)) child_width = max(100, int(width * proportion))
container_tag = f"child_container_{self.node_id}_{i}" with dpg.child_window(tag=container_tag, width=child_width, height=-1, border=False, no_scrollbar=True):
self.child_container_tags.append(container_tag) self._create_ui_recursive(child_layout, container_tag, child_path, child_width, height)
else:
with dpg.child_window(tag=container_tag, width=child_width, height=-1, border=False, no_scrollbar=True, resizable_x=False):
child.create_ui(container_tag, child_width, height)
else:
with dpg.group(tag=self.tag, parent=parent_tag):
for i, child in enumerate(self.children):
proportion = self.child_proportions[i]
child_height = max(100, int(height * proportion)) child_height = max(100, int(height * proportion))
container_tag = f"child_container_{self.node_id}_{i}" with dpg.child_window(tag=container_tag, width=-1, height=child_height, border=False, no_scrollbar=True):
self.child_container_tags.append(container_tag) self._create_ui_recursive(child_layout, container_tag, child_path, width, child_height)
with dpg.child_window(tag=container_tag, width=-1, height=child_height, border=False, no_scrollbar=True, resizable_y=False):
child.create_ui(container_tag, width, child_height)
def destroy_ui(self): def _rebuild_ui_at_path(self, path: list[int]):
for child in self.children: layout = self._get_layout_at_path(path)
if child:
child.destroy_ui()
if self.tag and dpg.does_item_exist(self.tag):
dpg.delete_item(self.tag)
self.child_container_tags.clear()
if not path: # Root update
class PlotLayoutManager: dpg.delete_item(self.container_tag, children_only=True)
def __init__(self, data_manager: DataManager, playback_manager, scale: float = 1.0):
self.data_manager = data_manager
self.playback_manager = playback_manager
self.scale = scale
self.container_tag = "plot_layout_container"
self._initialize_default_layout()
def _initialize_default_layout(self):
panel = TimeSeriesPanel(self.data_manager, self.playback_manager)
self.root_node = LeafNode(panel, layout_manager=self, scale=self.scale)
def create_ui(self, parent_tag: str):
if dpg.does_item_exist(self.container_tag):
dpg.delete_item(self.container_tag)
with dpg.child_window(tag=self.container_tag, parent=parent_tag, border=False, width=-1, height=-1, no_scrollbar=True):
container_width, container_height = dpg.get_item_rect_size(self.container_tag) container_width, container_height = dpg.get_item_rect_size(self.container_tag)
self.root_node.create_ui(self.container_tag, container_width, container_height) self._create_ui_recursive(layout, self.container_tag, path, container_width, container_height)
else:
def on_viewport_resize(self): container_tag = self._path_to_tag(path, "container")
if isinstance(self.root_node, SplitterNode): if dpg.does_item_exist(container_tag):
self.root_node.resize_children() self._cleanup_ui_recursive(layout)
dpg.delete_item(container_tag, children_only=True)
def split_node(self, node: LeafNode, orientation: str): width, height = dpg.get_item_rect_size(container_tag)
# create new panel for the split self._create_ui_recursive(layout, container_tag, path, width, height)
new_panel = TimeSeriesPanel(self.data_manager, self.playback_manager) # TODO: create same type of panel as the split
new_leaf = LeafNode(new_panel, layout_manager=self, scale=self.scale) def _cleanup_ui_recursive(self, layout: dict):
if layout["type"] == "panel":
parent_node, child_index = self._find_parent_and_index(node) panel = layout["panel"]
panel.destroy_ui()
if parent_node is None: # root node - create new splitter as root if panel in self.active_panels:
self.root_node = SplitterNode([node, new_leaf], orientation) self.active_panels.remove(panel)
self._update_ui_for_node(self.root_node, self.container_tag) else:
elif isinstance(parent_node, SplitterNode) and parent_node.orientation == orientation: # same orientation - add to existing splitter for child in layout["children"]:
parent_node.add_child(new_leaf, child_index + 1) self._cleanup_ui_recursive(child)
self._update_ui_for_node(parent_node)
else: # different orientation - replace node with new splitter def _resize_splits_recursive(self, layout: dict, path: list[int]):
new_splitter = SplitterNode([node, new_leaf], orientation) if layout["type"] == "split":
self._replace_child_in_parent(parent_node, node, new_splitter) split_tag = self._path_to_tag(path, "split")
if dpg.does_item_exist(split_tag):
def delete_node(self, node: LeafNode): # TODO: actually delete the node, not just the ui for the node parent_tag = dpg.get_item_parent(split_tag)
parent_node, child_index = self._find_parent_and_index(node) available_width, available_height = dpg.get_item_rect_size(parent_tag)
if parent_node is None: # root deletion - replace with new default for i, proportion in enumerate(layout["proportions"]):
node.destroy_ui() child_path = path + [i]
self._initialize_default_layout() container_tag = self._path_to_tag(child_path, "container")
self._update_ui_for_node(self.root_node, self.container_tag) if dpg.does_item_exist(container_tag):
elif isinstance(parent_node, SplitterNode): if layout["orientation"] == "horizontal":
parent_node.remove_child(node) dpg.configure_item(container_tag, width=max(100, int(available_width * proportion)))
if len(parent_node.children) == 1: # collapse splitter --> leaf to just leaf else:
remaining_child = parent_node.children[0] dpg.configure_item(container_tag, height=max(100, int(available_height * proportion)))
grandparent_node, parent_index = self._find_parent_and_index(parent_node)
self._resize_splits_recursive(layout["children"][i], child_path)
if grandparent_node is None: # promote remaining child to root
parent_node.children.remove(remaining_child) def _clear_panel(self, panel):
self.root_node = remaining_child if hasattr(panel, 'clear_all_series'):
parent_node.destroy_ui() panel.clear_all_series()
self._update_ui_for_node(self.root_node, self.container_tag)
else: # replace splitter with remaining child in grandparent node
self._replace_child_in_parent(grandparent_node, parent_node, remaining_child)
else: # update splpitter contents
self._update_ui_for_node(parent_node)
def _replace_child_in_parent(self, parent_node: SplitterNode, old_child: LayoutNode, new_child: LayoutNode):
child_index = parent_node.children.index(old_child)
child_container_tag = f"child_container_{parent_node.node_id}_{child_index}"
parent_node.replace_child(old_child, new_child)
# Clean up old child if it's being replaced (not just moved)
if old_child != new_child:
old_child.destroy_ui()
if dpg.does_item_exist(child_container_tag):
dpg.delete_item(child_container_tag, children_only=True)
container_width, container_height = dpg.get_item_rect_size(child_container_tag)
new_child.create_ui(child_container_tag, container_width, container_height)
def _update_ui_for_node(self, node: LayoutNode, container_tag: str = None):
if container_tag: # update node in a specific container (usually root)
dpg.delete_item(container_tag, children_only=True)
container_width, container_height = dpg.get_item_rect_size(container_tag)
node.create_ui(container_tag, container_width, container_height)
else: # update node in its current location (splitter updates)
if node.tag and dpg.does_item_exist(node.tag):
parent_container = dpg.get_item_parent(node.tag)
node.destroy_ui()
if parent_container and dpg.does_item_exist(parent_container):
parent_width, parent_height = dpg.get_item_rect_size(parent_container)
node.create_ui(parent_container, parent_width, parent_height)
def _find_parent_and_index(self, target_node: LayoutNode): # TODO: probably can be stored in child
def search_recursive(node: LayoutNode | None, parent: LayoutNode | None = None, index: int = 0):
if node == target_node:
return parent, index
if isinstance(node, SplitterNode):
for i, child in enumerate(node.children):
result = search_recursive(child, node, i)
if result[0] is not None:
return result
return None, None
return search_recursive(self.root_node)

@ -9,7 +9,7 @@ import numpy as np
from openpilot.common.basedir import BASEDIR from openpilot.common.basedir import BASEDIR
from openpilot.tools.jotpluggler.data import DataManager from openpilot.tools.jotpluggler.data import DataManager
from openpilot.tools.jotpluggler.views import DataTreeView from openpilot.tools.jotpluggler.views import DataTreeView
from openpilot.tools.jotpluggler.layout import PlotLayoutManager, SplitterNode, LeafNode from openpilot.tools.jotpluggler.layout import PlotLayoutManager
DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19" DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19"
@ -173,12 +173,12 @@ class MainController:
dpg.set_value("fps_counter", f"{dpg.get_frame_rate():.1f} FPS") dpg.set_value("fps_counter", f"{dpg.get_frame_rate():.1f} FPS")
def _update_visible_set(self): # for some reason, dpg has no way to easily check for visibility, and checking is slow... def _update_visible_set(self): # for some reason, dpg has no way to easily check for visibility, and checking is slow...
all_paths = list(self.data_tree_view.created_leaf_paths) all_paths = list(self.data_tree_view.created_leaf_paths)
if not all_paths: if not all_paths:
self.visible_paths.clear() self.visible_paths.clear()
return return
chunk_size = min(50, len(all_paths)) # check up to 50 paths per frame chunk_size = min(50, len(all_paths)) # check up to 50 paths per frame
end_index = min(self.check_index + chunk_size, len(all_paths)) end_index = min(self.check_index + chunk_size, len(all_paths))
for i in range(self.check_index, end_index): for i in range(self.check_index, end_index):
path = all_paths[i] path = all_paths[i]
@ -192,7 +192,7 @@ class MainController:
def _update_data_values(self): def _update_data_values(self):
value_column_width = dpg.get_item_rect_size("data_pool_window")[0] // 2 value_column_width = dpg.get_item_rect_size("data_pool_window")[0] // 2
for path in self.visible_paths.copy(): # avoid modification during iteration for path in self.visible_paths.copy(): # avoid modification during iteration
value_tag = f"value_{path}" value_tag = f"value_{path}"
group_tag = f"group_{path}" group_tag = f"group_{path}"
@ -207,16 +207,7 @@ class MainController:
dpg.set_value(value_tag, formatted_value) dpg.set_value(value_tag, formatted_value)
def _update_timeline_indicators(self, current_time_s: float): def _update_timeline_indicators(self, current_time_s: float):
def update_node_recursive(node): self.plot_layout_manager.update_all_panels()
if isinstance(node, LeafNode):
if hasattr(node.panel, 'update_timeline_indicator'):
node.panel.update_timeline_indicator(current_time_s)
elif isinstance(node, SplitterNode):
for child in node.children:
update_node_recursive(child)
if self.plot_layout_manager.root_node:
update_node_recursive(self.plot_layout_manager.root_node)
def main(route_to_load=None): def main(route_to_load=None):

@ -28,6 +28,9 @@ class ViewPanel(ABC):
def get_panel_type(self) -> str: def get_panel_type(self) -> str:
pass pass
def update(self):
pass
class TimeSeriesPanel(ViewPanel): class TimeSeriesPanel(ViewPanel):
def __init__(self, data_manager: DataManager, playback_manager, panel_id: str | None = None): def __init__(self, data_manager: DataManager, playback_manager, panel_id: str | None = None):
@ -61,6 +64,10 @@ class TimeSeriesPanel(ViewPanel):
self._ui_created = True self._ui_created = True
def update(self):
if self._ui_created:
self.update_timeline_indicator(self.playback_manager.current_time_s)
def update_timeline_indicator(self, current_time_s: float): def update_timeline_indicator(self, current_time_s: float):
if not self._ui_created or not dpg.does_item_exist(self.timeline_indicator_tag): if not self._ui_created or not dpg.does_item_exist(self.timeline_indicator_tag):
return return
@ -149,7 +156,7 @@ class DataTreeView:
self.ui_lock = ui_lock self.ui_lock = ui_lock
self.current_search = "" self.current_search = ""
self.data_tree = DataTreeNode(name="root") self.data_tree = DataTreeNode(name="root")
self.ui_render_queue: deque[tuple[DataTreeNode, str, str, bool]] = deque() # (node, parent_tag, search_term, is_leaf) self.ui_render_queue: deque[tuple[DataTreeNode, str, str, bool]] = deque() # (node, parent_tag, search_term, is_leaf)
self.visible_expanded_nodes: set[str] = set() self.visible_expanded_nodes: set[str] = set()
self.created_leaf_paths: set[str] = set() self.created_leaf_paths: set[str] = set()
self._all_paths_cache: list[str] = [] self._all_paths_cache: list[str] = []
@ -176,7 +183,6 @@ class DataTreeView:
for child in sorted(self.data_tree.children.values(), key=self._natural_sort_key): for child in sorted(self.data_tree.children.values(), key=self._natural_sort_key):
self.ui_render_queue.append((child, "data_tree_container", search_term, child.is_leaf)) self.ui_render_queue.append((child, "data_tree_container", search_term, child.is_leaf))
def _add_paths_to_tree(self, paths, incremental=False): def _add_paths_to_tree(self, paths, incremental=False):
search_term = self.current_search.strip().lower() search_term = self.current_search.strip().lower()
filtered_paths = [path for path in paths if self._should_show_path(path, search_term)] filtered_paths = [path for path in paths if self._should_show_path(path, search_term)]
@ -228,7 +234,7 @@ class DataTreeView:
def update_frame(self): def update_frame(self):
items_processed = 0 items_processed = 0
while self.ui_render_queue and items_processed < self.MAX_ITEMS_PER_FRAME: # process up to MAX_ITEMS_PER_FRAME to maintain performance while self.ui_render_queue and items_processed < self.MAX_ITEMS_PER_FRAME: # process up to MAX_ITEMS_PER_FRAME to maintain performance
node, parent_tag, search_term, is_leaf = self.ui_render_queue.popleft() node, parent_tag, search_term, is_leaf = self.ui_render_queue.popleft()
if is_leaf: if is_leaf:
self._create_leaf_ui(node, parent_tag) self._create_leaf_ui(node, parent_tag)
@ -269,7 +275,7 @@ class DataTreeView:
node.ui_tag = node_tag node.ui_tag = node_tag
label = f"{node.name} ({node.child_count} fields)" label = f"{node.name} ({node.child_count} fields)"
should_open = (bool(search_term) and len(search_term) > 1 and any(search_term in path for path in self._get_descendant_paths(node))) should_open = bool(search_term) and len(search_term) > 1 and any(search_term in path for path in self._get_descendant_paths(node))
with dpg.tree_node(label=label, parent=parent_tag, tag=node_tag, default_open=should_open, open_on_arrow=True, open_on_double_click=True) as tree_node: with dpg.tree_node(label=label, parent=parent_tag, tag=node_tag, default_open=should_open, open_on_arrow=True, open_on_double_click=True) as tree_node:
with dpg.item_handler_registry() as handler: with dpg.item_handler_registry() as handler:

Loading…
Cancel
Save