fix(gui): Fix imgui.begin_child unbalanced EndChild() calls

In ImGui, EndChild() MUST be called even if BeginChild() returns False (meaning the child is clipped). Using if imgui.begin_child(...): caused EndChild() to be skipped, unbalancing the stack and causing sloppy.py to crash when certain UI panels were off-screen or collapsed.
This commit is contained in:
2026-05-14 20:19:44 -04:00
parent 3ace10d352
commit 0863559e59
+37 -17
View File
@@ -1983,7 +1983,8 @@ class App:
# Left Sidebar # Left Sidebar
imgui.table_next_column() imgui.table_next_column()
if imgui.begin_child("prompt_list_pane", imgui.ImVec2(0, 0), False): imgui.begin_child("prompt_list_pane", imgui.ImVec2(0, 0), False)
if True:
if imgui.button("New Preset", imgui.ImVec2(-1, 0)): if imgui.button("New Preset", imgui.ImVec2(-1, 0)):
self._editing_preset_name = "" self._editing_preset_name = ""
self._editing_preset_system_prompt = "" self._editing_preset_system_prompt = ""
@@ -2003,7 +2004,8 @@ class App:
# Right Editor # Right Editor
imgui.table_next_column() imgui.table_next_column()
avail_r = imgui.get_content_region_avail() avail_r = imgui.get_content_region_avail()
if imgui.begin_child("prompt_edit_pane", imgui.ImVec2(0, avail_r.y - 45), False): imgui.begin_child("prompt_edit_pane", imgui.ImVec2(0, avail_r.y - 45), False)
if True:
p_disp = self._editing_preset_name or "(New Preset)" p_disp = self._editing_preset_name or "(New Preset)"
imgui.text_colored(C_IN, f"Editing Prompt Preset: {p_disp}") imgui.text_colored(C_IN, f"Editing Prompt Preset: {p_disp}")
imgui.separator() imgui.separator()
@@ -2085,7 +2087,8 @@ class App:
# Left Sidebar # Left Sidebar
imgui.table_next_column() imgui.table_next_column()
if imgui.begin_child("tp_list_pane", imgui.ImVec2(0, 0), False): imgui.begin_child("tp_list_pane", imgui.ImVec2(0, 0), False)
if True:
if imgui.button("New Preset", imgui.ImVec2(-1, 0)): if imgui.button("New Preset", imgui.ImVec2(-1, 0)):
self._editing_tool_preset_name = ""; self._editing_tool_preset_categories = {cat: {} for cat in models.DEFAULT_TOOL_CATEGORIES} self._editing_tool_preset_name = ""; self._editing_tool_preset_categories = {cat: {} for cat in models.DEFAULT_TOOL_CATEGORIES}
self._editing_tool_preset_scope = "project"; self._selected_tool_preset_idx = -1 self._editing_tool_preset_scope = "project"; self._selected_tool_preset_idx = -1
@@ -2101,7 +2104,8 @@ class App:
# Right Editor # Right Editor
imgui.table_next_column() imgui.table_next_column()
avail_r = imgui.get_content_region_avail() avail_r = imgui.get_content_region_avail()
if imgui.begin_child("tp_editor_content", imgui.ImVec2(0, avail_r.y - 45), False): imgui.begin_child("tp_editor_content", imgui.ImVec2(0, avail_r.y - 45), False)
if True:
p_name = self._editing_tool_preset_name or "(New Tool Preset)" p_name = self._editing_tool_preset_name or "(New Tool Preset)"
imgui.text_colored(C_IN, f"Editing Tool Preset: {p_name}"); imgui.separator() imgui.text_colored(C_IN, f"Editing Tool Preset: {p_name}"); imgui.separator()
@@ -2129,7 +2133,8 @@ class App:
f_idx = cat_opts.index(self.ui_tool_filter_category) if self.ui_tool_filter_category in cat_opts else 0 f_idx = cat_opts.index(self.ui_tool_filter_category) if self.ui_tool_filter_category in cat_opts else 0
imgui.set_next_item_width(200); ch_cat, next_f_idx = imgui.combo("##tp_filter", f_idx, cat_opts) imgui.set_next_item_width(200); ch_cat, next_f_idx = imgui.combo("##tp_filter", f_idx, cat_opts)
if ch_cat: self.ui_tool_filter_category = cat_opts[next_f_idx] if ch_cat: self.ui_tool_filter_category = cat_opts[next_f_idx]
if imgui.begin_child("tp_scroll", imgui.ImVec2(0, h1), True): imgui.begin_child("tp_scroll", imgui.ImVec2(0, h1), True)
if True:
for cat_name, default_tools in models.DEFAULT_TOOL_CATEGORIES.items(): for cat_name, default_tools in models.DEFAULT_TOOL_CATEGORIES.items():
if self.ui_tool_filter_category != "All" and self.ui_tool_filter_category != cat_name: continue if self.ui_tool_filter_category != "All" and self.ui_tool_filter_category != cat_name: continue
if imgui.tree_node(cat_name): if imgui.tree_node(cat_name):
@@ -2162,11 +2167,13 @@ class App:
opened_b = imgui.collapsing_header("Bias Profiles", imgui.TreeNodeFlags_.default_open) opened_b = imgui.collapsing_header("Bias Profiles", imgui.TreeNodeFlags_.default_open)
if opened_b != self._bias_list_open: self._bias_list_open = opened_b if opened_b != self._bias_list_open: self._bias_list_open = opened_b
if self._bias_list_open: if self._bias_list_open:
if imgui.begin_child("bias_area", imgui.ImVec2(0, h2), True): imgui.begin_child("bias_area", imgui.ImVec2(0, h2), True)
if True:
if imgui.begin_table("bias_split", 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders_inner_v): if imgui.begin_table("bias_split", 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders_inner_v):
imgui.table_setup_column("BList", imgui.TableColumnFlags_.width_fixed, 150); imgui.table_setup_column("BEdit", imgui.TableColumnFlags_.width_stretch) imgui.table_setup_column("BList", imgui.TableColumnFlags_.width_fixed, 150); imgui.table_setup_column("BEdit", imgui.TableColumnFlags_.width_stretch)
imgui.table_next_row(); imgui.table_next_column() imgui.table_next_row(); imgui.table_next_column()
if imgui.begin_child("blist_pane", imgui.ImVec2(0, 0), False): imgui.begin_child("blist_pane", imgui.ImVec2(0, 0), False)
if True:
if imgui.button("New Profile", imgui.ImVec2(-1, 0)): if imgui.button("New Profile", imgui.ImVec2(-1, 0)):
self._editing_bias_profile_name = ""; self._editing_bias_profile_tool_weights = {} self._editing_bias_profile_name = ""; self._editing_bias_profile_tool_weights = {}
self._editing_bias_profile_category_multipliers = {}; self._selected_bias_profile_idx = -1 self._editing_bias_profile_category_multipliers = {}; self._selected_bias_profile_idx = -1
@@ -2178,7 +2185,8 @@ class App:
imgui.end_child() imgui.end_child()
imgui.table_next_column() imgui.table_next_column()
if imgui.begin_child("bedit_pane", imgui.ImVec2(0, 0), False): imgui.begin_child("bedit_pane", imgui.ImVec2(0, 0), False)
if True:
imgui.text("Name:"); imgui.same_line(); imgui.set_next_item_width(-1); _, self._editing_bias_profile_name = imgui.input_text("##bname", self._editing_bias_profile_name) imgui.text("Name:"); imgui.same_line(); imgui.set_next_item_width(-1); _, self._editing_bias_profile_name = imgui.input_text("##bname", self._editing_bias_profile_name)
rem_bias_y = imgui.get_content_region_avail().y - 45 rem_bias_y = imgui.get_content_region_avail().y - 45
if self._bias_weights_open and self._bias_cats_open: bh1, bh2 = rem_bias_y * self._bias_split_v, rem_bias_y - (rem_bias_y * self._bias_split_v) - 10 if self._bias_weights_open and self._bias_cats_open: bh1, bh2 = rem_bias_y * self._bias_split_v, rem_bias_y - (rem_bias_y * self._bias_split_v) - 10
@@ -2189,7 +2197,8 @@ class App:
opened_bw = imgui.collapsing_header("Tool Weights", imgui.TreeNodeFlags_.default_open) opened_bw = imgui.collapsing_header("Tool Weights", imgui.TreeNodeFlags_.default_open)
if opened_bw != self._bias_weights_open: self._bias_weights_open = opened_bw if opened_bw != self._bias_weights_open: self._bias_weights_open = opened_bw
if self._bias_weights_open: if self._bias_weights_open:
if imgui.begin_child("btool_scroll", imgui.ImVec2(0, bh1), True): imgui.begin_child("btool_scroll", imgui.ImVec2(0, bh1), True)
if True:
for cat_name, default_tools in models.DEFAULT_TOOL_CATEGORIES.items(): for cat_name, default_tools in models.DEFAULT_TOOL_CATEGORIES.items():
if imgui.tree_node(f"{cat_name}##b_list"): if imgui.tree_node(f"{cat_name}##b_list"):
if imgui.begin_table(f"bt_{cat_name}", 2): if imgui.begin_table(f"bt_{cat_name}", 2):
@@ -2209,7 +2218,8 @@ class App:
opened_bc = imgui.collapsing_header("Category Multipliers", imgui.TreeNodeFlags_.default_open) opened_bc = imgui.collapsing_header("Category Multipliers", imgui.TreeNodeFlags_.default_open)
if opened_bc != self._bias_cats_open: self._bias_cats_open = opened_bc if opened_bc != self._bias_cats_open: self._bias_cats_open = opened_bc
if self._bias_cats_open: if self._bias_cats_open:
if imgui.begin_child("bcat_scroll", imgui.ImVec2(0, bh2), True): imgui.begin_child("bcat_scroll", imgui.ImVec2(0, bh2), True)
if True:
if imgui.begin_table("bcats", 2): if imgui.begin_table("bcats", 2):
imgui.table_setup_column("C", imgui.TableColumnFlags_.width_fixed, 220); imgui.table_setup_column("M", imgui.TableColumnFlags_.width_stretch) imgui.table_setup_column("C", imgui.TableColumnFlags_.width_fixed, 220); imgui.table_setup_column("M", imgui.TableColumnFlags_.width_stretch)
for cn in sorted(models.DEFAULT_TOOL_CATEGORIES.keys()): for cn in sorted(models.DEFAULT_TOOL_CATEGORIES.keys()):
@@ -2267,7 +2277,8 @@ class App:
imgui.table_next_row() imgui.table_next_row()
imgui.table_next_column() imgui.table_next_column()
if imgui.begin_child("persona_list_pane", imgui.ImVec2(0, 0), False): imgui.begin_child("persona_list_pane", imgui.ImVec2(0, 0), False)
if True:
if imgui.button("New Persona", imgui.ImVec2(-1, 0)): if imgui.button("New Persona", imgui.ImVec2(-1, 0)):
self._editing_persona_name = ""; self._editing_persona_system_prompt = "" self._editing_persona_name = ""; self._editing_persona_system_prompt = ""
self._editing_persona_tool_preset_id = ""; self._editing_persona_bias_profile_id = "" self._editing_persona_tool_preset_id = ""; self._editing_persona_bias_profile_id = ""
@@ -2290,7 +2301,8 @@ class App:
# --- Right Editor --- # --- Right Editor ---
imgui.table_next_column() imgui.table_next_column()
avail = imgui.get_content_region_avail() avail = imgui.get_content_region_avail()
if imgui.begin_child("persona_editor_content", imgui.ImVec2(0, avail.y - 45), False): imgui.begin_child("persona_editor_content", imgui.ImVec2(0, avail.y - 45), False)
if True:
header_text = "New Persona" if getattr(self, '_editing_persona_is_new', True) else f"Editing Persona: {self._editing_persona_name}" header_text = "New Persona" if getattr(self, '_editing_persona_is_new', True) else f"Editing Persona: {self._editing_persona_name}"
imgui.text_colored(C_IN, header_text); imgui.separator() imgui.text_colored(C_IN, header_text); imgui.separator()
@@ -2314,7 +2326,8 @@ class App:
if opened_models != self._persona_models_open: self._persona_models_open = opened_models if opened_models != self._persona_models_open: self._persona_models_open = opened_models
if self._persona_models_open: if self._persona_models_open:
if imgui.begin_child("pref_models_scroll", imgui.ImVec2(0, h1), True): imgui.begin_child("pref_models_scroll", imgui.ImVec2(0, h1), True)
if True:
to_remove = [] to_remove = []
providers = models.PROVIDERS providers = models.PROVIDERS
if not hasattr(self, '_persona_pref_models_expanded'): self._persona_pref_models_expanded = {} if not hasattr(self, '_persona_pref_models_expanded'): self._persona_pref_models_expanded = {}
@@ -2384,7 +2397,8 @@ class App:
if opened_prompt != self._persona_prompt_open: self._persona_prompt_open = opened_prompt if opened_prompt != self._persona_prompt_open: self._persona_prompt_open = opened_prompt
if self._persona_prompt_open: if self._persona_prompt_open:
if imgui.begin_child("p_prompt_header_pane", imgui.ImVec2(0, 30), False): imgui.begin_child("p_prompt_header_pane", imgui.ImVec2(0, 30), False)
if True:
imgui.text("Template:"); imgui.same_line(); p_pre = ["Select..."] + sorted(self.controller.presets.keys()) imgui.text("Template:"); imgui.same_line(); p_pre = ["Select..."] + sorted(self.controller.presets.keys())
if not hasattr(self, "_load_preset_idx"): self._load_preset_idx = 0 if not hasattr(self, "_load_preset_idx"): self._load_preset_idx = 0
imgui.set_next_item_width(200); _, self._load_preset_idx = imgui.combo("##load_p", self._load_preset_idx, p_pre) imgui.set_next_item_width(200); _, self._load_preset_idx = imgui.combo("##load_p", self._load_preset_idx, p_pre)
@@ -2416,6 +2430,9 @@ class App:
if imgui.button("Close##pers", imgui.ImVec2(100, 0)): if imgui.button("Close##pers", imgui.ImVec2(100, 0)):
self.show_persona_editor_window = False self.show_persona_editor_window = False
imgui.end_table() imgui.end_table()
if not is_embedded:
imgui.end()
def _render_provider_panel(self) -> None: def _render_provider_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_provider_panel") if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_provider_panel")
@@ -2870,7 +2887,8 @@ class App:
imgui.table_next_column() imgui.table_next_column()
#region: LEFT COLUMN (Tree) --- #region: LEFT COLUMN (Tree) ---
if imgui.begin_child("ast_tree_scroll", imgui.ImVec2(0, 600), True): imgui.begin_child("ast_tree_scroll", imgui.ImVec2(0, 600), True)
if True:
if not self._cached_ast_nodes: imgui.text("No AST nodes found or error fetching outline.") if not self._cached_ast_nodes: imgui.text("No AST nodes found or error fetching outline.")
else: else:
for node in self._cached_ast_nodes: for node in self._cached_ast_nodes:
@@ -2899,7 +2917,8 @@ class App:
imgui.table_next_column() imgui.table_next_column()
#region: RIGHT COLUMN (Content) --- #region: RIGHT COLUMN (Content) ---
if imgui.begin_child("ast_content_scroll", imgui.ImVec2(0, 600), True): imgui.begin_child("ast_content_scroll", imgui.ImVec2(0, 600), True)
if True:
if not hasattr(self, '_cached_ast_file_lines') or not self._cached_ast_file_lines: if not hasattr(self, '_cached_ast_file_lines') or not self._cached_ast_file_lines:
imgui.text("No file content loaded.") imgui.text("No file content loaded.")
else: else:
@@ -2950,7 +2969,8 @@ class App:
""" """
if imgui.begin_popup_modal("Select Context Files", None, imgui.WindowFlags_.always_auto_resize)[0]: if imgui.begin_popup_modal("Select Context Files", None, imgui.WindowFlags_.always_auto_resize)[0]:
imgui.text("Select files from project to add to context:") imgui.text("Select files from project to add to context:")
if imgui.begin_child("ctx_picker_list", imgui.ImVec2(600, 300), True): imgui.begin_child("ctx_picker_list", imgui.ImVec2(600, 300), True)
if True:
# Create a temporary selection set if not initialized # Create a temporary selection set if not initialized
if not hasattr(self, '_ui_picker_selected'): self._ui_picker_selected = set() if not hasattr(self, '_ui_picker_selected'): self._ui_picker_selected = set()
for f in self.files: for f in self.files: