| 
						
						
							
								
							
						
						
					 | 
					 | 
					@ -20,17 +20,35 @@ class LayoutManager: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    self.tabs: dict = {0: {"name": "Tab 1", "panel_layout": initial_panel_layout}} | 
					 | 
					 | 
					 | 
					    self.tabs: dict = {0: {"name": "Tab 1", "panel_layout": initial_panel_layout}} | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    self._next_tab_id = self.active_tab + 1 | 
					 | 
					 | 
					 | 
					    self._next_tab_id = self.active_tab + 1 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    self._create_tab_themes() | 
					 | 
					 | 
					 | 
					  def to_dict(self) -> dict: | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					    return { | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					  def _create_tab_themes(self): | 
					 | 
					 | 
					 | 
					      "tabs": { | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    for tag, color in (("active_tab_theme", (37, 37, 38, 255)), ("inactive_tab_theme", (70, 70, 75, 255))): | 
					 | 
					 | 
					 | 
					        str(tab_id): { | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      with dpg.theme(tag=tag): | 
					 | 
					 | 
					 | 
					          "name": tab_data["name"], | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        for cmp, target in ((dpg.mvChildWindow, dpg.mvThemeCol_ChildBg), (dpg.mvInputText, dpg.mvThemeCol_FrameBg), (dpg.mvImageButton, dpg.mvThemeCol_Button)): | 
					 | 
					 | 
					 | 
					          "panel_layout": tab_data["panel_layout"].to_dict() | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					          with dpg.theme_component(cmp): | 
					 | 
					 | 
					 | 
					        } | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					            dpg.add_theme_color(target, color) | 
					 | 
					 | 
					 | 
					        for tab_id, tab_data in self.tabs.items() | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    with dpg.theme(tag="tab_bar_theme"): | 
					 | 
					 | 
					 | 
					      } | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      with dpg.theme_component(dpg.mvChildWindow): | 
					 | 
					 | 
					 | 
					    } | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        dpg.add_theme_color(dpg.mvThemeCol_ChildBg, (51, 51, 55, 255)) | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					  def clear_and_load_from_dict(self, data: dict): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    tab_ids_to_close = list(self.tabs.keys()) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    for tab_id in tab_ids_to_close: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					      self.close_tab(tab_id, force=True) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    for tab_id_str, tab_data in data["tabs"].items(): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					      tab_id = int(tab_id_str) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					      panel_layout = PanelLayoutManager.load_from_dict( | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        tab_data["panel_layout"], self.data_manager, self.playback_manager, | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        self.worker_manager, self.scale | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					      ) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					      self.tabs[tab_id] = { | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        "name": tab_data["name"], | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        "panel_layout": panel_layout | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					      } | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    self.active_tab = min(self.tabs.keys()) if self.tabs else 0 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    self._next_tab_id = max(self.tabs.keys()) + 1 if self.tabs else 1 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					  def create_ui(self, parent_tag: str): | 
					 | 
					 | 
					 | 
					  def create_ui(self, parent_tag: str): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    if dpg.does_item_exist(self.container_tag): | 
					 | 
					 | 
					 | 
					    if dpg.does_item_exist(self.container_tag): | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					 | 
					@ -52,7 +70,7 @@ class LayoutManager: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					  def _create_tab_ui(self, tab_id: int, tab_name: str): | 
					 | 
					 | 
					 | 
					  def _create_tab_ui(self, tab_id: int, tab_name: str): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    text_size = int(13 * self.scale) | 
					 | 
					 | 
					 | 
					    text_size = int(13 * self.scale) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    tab_width = int(120 * self.scale) | 
					 | 
					 | 
					 | 
					    tab_width = int(140 * self.scale) | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    with dpg.child_window(width=tab_width, height=-1, border=False, no_scrollbar=True, tag=f"tab_window_{tab_id}", parent="tab_bar_group"): | 
					 | 
					 | 
					 | 
					    with dpg.child_window(width=tab_width, height=-1, border=False, no_scrollbar=True, tag=f"tab_window_{tab_id}", parent="tab_bar_group"): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      with dpg.group(horizontal=True, tag=f"tab_group_{tab_id}"): | 
					 | 
					 | 
					 | 
					      with dpg.group(horizontal=True, tag=f"tab_group_{tab_id}"): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        dpg.add_input_text( | 
					 | 
					 | 
					 | 
					        dpg.add_input_text( | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					 | 
					@ -70,20 +88,21 @@ class LayoutManager: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					  def _create_tab_content(self): | 
					 | 
					 | 
					 | 
					  def _create_tab_content(self): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    with dpg.child_window(tag=self.tab_content_tag, parent=self.container_tag, border=False, width=-1, height=-1, no_scrollbar=True, no_scroll_with_mouse=True): | 
					 | 
					 | 
					 | 
					    with dpg.child_window(tag=self.tab_content_tag, parent=self.container_tag, border=False, width=-1, height=-1, no_scrollbar=True, no_scroll_with_mouse=True): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      active_panel_layout = self.tabs[self.active_tab]["panel_layout"] | 
					 | 
					 | 
					 | 
					      if self.active_tab in self.tabs: | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      active_panel_layout.create_ui() | 
					 | 
					 | 
					 | 
					        active_panel_layout = self.tabs[self.active_tab]["panel_layout"] | 
				
			
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        active_panel_layout.create_ui() | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					  def add_tab(self): | 
					 | 
					 | 
					 | 
					  def add_tab(self): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    new_panel_layout = PanelLayoutManager(self.data_manager, self.playback_manager, self.worker_manager, self.scale) | 
					 | 
					 | 
					 | 
					    new_panel_layout = PanelLayoutManager(self.data_manager, self.playback_manager, self.worker_manager, self.scale) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    new_tab = {"name": f"Tab {self._next_tab_id + 1}", "panel_layout": new_panel_layout} | 
					 | 
					 | 
					 | 
					    new_tab = {"name": f"Tab {self._next_tab_id + 1}", "panel_layout": new_panel_layout} | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    self.tabs[ self._next_tab_id] = new_tab | 
					 | 
					 | 
					 | 
					    self.tabs[self._next_tab_id] = new_tab | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    self._create_tab_ui( self._next_tab_id, new_tab["name"]) | 
					 | 
					 | 
					 | 
					    self._create_tab_ui(self._next_tab_id, new_tab["name"]) | 
				
			
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    dpg.move_item("add_tab_button", parent="tab_bar_group")  # move plus button to end | 
					 | 
					 | 
					 | 
					    dpg.move_item("add_tab_button", parent="tab_bar_group")  # move plus button to end | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    self.switch_tab( self._next_tab_id) | 
					 | 
					 | 
					 | 
					    self.switch_tab(self._next_tab_id) | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    self._next_tab_id += 1 | 
					 | 
					 | 
					 | 
					    self._next_tab_id += 1 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					  def close_tab(self, tab_id: int): | 
					 | 
					 | 
					 | 
					  def close_tab(self, tab_id: int, force = False): | 
				
			
			
				
				
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    if len(self.tabs) <= 1: | 
					 | 
					 | 
					 | 
					    if len(self.tabs) <= 1 and not force: | 
				
			
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      return  # don't allow closing the last tab | 
					 | 
					 | 
					 | 
					      return  # don't allow closing the last tab | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    tab_to_close = self.tabs[tab_id] | 
					 | 
					 | 
					 | 
					    tab_to_close = self.tabs[tab_id] | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					 | 
					@ -94,7 +113,7 @@ class LayoutManager: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        dpg.delete_item(tag) | 
					 | 
					 | 
					 | 
					        dpg.delete_item(tag) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    del self.tabs[tab_id] | 
					 | 
					 | 
					 | 
					    del self.tabs[tab_id] | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    if self.active_tab == tab_id: # switch to another tab if we closed the active one | 
					 | 
					 | 
					 | 
					    if self.active_tab == tab_id and self.tabs: # switch to another tab if we closed the active one | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      self.active_tab = next(iter(self.tabs.keys())) | 
					 | 
					 | 
					 | 
					      self.active_tab = next(iter(self.tabs.keys())) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      self._switch_tab_content() | 
					 | 
					 | 
					 | 
					      self._switch_tab_content() | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      dpg.bind_item_theme(f"tab_window_{self.active_tab}", "active_tab_theme") | 
					 | 
					 | 
					 | 
					      dpg.bind_item_theme(f"tab_window_{self.active_tab}", "active_tab_theme") | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
					 | 
					@ -134,6 +153,8 @@ class PanelLayoutManager: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    self.scale = scale | 
					 | 
					 | 
					 | 
					    self.scale = scale | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    self.active_panels: list = [] | 
					 | 
					 | 
					 | 
					    self.active_panels: list = [] | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    self.parent_tag = "tab_content_area" | 
					 | 
					 | 
					 | 
					    self.parent_tag = "tab_content_area" | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    self._queue_resize = False | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    self._created_handler_tags: set[str] = set() | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    self.grip_size = int(GRIP_SIZE * self.scale) | 
					 | 
					 | 
					 | 
					    self.grip_size = int(GRIP_SIZE * self.scale) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    self.min_pane_size = int(MIN_PANE_SIZE * self.scale) | 
					 | 
					 | 
					 | 
					    self.min_pane_size = int(MIN_PANE_SIZE * self.scale) | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					 | 
					@ -141,19 +162,70 @@ class PanelLayoutManager: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    initial_panel = TimeSeriesPanel(data_manager, playback_manager, worker_manager) | 
					 | 
					 | 
					 | 
					    initial_panel = TimeSeriesPanel(data_manager, playback_manager, worker_manager) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    self.layout: dict = {"type": "panel", "panel": initial_panel} | 
					 | 
					 | 
					 | 
					    self.layout: dict = {"type": "panel", "panel": initial_panel} | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					  def to_dict(self) -> dict: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    return self._layout_to_dict(self.layout) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					  def _layout_to_dict(self, layout: dict) -> dict: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    if layout["type"] == "panel": | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					      return { | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        "type": "panel", | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        "panel": layout["panel"].to_dict() | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					      } | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    else:  # split | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					      return { | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        "type": "split", | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        "orientation": layout["orientation"], | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        "proportions": layout["proportions"], | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        "children": [self._layout_to_dict(child) for child in layout["children"]] | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					      } | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					  @classmethod | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					  def load_from_dict(cls, data: dict, data_manager, playback_manager, worker_manager, scale: float = 1.0): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    manager = cls(data_manager, playback_manager, worker_manager, scale) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    manager.layout = manager._dict_to_layout(data) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    return manager | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					  def _dict_to_layout(self, data: dict) -> dict: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    if data["type"] == "panel": | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					      panel_data = data["panel"] | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					      if panel_data["type"] == "timeseries": | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        panel = TimeSeriesPanel.load_from_dict( | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					          panel_data, self.data_manager, self.playback_manager, self.worker_manager | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        ) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        return {"type": "panel", "panel": panel} | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					      else: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        # Handle future panel types here or make a general mapping | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        raise ValueError(f"Unknown panel type: {panel_data['type']}") | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    else:  # split | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					      return { | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        "type": "split", | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        "orientation": data["orientation"], | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        "proportions": data["proportions"], | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        "children": [self._dict_to_layout(child) for child in data["children"]] | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					      } | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					  def create_ui(self): | 
					 | 
					 | 
					 | 
					  def create_ui(self): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    self.active_panels.clear() | 
					 | 
					 | 
					 | 
					    self.active_panels.clear() | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    if dpg.does_item_exist(self.parent_tag): | 
					 | 
					 | 
					 | 
					    if dpg.does_item_exist(self.parent_tag): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      dpg.delete_item(self.parent_tag, children_only=True) | 
					 | 
					 | 
					 | 
					      dpg.delete_item(self.parent_tag, children_only=True) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    self._cleanup_all_handlers() | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    container_width, container_height = dpg.get_item_rect_size(self.parent_tag) | 
					 | 
					 | 
					 | 
					    container_width, container_height = dpg.get_item_rect_size(self.parent_tag) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    if container_width == 0 and container_height == 0: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					      self._queue_resize = True | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    self._create_ui_recursive(self.layout, self.parent_tag, [], container_width, container_height) | 
					 | 
					 | 
					 | 
					    self._create_ui_recursive(self.layout, self.parent_tag, [], container_width, container_height) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					  def destroy_ui(self): | 
					 | 
					 | 
					 | 
					  def destroy_ui(self): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    self._cleanup_ui_recursive(self.layout, []) | 
					 | 
					 | 
					 | 
					    self._cleanup_ui_recursive(self.layout, []) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    self._cleanup_all_handlers() | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    self.active_panels.clear() | 
					 | 
					 | 
					 | 
					    self.active_panels.clear() | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					  def _cleanup_all_handlers(self): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    for handler_tag in list(self._created_handler_tags): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					      if dpg.does_item_exist(handler_tag): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        dpg.delete_item(handler_tag) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    self._created_handler_tags.clear() | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					  def _create_ui_recursive(self, layout: dict, parent_tag: str, path: list[int], width: int, height: int): | 
					 | 
					 | 
					 | 
					  def _create_ui_recursive(self, layout: dict, parent_tag: str, path: list[int], width: int, height: int): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    if layout["type"] == "panel": | 
					 | 
					 | 
					 | 
					    if layout["type"] == "panel": | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      self._create_panel_ui(layout, parent_tag, path, width, height) | 
					 | 
					 | 
					 | 
					      self._create_panel_ui(layout, parent_tag, path, width, height) | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					 | 
					@ -165,13 +237,14 @@ class PanelLayoutManager: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    panel = layout["panel"] | 
					 | 
					 | 
					 | 
					    panel = layout["panel"] | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    self.active_panels.append(panel) | 
					 | 
					 | 
					 | 
					    self.active_panels.append(panel) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    text_size = int(13 * self.scale) | 
					 | 
					 | 
					 | 
					    text_size = int(13 * self.scale) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    bar_height = (text_size + 24) if width < int(279 * self.scale + 64) else (text_size + 8)  # adjust height to allow for scrollbar | 
					 | 
					 | 
					 | 
					    bar_height = (text_size + 24) if width < int(329 * self.scale + 64) else (text_size + 8)  # adjust height to allow for scrollbar | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    with dpg.child_window(parent=parent_tag, border=False, width=-1, height=-1, no_scrollbar=True): | 
					 | 
					 | 
					 | 
					    with dpg.child_window(parent=parent_tag, border=False, width=-1, height=-1, no_scrollbar=True): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      with dpg.group(horizontal=True): | 
					 | 
					 | 
					 | 
					      with dpg.group(horizontal=True): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        with dpg.child_window(tag=panel_tag, width=-(text_size + 16), height=bar_height, horizontal_scrollbar=True, no_scroll_with_mouse=True, border=False): | 
					 | 
					 | 
					 | 
					        with dpg.child_window(tag=panel_tag, width=-(text_size + 16), height=bar_height, horizontal_scrollbar=True, no_scroll_with_mouse=True, border=False): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					          with dpg.group(horizontal=True): | 
					 | 
					 | 
					 | 
					          with dpg.group(horizontal=True): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					            dpg.add_input_text(default_value=panel.title, width=int(100 * self.scale), callback=lambda s, v: setattr(panel, "title", v)) | 
					 | 
					 | 
					 | 
					            # if you change the widths make sure to change the sum of widths (currently 329 * scale) | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					            dpg.add_input_text(default_value=panel.title, width=int(150 * self.scale), callback=lambda s, v: setattr(panel, "title", v)) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					            dpg.add_combo(items=["Time Series"], default_value="Time Series", width=int(100 * self.scale)) | 
					 | 
					 | 
					 | 
					            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(panel), width=int(40 * self.scale)) | 
					 | 
					 | 
					 | 
					            dpg.add_button(label="Clear", callback=lambda: self.clear_panel(panel), width=int(40 * self.scale)) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					            dpg.add_image_button(texture_tag="split_h_texture", callback=lambda: self.split_panel(path, 0), width=text_size, height=text_size) | 
					 | 
					 | 
					 | 
					            dpg.add_image_button(texture_tag="split_h_texture", callback=lambda: self.split_panel(path, 0), width=text_size, height=text_size) | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
					 | 
					@ -280,11 +353,16 @@ class PanelLayoutManager: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        handler_tag = f"{self._path_to_tag(path, f'grip_{i}')}_handler" | 
					 | 
					 | 
					 | 
					        handler_tag = f"{self._path_to_tag(path, f'grip_{i}')}_handler" | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        if dpg.does_item_exist(handler_tag): | 
					 | 
					 | 
					 | 
					        if dpg.does_item_exist(handler_tag): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					          dpg.delete_item(handler_tag) | 
					 | 
					 | 
					 | 
					          dpg.delete_item(handler_tag) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        self._created_handler_tags.discard(handler_tag) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      for i, child in enumerate(layout["children"]): | 
					 | 
					 | 
					 | 
					      for i, child in enumerate(layout["children"]): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        self._cleanup_ui_recursive(child, path + [i]) | 
					 | 
					 | 
					 | 
					        self._cleanup_ui_recursive(child, path + [i]) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					  def update_all_panels(self): | 
					 | 
					 | 
					 | 
					  def update_all_panels(self): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    if self._queue_resize: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					      if (size := dpg.get_item_rect_size(self.parent_tag)) != [0, 0]: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        self._queue_resize = False | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					        self._resize_splits_recursive(self.layout, [], *size) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    for panel in self.active_panels: | 
					 | 
					 | 
					 | 
					    for panel in self.active_panels: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      panel.update() | 
					 | 
					 | 
					 | 
					      panel.update() | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
					 | 
					@ -308,7 +386,7 @@ class PanelLayoutManager: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					            self._resize_splits_recursive(child_layout, child_path, child_width, child_height) | 
					 | 
					 | 
					 | 
					            self._resize_splits_recursive(child_layout, child_path, child_width, child_height) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    else:  # leaf node/panel - adjust bar height to allow for scrollbar | 
					 | 
					 | 
					 | 
					    else:  # leaf node/panel - adjust bar height to allow for scrollbar | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      panel_tag = self._path_to_tag(path, "panel") | 
					 | 
					 | 
					 | 
					      panel_tag = self._path_to_tag(path, "panel") | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      if width is not None and width < int(279 * self.scale + 64):  # scaled widths of the elements in top bar + fixed 8 padding on left and right of each item | 
					 | 
					 | 
					 | 
					      if width is not None and width < int(329 * self.scale + 64):  # scaled widths of the elements in top bar + fixed 8 padding on left and right of each item | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        dpg.configure_item(panel_tag, height=(int(13 * self.scale) + 24)) | 
					 | 
					 | 
					 | 
					        dpg.configure_item(panel_tag, height=(int(13 * self.scale) + 24)) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      else: | 
					 | 
					 | 
					 | 
					      else: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					        dpg.configure_item(panel_tag, height=(int(13 * self.scale) + 8)) | 
					 | 
					 | 
					 | 
					        dpg.configure_item(panel_tag, height=(int(13 * self.scale) + 8)) | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
					 | 
					@ -342,16 +420,18 @@ class PanelLayoutManager: | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					  def _create_grip(self, parent_tag: str, path: list[int], grip_index: int, orientation: int): | 
					 | 
					 | 
					 | 
					  def _create_grip(self, parent_tag: str, path: list[int], grip_index: int, orientation: int): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    grip_tag = self._path_to_tag(path, f"grip_{grip_index}") | 
					 | 
					 | 
					 | 
					    grip_tag = self._path_to_tag(path, f"grip_{grip_index}") | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    handler_tag = f"{grip_tag}_handler" | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    width, height = [(self.grip_size, -1), (-1, self.grip_size)][orientation] | 
					 | 
					 | 
					 | 
					    width, height = [(self.grip_size, -1), (-1, self.grip_size)][orientation] | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    with dpg.child_window(tag=grip_tag, parent=parent_tag, width=width, height=height, no_scrollbar=True, border=False): | 
					 | 
					 | 
					 | 
					    with dpg.child_window(tag=grip_tag, parent=parent_tag, width=width, height=height, no_scrollbar=True, border=False): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      button_tag = dpg.add_button(label="", width=-1, height=-1) | 
					 | 
					 | 
					 | 
					      button_tag = dpg.add_button(label="", width=-1, height=-1) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    with dpg.item_handler_registry(tag=f"{grip_tag}_handler"): | 
					 | 
					 | 
					 | 
					    with dpg.item_handler_registry(tag=handler_tag): | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      user_data = (path, grip_index, orientation) | 
					 | 
					 | 
					 | 
					      user_data = (path, grip_index, orientation) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      dpg.add_item_active_handler(callback=self._on_grip_drag, user_data=user_data) | 
					 | 
					 | 
					 | 
					      dpg.add_item_active_handler(callback=self._on_grip_drag, user_data=user_data) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					      dpg.add_item_deactivated_handler(callback=self._on_grip_end, user_data=user_data) | 
					 | 
					 | 
					 | 
					      dpg.add_item_deactivated_handler(callback=self._on_grip_end, user_data=user_data) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    dpg.bind_item_handler_registry(button_tag, f"{grip_tag}_handler") | 
					 | 
					 | 
					 | 
					    dpg.bind_item_handler_registry(button_tag, handler_tag) | 
				
			
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					 | 
					    self._created_handler_tags.add(handler_tag) | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					
 | 
					 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					  def _on_grip_drag(self, sender, app_data, user_data): | 
					 | 
					 | 
					 | 
					  def _on_grip_drag(self, sender, app_data, user_data): | 
				
			
			
		
	
		
		
			
				
					
					 | 
					 | 
					 | 
					    path, grip_index, orientation = user_data | 
					 | 
					 | 
					 | 
					    path, grip_index, orientation = user_data | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
						
					 | 
					 | 
					
  |