feat(gui): Add imgui_scopes with per-type context managers for begin/end pairs

This commit is contained in:
2026-05-12 00:13:22 -04:00
parent b9c1b63f8d
commit aed3ebe063
2 changed files with 146 additions and 81 deletions
+27 -31
View File
@@ -45,7 +45,7 @@ else:
from pydantic import BaseModel from pydantic import BaseModel
from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed, imgui_color_text_edit as ced from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed, imgui_color_text_edit as ced
from src.imgui_scopes import imgui_window from src.imgui_scopes import imgui_begin, imgui_begin_child, imgui_begin_table, imgui_begin_menu_bar, imgui_begin_menu, imgui_begin_popup, imgui_begin_tooltip, imgui_begin_group, node_begin
COMMS_CLAMP_CHARS: int = 300 COMMS_CLAMP_CHARS: int = 300
@@ -779,43 +779,38 @@ class App:
[C: tests/test_shader_live_editor.py:test_shader_live_editor_renders] [C: tests/test_shader_live_editor.py:test_shader_live_editor_renders]
""" """
if self.show_windows.get('Shader Editor', False): if self.show_windows.get('Shader Editor', False):
with imgui_window('Shader Editor', self.show_windows['Shader Editor']) as window_result: _, opened = imgui_begin('Shader Editor', self.show_windows['Shader Editor'])
if window_result: self.show_windows['Shader Editor'] = bool(opened)
changed_crt, self.shader_uniforms['crt'] = imgui.slider_float('CRT Curvature', self.shader_uniforms['crt'], 0.0, 2.0) if opened:
changed_scan, self.shader_uniforms['scanline'] = imgui.slider_float('Scanline Intensity', self.shader_uniforms['scanline'], 0.0, 1.0) changed_crt, self.shader_uniforms['crt'] = imgui.slider_float('CRT Curvature', self.shader_uniforms['crt'], 0.0, 2.0)
changed_bloom, self.shader_uniforms['bloom'] = imgui.slider_float('Bloom Threshold', self.shader_uniforms['bloom'], 0.0, 1.0) changed_scan, self.shader_uniforms['scanline'] = imgui.slider_float('Scanline Intensity', self.shader_uniforms['scanline'], 0.0, 1.0)
changed_bloom, self.shader_uniforms['bloom'] = imgui.slider_float('Bloom Threshold', self.shader_uniforms['bloom'], 0.0, 1.0)
def _render_history_window(self) -> None: def _render_history_window(self) -> None:
if not self.show_windows.get('Undo/Redo History', False): if not self.show_windows.get('Undo/Redo History', False):
return return
with imgui_window("Undo/Redo History", self.show_windows['Undo/Redo History']) as (exp, opened): with imgui_begin("Undo/Redo History", self.show_windows['Undo/Redo History']) as (exp, opened):
self.show_windows['Undo/Redo History'] = bool(opened) self.show_windows['Undo/Redo History'] = bool(opened)
if exp: if exp:
if imgui.button("Undo") and self.history.can_undo: if imgui.button("Undo") and self.history.can_undo: self._handle_undo(); imgui.same_line()
self._handle_undo() if imgui.button("Redo") and self.history.can_redo: self._handle_redo()
imgui.same_line()
if imgui.button("Redo") and self.history.can_redo:
self._handle_redo()
imgui.separator() imgui.separator()
imgui.begin_child("history_list", imgui.ImVec2(0, 0), True) with imgui_begin_child("history_list", imgui.ImVec2(0, 0), True):
history = self.history.get_history() history = self.history.get_history()
if not history: if not history: imgui.text("No history available.")
imgui.text("No history available.") else:
else: for i, entry in enumerate(reversed(history)):
for i, entry in enumerate(reversed(history)): actual_idx = len(history) - 1 - i
actual_idx = len(history) - 1 - i desc = entry.get("description", "UI Change")
desc = entry.get("description", "UI Change") ts = entry.get("timestamp", 0.0)
ts = entry.get("timestamp", 0.0) import datetime
import datetime ts_str = datetime.datetime.fromtimestamp(ts).strftime("%H:%M:%S")
ts_str = datetime.datetime.fromtimestamp(ts).strftime("%H:%M:%S")
label = f"[{ts_str}] {desc}##{actual_idx}" label = f"[{ts_str}] {desc}##{actual_idx}"
_, selected = imgui.selectable(label, False) _, selected = imgui.selectable(label, False)
if selected: if selected:
self._handle_jump_to_history(actual_idx) self._handle_jump_to_history(actual_idx)
imgui.end_child()
def _gui_func(self) -> None: def _gui_func(self) -> None:
self._render_custom_title_bar() self._render_custom_title_bar()
@@ -918,7 +913,7 @@ class App:
#region: Project Settings #region: Project Settings
if self.show_windows.get("Project Settings", False): if self.show_windows.get("Project Settings", False):
with imgui_window("Project Settings", self.show_windows["Project Settings"]) as (exp, opened): with imgui_begin("Project Settings", self.show_windows["Project Settings"]) as (exp, opened):
self.show_windows["Project Settings"] = bool(opened) self.show_windows["Project Settings"] = bool(opened)
if exp: if exp:
if imgui.begin_tab_bar('context_hub_tabs'): if imgui.begin_tab_bar('context_hub_tabs'):
@@ -1053,7 +1048,7 @@ class App:
#region: AI Settings #region: AI Settings
if self.show_windows.get("AI Settings", False): if self.show_windows.get("AI Settings", False):
with imgui_window("AI Settings", self.show_windows["AI Settings"]) as (exp, opened): with imgui_begin("AI Settings", self.show_windows["AI Settings"]) as (exp, opened):
self.show_windows["AI Settings"] = bool(opened) self.show_windows["AI Settings"] = bool(opened)
if exp: if exp:
self._render_persona_selector_panel() self._render_persona_selector_panel()
@@ -1064,6 +1059,7 @@ class App:
if imgui.collapsing_header("RAG Settings"): if imgui.collapsing_header("RAG Settings"):
self._render_rag_panel() self._render_rag_panel()
self._render_agent_tools_panel() self._render_agent_tools_panel()
if self.ui_separate_usage_analytics and self.show_windows.get("Usage Analytics", False): 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"]) exp, opened = imgui.begin("Usage Analytics", self.show_windows["Usage Analytics"])
self.show_windows["Usage Analytics"] = bool(opened) self.show_windows["Usage Analytics"] = bool(opened)
+118 -49
View File
@@ -3,76 +3,145 @@ from imgui_bundle import imgui
from imgui_bundle import imgui_node_editor from imgui_bundle import imgui_node_editor
class ImGuiScope: def imgui_begin(name: str, visible: bool = True, flags: int = 0):
_entered: bool return _ImguiBegin(name, visible, flags)
_opened: bool | tuple
_begin_fn: object
_end_fn: object
_args: tuple[object, ...]
_kwargs: dict[str, object]
def __init__(self, begin_fn: object, end_fn: object, *args: object, **kwargs: object) -> None:
self._begin_fn = begin_fn
self._end_fn = end_fn
self._args = args
self._kwargs = kwargs
self._opened = False
self._entered = False
def __enter__(self) -> bool | tuple: class _ImguiBegin:
self._opened = self._begin_fn(*self._args, **self._kwargs) def __init__(self, name: str, visible: bool, flags: int):
if isinstance(self._opened, tuple): self._name = name
self._entered = bool(self._opened[0]) self._visible = visible
return self._opened self._flags = flags
else: self._result = None
self._entered = bool(self._opened)
return self._opened
def __exit__(self, *args: object) -> bool: def __enter__(self):
if self._entered: self._result = imgui.begin(self._name, self._visible, self._flags)
self._end_fn() return self._result
def __exit__(self, *args):
imgui.end()
return False return False
def imgui_window(name: str, visible: bool = True, flags: int = 0) -> ImGuiScope: def imgui_begin_child(id_str: str, size_x: float = 0, size_y: float = 0, flags: int = 0):
return ImGuiScope(imgui.begin, imgui.end, name, visible, flags) return _ImguiBeginChild(id_str, size_x, size_y, flags)
def imgui_table(name: str, columns: int, flags: int = 0) -> ImGuiScope: class _ImguiBeginChild:
return ImGuiScope(imgui.begin_table, imgui.end_table, name, columns, flags) def __init__(self, id_str: str, size_x: float, size_y: float, flags: int):
self._id = id_str
self._sx = size_x
self._sy = size_y
self._flags = flags
def __enter__(self):
return imgui.begin_child(self._id, self._sx, self._sy, self._flags)
def __exit__(self, *args):
imgui.end_child()
return False
def imgui_menu_bar() -> ImGuiScope: def imgui_begin_table(name: str, columns: int, flags: int = 0):
return ImGuiScope(imgui.begin_menu_bar, imgui.end_menu_bar) return _ImguiBeginTable(name, columns, flags)
def imgui_menu(label: str) -> ImGuiScope: class _ImguiBeginTable:
return ImGuiScope(imgui.begin_menu, imgui.end_menu, label) def __init__(self, name: str, columns: int, flags: int):
self._name = name
self._columns = columns
self._flags = flags
def __enter__(self):
return imgui.begin_table(self._name, self._columns, self._flags)
def __exit__(self, *args):
imgui.end_table()
return False
def imgui_child(id_str: str, width: float = 0, height: float = 0, flags: int = 0) -> ImGuiScope: def imgui_begin_menu_bar():
return ImGuiScope(imgui.begin_child, imgui.end_child, id_str, width, height, flags) return _ImguiBeginMenuBar()
def imgui_group() -> ImGuiScope: class _ImguiBeginMenuBar:
return ImGuiScope(imgui.begin_group, imgui.end_group) def __enter__(self):
return imgui.begin_menu_bar()
def __exit__(self, *args):
imgui.end_menu_bar()
return False
def imgui_popup(id_str: str) -> ImGuiScope: def imgui_begin_menu(label: str):
return ImGuiScope(imgui.begin_popup, imgui.end_popup, id_str) return _ImguiBeginMenu(label)
def imgui_tooltip() -> ImGuiScope: class _ImguiBeginMenu:
return ImGuiScope(imgui.begin_tooltip, imgui.end_tooltip) def __init__(self, label: str):
self._label = label
def __enter__(self):
return imgui.begin_menu(self._label)
def __exit__(self, *args):
imgui.end_menu()
return False
def imgui_clipper(count: int) -> ImGuiScope: def imgui_begin_popup(id_str: str):
return ImGuiScope( return _ImguiBeginPopup(id_str)
lambda n: imgui.listing_builder.begin_clipper(n, -1),
imgui.listing_builder.end_clipper,
count
)
def node_editor_scope(name: str) -> ImGuiScope: class _ImguiBeginPopup:
return ImGuiScope(imgui_node_editor.begin, imgui_node_editor.end, name) def __init__(self, id_str: str):
self._id = id_str
def __enter__(self):
return imgui.begin_popup(self._id)
def __exit__(self, *args):
imgui.end_popup()
return False
def imgui_begin_tooltip():
return _ImguiBeginTooltip()
class _ImguiBeginTooltip:
def __enter__(self):
return imgui.begin_tooltip()
def __exit__(self, *args):
imgui.end_tooltip()
return False
def imgui_begin_group():
return _ImguiBeginGroup()
class _ImguiBeginGroup:
def __enter__(self):
return imgui.begin_group()
def __exit__(self, *args):
imgui.end_group()
return False
def node_begin(name: str):
return _NodeBegin(name)
class _NodeBegin:
def __init__(self, name: str):
self._name = name
def __enter__(self):
return imgui_node_editor.begin(self._name)
def __exit__(self, *args):
imgui_node_editor.end()
return False