progress on fixing up gui code

This commit is contained in:
2026-05-12 15:20:34 -04:00
parent fb45b44824
commit c4e1cca66b
6 changed files with 198 additions and 197 deletions
+25 -1
View File
@@ -131,7 +131,31 @@ max-locals = 8
max-args = 5 max-args = 5
``` ```
## 11. Structural Dependency Mapping (SDM) ### 11. ImGui Defer Patterns
To prevent `PopID` or `End` leaks in immediate-mode rendering, and to keep code flat (0-1 levels of nesting) for AI agents, use the following patterns:
- **The Context Manager Pattern (Mandatory for complex blocks):**
Wrap all `Begin/End` blocks in `imscope` context managers (from `src/imgui_scopes.py`).
```python
with imscope.window("My Window") as (exp, opened):
if exp:
imgui.text("Hello")
with imscope.tab_item("My Tab") as (exp, _):
if exp:
self._render_tab_content()
```
This adds only 1 space of indentation (project standard) and guarantees the corresponding `End` is called even on early returns or exceptions. **Crucial:** Always check the `exp` (expanded/visible) state before rendering content to avoid ID conflicts and performance overhead.
- **The Flat Dispatch Pattern (Recommended for the main loop):**
To avoid nesting multiple window checks, use a dispatch helper that encapsulates the state check and the scope.
```python
self._render_window_if_open("My Window", self._render_my_panel)
```
This keeps the main GUI loop as a flat sequence of declarative calls.
## 12. Structural Dependency Mapping (SDM)
To assist AI agents in evaluating refactoring impact across dynamic codebases, all major definitions SHOULD include terse SDM tags at the end of their docstrings. To assist AI agents in evaluating refactoring impact across dynamic codebases, all major definitions SHOULD include terse SDM tags at the end of their docstrings.
+4
View File
@@ -54,6 +54,10 @@ This file tracks all major tracks for the project. Each track has its own detail
*Link: [./tracks/context_comp_presets_20260510/](./tracks/context_comp_presets_20260510/)* *Link: [./tracks/context_comp_presets_20260510/](./tracks/context_comp_presets_20260510/)*
*Goal: Implement Context Preset save/load with validation, and Context Preview before sending to agent.* *Goal: Implement Context Preset save/load with validation, and Context Preview before sending to agent.*
12. [ ] **Track: GUI Architecture Refinement & AI-Friendliness**
*Link: [./tracks/gui_architecture_refinement_20260512/](./tracks/gui_architecture_refinement_20260512/)*
*Goal: Reduce nesting and compactness of ImGui code in `gui_2.py`, and formalize ImGui Defer patterns.*
--- ---
## Hot Reload Feature ## Hot Reload Feature
@@ -0,0 +1,45 @@
# Plan: GUI Architecture Refinement & AI-Friendliness
**Track ID:** gui_architecture_refinement_20260512
**Status:** [~] Draft
## Objective
Reduce nesting and improve compactness of ImGui code in `gui_2.py` to make it more AI-friendly. Formalize the "defer/scope" patterns (inspired by Go's `defer` and Ryan Fleury's macros) in the project style guides to prevent `PopID` / `End` leaks.
## Background & Motivation
The main GUI render loop (`_gui_func__abusrd_try_scope`) has grown to over 600 lines with deep nesting. Raw `imgui.begin()` and `imgui.end()` calls are prone to leaks if an early return occurs or if the return value of `begin` is ignored. While `imscope` context managers solve the leak issue, they still introduce nesting. We need a way to keep the code extremely flat (0-1 levels of nesting) while maintaining safety.
## Proposed Solution
### 1. Update Style Guides (`python.md` & `workflow.md`)
Introduce a new section explicitly defining the "ImGui Defer Patterns":
- **The Context Manager Pattern:** Use `with imscope.window("Name"):` to automatically handle `End()`. This adds only 1 space of indentation (per project rules).
- **The Flat Dispatch Pattern:** To avoid nesting multiple windows, use dispatch helpers like `self._render_window_if_open(name, render_func)` which encapsulate the state-checking, `Begin`, `End`, and execution logic.
### 2. Implement Flat Dispatch Helper
Create a helper method in `App`:
```python
def _render_window_if_open(self, name: str, render_func: Callable[[], None], flag_condition: bool = True) -> None:
if not flag_condition or not self.show_windows.get(name, False): return
with imscope.window(name, self.show_windows[name]) as (exp, opened):
self.show_windows[name] = bool(opened)
if exp: render_func()
```
### 3. Refactor `gui_2.py`
- Extract inline hub definitions (e.g., "Operations Hub", "Discussion Hub", "AI Settings") from `_gui_func__abusrd_try_scope` into dedicated methods (`_render_operations_hub`, etc.).
- Replace the massive `if self.show_windows.get...` blocks in `_gui_func__abusrd_try_scope` with a flat sequence of `_render_window_if_open` calls.
- Rename `_gui_func__abusrd_try_scope` to a cleaner name (e.g., `_gui_func_body`) once stabilized.
## Implementation Steps
1. [x] Edit `conductor/code_styleguides/python.md` to add "ImGui Defer Patterns" under the "AI-Agent Specific Conventions" or "Anti-OOP" section.
2. [x] Edit `conductor/workflow.md` to reference the mandatory use of `imscope` or dispatch helpers for ImGui code.
3. [x] Add `_render_window_if_open` to `gui_2.py`.
4. [x] Extract `_render_operations_hub`, `_render_discussion_hub`, and `_render_ai_settings_hub` in `gui_2.py`.
5. [x] Flatten `_gui_func__abusrd_try_scope` using the new helper.
## Verification & Testing
- Ensure the app launches successfully without `PopID` errors.
- Verify that toggling windows via the menu still opens and closes them correctly.
- Run `uv run pytest tests/test_gui_startup_smoke.py` and `uv run pytest tests/test_gui_window_controls.py`.
+1
View File
@@ -9,6 +9,7 @@
- Use `./scripts/ai_style_formatter.py` for formatting validation - Use `./scripts/ai_style_formatter.py` for formatting validation
- **NO COMMENTS** unless explicitly requested - **NO COMMENTS** unless explicitly requested
- Type hints required for all public functions - Type hints required for all public functions
- **ImGui Defer Patterns:** Use `imscope` context managers or `_render_window_if_open` dispatch helpers to prevent resource leaks and keep the main loop flat. See `conductor/code_styleguides/python.md` for details.
### CRITICAL: Native Edit Tool Destroys Indentation ### CRITICAL: Native Edit Tool Destroys Indentation
+91 -182
View File
@@ -653,6 +653,67 @@ class App:
imgui.pop_style_var(2) imgui.pop_style_var(2)
imgui.pop_id() imgui.pop_id()
def _render_window_if_open(self, name: str, render_func: Callable[[], None], flag_condition: bool = True) -> None:
"""Helper to render a window only if its toggle is active."""
if not flag_condition or not self.show_windows.get(name, False): return
with imscope.window(name, self.show_windows[name]) as (exp, opened):
self.show_windows[name] = bool(opened)
if exp: render_func()
def _render_project_settings_hub(self) -> None:
with imscope.tab_bar('context_hub_tabs'):
with imscope.tab_item('Projects'): self._render_projects_panel()
with imscope.tab_item('Paths'): self._render_paths_panel()
def _render_ai_settings_hub(self) -> None:
self._render_persona_selector_panel()
if imgui.collapsing_header("Provider & Model"): self._render_provider_panel()
if imgui.collapsing_header("System Prompts"): self._render_system_prompts_panel()
if imgui.collapsing_header("RAG Settings"): self._render_rag_panel()
self._render_agent_tools_panel()
def _render_discussion_hub(self) -> None:
with imscope.tab_bar("discussion_hub_tabs"):
with imscope.tab_item("Discussion"): self._render_discussion_tab()
with imscope.tab_item("Context Composition"): self._render_context_composition_panel()
with imscope.tab_item("Snapshot"): self._render_snapshot_tab()
with imscope.tab_item("Takes"): self._render_takes_panel()
def _render_operations_hub(self) -> None:
imgui.push_style_var(imgui.StyleVar_.item_spacing, imgui.ImVec2(10, 4))
ch1, self.ui_separate_tool_calls_panel = imgui.checkbox("Pop Out Tool Calls", self.ui_separate_tool_calls_panel)
if ch1: self.show_windows["Tool Calls"] = self.ui_separate_tool_calls_panel
imgui.same_line()
ch2, self.ui_separate_usage_analytics = imgui.checkbox("Pop Out Usage Analytics", self.ui_separate_usage_analytics)
if ch2: self.show_windows["Usage Analytics"] = self.ui_separate_usage_analytics
imgui.same_line()
ch3, self.ui_separate_external_tools = imgui.checkbox('Pop Out External Tools', self.ui_separate_external_tools)
if ch3: self.show_windows['External Tools'] = self.ui_separate_external_tools
imgui.pop_style_var()
show_tc_tab, show_usage_tab = not self.ui_separate_tool_calls_panel, not self.ui_separate_usage_analytics
with imscope.tab_bar("ops_tabs"):
with imscope.tab_item("Comms History"): self._render_comms_history_panel()
if show_tc_tab:
with imscope.tab_item("Tool Calls"): self._render_tool_calls_panel()
if show_usage_tab:
with imscope.tab_item("Usage Analytics"): self._render_usage_analytics_panel()
if not self.ui_separate_external_tools:
with imscope.tab_item("External Tools"):
self._render_external_tools_panel()
imgui.separator(); imgui.text("")
try: self._render_external_editor_panel()
except Exception as e: imgui.text_colored(vec4(1, 0.3, 0.3, 1), f"Error: {str(e)}")
with imscope.tab_item("Workspace Layouts"):
imgui.text("Experimental: Auto-switch layout by Tier")
ch, self.controller.ui_auto_switch_layout = imgui.checkbox("Enable Auto-Switch", self.controller.ui_auto_switch_layout)
if self.controller.ui_auto_switch_layout:
imgui.separator(); imgui.text("Tier Bindings (select profile for each tier)")
profiles = [""] + [p.name for p in self.controller.workspace_profiles.values()]
for t in ["Tier 1", "Tier 2", "Tier 3", "Tier 4"]:
curr = self.controller.ui_tier_layout_bindings.get(t, ""); idx = profiles.index(curr) if curr in profiles else 0
ch_combo, new_idx = imgui.combo(t, idx, profiles)
if ch_combo: self.controller.ui_tier_layout_bindings[t] = profiles[new_idx]
def _show_menus(self) -> None: def _show_menus(self) -> None:
""" """
[C: tests/test_gui_window_controls.py:test_gui_window_controls_minimize_maximize_close] [C: tests/test_gui_window_controls.py:test_gui_window_controls_minimize_maximize_close]
@@ -732,16 +793,16 @@ class App:
if hwnd: if hwnd:
btn_w = 40 btn_w = 40
display_w = imgui.get_io().display_size.x # Use window width (points) instead of display_size (pixels) for correct scaling
right_x = display_w - (btn_w * 3) window_w = imgui.get_window_width()
bar_h = imgui.get_window_height()
right_x = window_w - (btn_w * 3)
# Drag area check using an explicit invisible button spanning the empty space # Drag area check using an explicit invisible button spanning the empty space
curr_x = imgui.get_cursor_pos_x() curr_x = imgui.get_cursor_pos_x()
drag_w = right_x - curr_x drag_w = right_x - curr_x
if drag_w > 0: if drag_w > 0:
# Use a small positive height to satisfy IM_ASSERT(size_arg.y != 0.0f) imgui.invisible_button("##drag_area", (drag_w, bar_h))
# The menu bar naturally constrains the hit box height anyway.
imgui.invisible_button("##drag_area", (drag_w, 20.0))
if imgui.is_item_active() and imgui.is_mouse_dragging(0): if imgui.is_item_active() and imgui.is_mouse_dragging(0):
# CRITICAL: We must reset ImGui's mouse_down state BEFORE passing control to Windows. # CRITICAL: We must reset ImGui's mouse_down state BEFORE passing control to Windows.
# Otherwise, the Windows modal drag loop swallows the WM_LBUTTONUP event, # Otherwise, the Windows modal drag loop swallows the WM_LBUTTONUP event,
@@ -757,17 +818,18 @@ class App:
except Exception: except Exception:
is_max = False is_max = False
imgui.set_cursor_pos_x(right_x) # Explicitly set Y to 0 and match button height to bar height for perfect alignment
if imgui.button("_", (btn_w, 0)): imgui.set_cursor_pos((right_x, 0))
if imgui.button("_", (btn_w, bar_h)):
win32gui.ShowWindow(hwnd, win32con.SW_MINIMIZE) win32gui.ShowWindow(hwnd, win32con.SW_MINIMIZE)
imgui.set_cursor_pos_x(right_x + btn_w) imgui.set_cursor_pos((right_x + btn_w, 0))
if imgui.button("[=]" if is_max else "[]", (btn_w, 0)): if imgui.button("[=]" if is_max else "[]", (btn_w, bar_h)):
win32gui.ShowWindow(hwnd, win32con.SW_RESTORE if is_max else win32con.SW_MAXIMIZE) win32gui.ShowWindow(hwnd, win32con.SW_RESTORE if is_max else win32con.SW_MAXIMIZE)
imgui.set_cursor_pos_x(right_x + btn_w * 2) imgui.set_cursor_pos((right_x + btn_w * 2, 0))
imgui.push_style_color(imgui.Col_.button_hovered, vec4(200, 50, 50, 255)) imgui.push_style_color(imgui.Col_.button_hovered, vec4(200, 50, 50, 255))
if imgui.button("X", (btn_w, 0)): if imgui.button("X", (btn_w, bar_h)):
win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0) win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
imgui.pop_style_color() imgui.pop_style_color()
@@ -985,182 +1047,29 @@ class App:
else: self._tool_log_cache = log_raw else: self._tool_log_cache = log_raw
self._tool_log_dirty = False self._tool_log_dirty = False
#region: Project Settings self._render_window_if_open("Project Settings", self._render_project_settings_hub)
if self.show_windows.get("Project Settings", False): self._render_window_if_open("Files & Media", self._render_files_and_media)
with imscope.window("Project Settings", self.show_windows["Project Settings"]) as (exp, opened): self._render_window_if_open("AI Settings", self._render_ai_settings_hub)
self.show_windows["Project Settings"] = bool(opened) self._render_window_if_open("Usage Analytics", self._render_usage_analytics_panel, self.ui_separate_usage_analytics)
if exp and imscope.tab_bar('context_hub_tabs'): self._render_window_if_open("MMA Dashboard", self._render_mma_dashboard)
if imscope.tab_item('Projects'): self._render_projects_panel() self._render_window_if_open("Task DAG", self._render_task_dag_panel, self.ui_separate_task_dag)
if imscope.tab_item('Paths'): self._render_paths_panel()
#endregion: Project Settings
#region: Files & Media window self._render_window_if_open("Tier 1: Strategy", lambda: self._render_tier_stream_panel("Tier 1", "Tier 1"), self.ui_separate_tier1)
if self.show_windows.get("Files & Media", False): self._render_window_if_open("Tier 2: Tech Lead", lambda: self._render_tier_stream_panel("Tier 2", "Tier 2 (Tech Lead)"), self.ui_separate_tier2)
exp, opened = imgui.begin("Files & Media", self.show_windows["Files & Media"]) self._render_window_if_open("Tier 3: Workers", lambda: self._render_tier_stream_panel("Tier 3", None), self.ui_separate_tier3)
self.show_windows["Files & Media"] = bool(opened) self._render_window_if_open("Tier 4: QA", lambda: self._render_tier_stream_panel("Tier 4", "Tier 4 (QA)"), self.ui_separate_tier4)
if opened and exp: self._render_files_and_media()
imgui.end()
#endregion: Files & Media window
#region: AI Settings
if self.show_windows.get("AI Settings", False):
with imscope.window("AI Settings", self.show_windows["AI Settings"]) as (exp, opened):
self.show_windows["AI Settings"] = bool(opened)
if exp:
self._render_persona_selector_panel()
if imgui.collapsing_header("Provider & Model"): self._render_provider_panel()
if imgui.collapsing_header("System Prompts"): self._render_system_prompts_panel()
if imgui.collapsing_header("RAG Settings"): self._render_rag_panel()
self._render_agent_tools_panel()
#endregion: AI Settings
if self.ui_separate_usage_analytics and self.show_windows.get("Usage Analytics", False):
exp, opened = imgui.begin("Usage Analytics", self.show_windows["Usage Analytics"])
self.show_windows["Usage Analytics"] = bool(opened)
if exp:
self._render_usage_analytics_panel()
imgui.end()
if self.show_windows.get("MMA Dashboard", False):
exp, opened = imgui.begin("MMA Dashboard", self.show_windows["MMA Dashboard"])
self.show_windows["MMA Dashboard"] = bool(opened)
if exp:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_mma_dashboard")
self._render_mma_dashboard()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_mma_dashboard")
imgui.end()
#region: Seprate Task Dag to Tier 4
if self.ui_separate_task_dag and self.show_windows.get("Task DAG", False):
exp, opened = imgui.begin("Task DAG", self.show_windows["Task DAG"])
self.show_windows["Task DAG"] = bool(opened)
if exp: self._render_task_dag_panel()
imgui.end()
if self.ui_separate_tier1 and self.show_windows.get("Tier 1: Strategy", False):
exp, opened = imgui.begin("Tier 1: Strategy", self.show_windows["Tier 1: Strategy"])
self.show_windows["Tier 1: Strategy"] = bool(opened)
if exp: self._render_tier_stream_panel("Tier 1", "Tier 1")
imgui.end()
if self.ui_separate_tier2 and self.show_windows.get("Tier 2: Tech Lead", False):
exp, opened = imgui.begin("Tier 2: Tech Lead", self.show_windows["Tier 2: Tech Lead"])
self.show_windows["Tier 2: Tech Lead"] = bool(opened)
if exp: self._render_tier_stream_panel("Tier 2", "Tier 2 (Tech Lead)")
imgui.end()
if self.ui_separate_tier3 and self.show_windows.get("Tier 3: Workers", False):
exp, opened = imgui.begin("Tier 3: Workers", self.show_windows["Tier 3: Workers"])
self.show_windows["Tier 3: Workers"] = bool(opened)
if exp: self._render_tier_stream_panel("Tier 3", None)
imgui.end()
if self.ui_separate_tier4 and self.show_windows.get("Tier 4: QA", False):
exp, opened = imgui.begin("Tier 4: QA", self.show_windows["Tier 4: QA"])
self.show_windows["Tier 4: QA"] = bool(opened)
if exp: self._render_tier_stream_panel("Tier 4", "Tier 4 (QA)")
imgui.end()
#endregion: Separate Task Dag to Tier 4
if self.show_windows.get("Theme", False): self._render_theme_panel() if self.show_windows.get("Theme", False): self._render_theme_panel()
#region: Discussion Hub self._render_window_if_open("Discussion Hub", self._render_discussion_hub)
if self.show_windows.get("Discussion Hub", False): self._render_window_if_open("Operations Hub", self._render_operations_hub)
with imscope.window("Discussion Hub", self.show_windows["Discussion Hub"]) as (exp, opened):
self.show_windows["Discussion Hub"] = bool(opened)
if exp:
if imscope.tab_bar("discussion_hub_tabs"):
if imscope.tab_item("Discussion"):
self._render_discussion_tab()
if imscope.tab_item("Context Composition"):
self._render_context_composition_panel()
if imscope.tab_item("Snapshot"):
self._render_snapshot_tab()
if imscope.tab_item("Takes"):
self._render_takes_panel()
#endregion: Discussion Hub
#region: Operations Hub self._render_window_if_open("Message", self._render_message_panel, self.ui_separate_message_panel)
if self.show_windows.get("Operations Hub", False): self._render_window_if_open("Response", self._render_response_panel, self.ui_separate_response_panel)
exp, opened = imgui.begin("Operations Hub", self.show_windows["Operations Hub"]) self._render_window_if_open("Tool Calls", self._render_tool_calls_panel, self.ui_separate_tool_calls_panel)
self.show_windows["Operations Hub"] = bool(opened) self._render_window_if_open("External Tools", self._render_external_tools_panel, self.ui_separate_external_tools)
if exp: self._render_window_if_open("Log Management", self._render_log_management)
imgui.push_style_var(imgui.StyleVar_.item_spacing, imgui.ImVec2(10, 4)) self._render_window_if_open("Diagnostics", self._render_diagnostics_panel)
ch1, self.ui_separate_tool_calls_panel = imgui.checkbox("Pop Out Tool Calls", self.ui_separate_tool_calls_panel)
if ch1: self.show_windows["Tool Calls"] = self.ui_separate_tool_calls_panel
imgui.same_line()
ch2, self.ui_separate_usage_analytics = imgui.checkbox("Pop Out Usage Analytics", self.ui_separate_usage_analytics)
if ch2: self.show_windows["Usage Analytics"] = self.ui_separate_usage_analytics
imgui.same_line()
ch3, self.ui_separate_external_tools = imgui.checkbox('Pop Out External Tools', self.ui_separate_external_tools)
if ch3: self.show_windows['External Tools'] = self.ui_separate_external_tools
imgui.pop_style_var()
show_tc_tab = not self.ui_separate_tool_calls_panel
show_usage_tab = not self.ui_separate_usage_analytics
with imscope.tab_bar("ops_tabs"):
with imscope.tab_item("Comms History"): self._render_comms_history_panel()
if show_tc_tab:
with imscope.tab_item("Tool Calls"): self._render_tool_calls_panel()
if show_usage_tab:
with imscope.tab_item("Usage Analytics"): self._render_usage_analytics_panel()
if not self.ui_separate_external_tools:
with imscope.tab_item("External Tools"):
self._render_external_tools_panel()
imgui.separator()
imgui.text("")
try:
self._render_external_editor_panel()
except Exception as e:
imgui.text_colored(vec4(1, 0.3, 0.3, 1), f"Error: {str(e)}")
with imscope.tab_item("Workspace Layouts"):
imgui.text("Experimental: Auto-switch layout by Tier")
ch, self.controller.ui_auto_switch_layout = imgui.checkbox("Enable Auto-Switch", self.controller.ui_auto_switch_layout)
if self.controller.ui_auto_switch_layout:
imgui.separator()
imgui.text("Tier Bindings (select profile for each tier)")
profiles = [""] + [p.name for p in self.controller.workspace_profiles.values()]
for t in ["Tier 1", "Tier 2", "Tier 3", "Tier 4"]:
curr = self.controller.ui_tier_layout_bindings.get(t, "")
idx = profiles.index(curr) if curr in profiles else 0
ch_combo, new_idx = imgui.combo(t, idx, profiles)
if ch_combo: self.controller.ui_tier_layout_bindings[t] = profiles[new_idx]
imgui.end()
#endregion: Operations Hub
#region: Separate Message, Response, Tool Calls, External Tools
if self.ui_separate_message_panel and self.show_windows.get("Message", False):
exp, opened = imgui.begin("Message", self.show_windows["Message"])
self.show_windows["Message"] = bool(opened)
if exp: self._render_message_panel()
imgui.end()
if self.ui_separate_response_panel and self.show_windows.get("Response", False):
exp, opened = imgui.begin("Response", self.show_windows["Response"])
self.show_windows["Response"] = bool(opened)
if exp: self._render_response_panel()
imgui.end()
if self.ui_separate_tool_calls_panel and self.show_windows.get("Tool Calls", False):
exp, opened = imgui.begin("Tool Calls", self.show_windows["Tool Calls"])
self.show_windows["Tool Calls"] = bool(opened)
if exp: self._render_tool_calls_panel()
imgui.end()
if self.ui_separate_external_tools and self.show_windows.get('External Tools', False):
exp, opened = imgui.begin('External Tools', self.show_windows['External Tools'])
self.show_windows['External Tools'] = bool(opened)
if exp: self._render_external_tools_panel()
imgui.end()
if self.show_windows.get("Log Management", False):
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_log_management")
self._render_log_management()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_log_management")
#endregion: Separate Message, Response, Tool Calls, External Tools
if self.show_windows.get("Diagnostics", False): self._render_diagnostics_panel()
self.perf_monitor.end_frame() self.perf_monitor.end_frame()
+32 -14
View File
@@ -24,7 +24,8 @@ class _ScopeChild:
self._sy = size_y self._sy = size_y
self._flags = flags self._flags = flags
def __enter__(self): def __enter__(self):
return imgui.begin_child(self._id, self._sx, self._sy, self._flags) res = imgui.begin_child(self._id, self._sx, self._sy, self._flags)
return res
def __exit__(self, *args): def __exit__(self, *args):
imgui.end_child() imgui.end_child()
return False return False
@@ -35,38 +36,51 @@ class _ScopeTable:
self._name = name self._name = name
self._columns = columns self._columns = columns
self._flags = flags self._flags = flags
self._active = False
def __enter__(self): def __enter__(self):
return imgui.begin_table(self._name, self._columns, self._flags) self._active = imgui.begin_table(self._name, self._columns, self._flags)
return self._active
def __exit__(self, *args): def __exit__(self, *args):
imgui.end_table() if self._active:
imgui.end_table()
return False return False
def menu_bar(): return _ScopeMenuBar() def menu_bar(): return _ScopeMenuBar()
class _ScopeMenuBar: class _ScopeMenuBar:
def __init__(self):
self._active = False
def __enter__(self): def __enter__(self):
return imgui.begin_menu_bar() self._active = imgui.begin_menu_bar()
return self._active
def __exit__(self, *args): def __exit__(self, *args):
imgui.end_menu_bar() if self._active:
imgui.end_menu_bar()
return False return False
def menu(label: str): return _ScopeMenu(label) def menu(label: str): return _ScopeMenu(label)
class _ScopeMenu: class _ScopeMenu:
def __init__(self, label: str): def __init__(self, label: str):
self._label = label self._label = label
self._active = False
def __enter__(self): def __enter__(self):
return imgui.begin_menu(self._label) self._active = imgui.begin_menu(self._label)
return self._active
def __exit__(self, *args): def __exit__(self, *args):
imgui.end_menu() if self._active:
imgui.end_menu()
return False return False
def popup(id_str: str): return _ScopePopup(id_str) def popup(id_str: str): return _ScopePopup(id_str)
class _ScopePopup: class _ScopePopup:
def __init__(self, id_str: str): def __init__(self, id_str: str):
self._id = id_str self._id = id_str
self._active = False
def __enter__(self): def __enter__(self):
return imgui.begin_popup(self._id) self._active = imgui.begin_popup(self._id)
return self._active
def __exit__(self, *args): def __exit__(self, *args):
imgui.end_popup() if self._active:
imgui.end_popup()
return False return False
def tooltip(): return _ScopeTooltip() def tooltip(): return _ScopeTooltip()
@@ -120,10 +134,13 @@ class _ScopeTabBar:
def __init__(self, id_str: str, flags: int): def __init__(self, id_str: str, flags: int):
self._id = id_str self._id = id_str
self._flags = flags self._flags = flags
self._active = False
def __enter__(self): def __enter__(self):
return imgui.begin_tab_bar(self._id, self._flags) self._active = imgui.begin_tab_bar(self._id, self._flags)
return self._active
def __exit__(self, *args): def __exit__(self, *args):
imgui.end_tab_bar() if self._active:
imgui.end_tab_bar()
return False return False
def tab_item(label: str, flags: int = 0): return _ScopeTabItem(label, flags) def tab_item(label: str, flags: int = 0): return _ScopeTabItem(label, flags)
@@ -131,11 +148,12 @@ class _ScopeTabItem:
def __init__(self, label: str, flags: int): def __init__(self, label: str, flags: int):
self._label = label self._label = label
self._flags = flags self._flags = flags
self._expanded = False
self._open = None self._open = None
def __enter__(self): def __enter__(self):
exp, self._open = imgui.begin_tab_item(self._label, None, self._flags) self._expanded, self._open = imgui.begin_tab_item(self._label, None, self._flags)
return exp, self._open return self._expanded, self._open
def __exit__(self, *args): def __exit__(self, *args):
if self._open: if self._expanded:
imgui.end_tab_item() imgui.end_tab_item()
return False return False