UX UX UX UX UX
This commit is contained in:
+34
-304
@@ -2632,24 +2632,22 @@ def render_files_and_media(app: App) -> None:
|
||||
"""
|
||||
[C: tests/test_gui_fast_render.py:test_render_files_and_media_fast]
|
||||
"""
|
||||
avail = imgui.get_content_region_avail().y
|
||||
if not hasattr(app, 'files_screenshots_split'): app.files_screenshots_split = 0.65
|
||||
split_y = int(avail * app.files_screenshots_split)
|
||||
if imgui.collapsing_header("Files", imgui.TreeNodeFlags_.default_open):
|
||||
with imscope.child("Files_child", -1, split_y, True):
|
||||
if not hasattr(app, 'files_last_selected'): app.files_last_selected = -1
|
||||
|
||||
with imscope.table("files_table", 3, imgui.TableFlags_.resizable | imgui.TableFlags_.borders):
|
||||
imgui.table_setup_column("", imgui.TableColumnFlags_.width_fixed, 25)
|
||||
with imscope.group():
|
||||
if imgui.begin_table("files_table", 3, imgui.TableFlags_.resizable | imgui.TableFlags_.borders | imgui.TableFlags_.row_bg):
|
||||
imgui.table_setup_column("Act", imgui.TableColumnFlags_.width_fixed, 60)
|
||||
imgui.table_setup_column("Path", imgui.TableColumnFlags_.width_stretch)
|
||||
imgui.table_setup_column("Status", imgui.TableColumnFlags_.width_fixed, 60)
|
||||
imgui.table_setup_column("Status", imgui.TableColumnFlags_.width_fixed, 70)
|
||||
imgui.table_headers_row()
|
||||
|
||||
to_remove_idx = -1
|
||||
app.files.sort(key=lambda f: f.path.lower() if hasattr(f, 'path') else str(f).lower())
|
||||
for i, f_item in enumerate(app.files):
|
||||
imgui.table_next_row(); imgui.table_set_column_index(0)
|
||||
imgui.table_next_row()
|
||||
imgui.table_set_column_index(0)
|
||||
fpath = f_item.path if hasattr(f_item, 'path') else str(f_item)
|
||||
in_context = any((cf.path if hasattr(cf, 'path') else str(cf)) == fpath for cf in app.context_files)
|
||||
is_cached = any(fpath in c for c in getattr(app, '_cached_files', []))
|
||||
|
||||
if imgui.button(f"+##add_f_{i}"):
|
||||
if not in_context:
|
||||
@@ -2657,157 +2655,48 @@ def render_files_and_media(app: App) -> None:
|
||||
new_item = models.FileItem(path=fpath)
|
||||
app.context_files.append(new_item)
|
||||
app._populate_auto_slices(new_item)
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button(f"x##rem_f_{i}"):
|
||||
to_remove_idx = i
|
||||
|
||||
imgui.table_set_column_index(1); imgui.text(fpath)
|
||||
imgui.table_set_column_index(1)
|
||||
imgui.text(fpath)
|
||||
if imgui.is_item_hovered(): imgui.set_tooltip(fpath)
|
||||
|
||||
imgui.table_set_column_index(2)
|
||||
if in_context:
|
||||
imgui.text_colored(imgui.ImVec4(0.3, 0.8, 0.3, 1), "Active")
|
||||
elif is_cached:
|
||||
imgui.text_colored(imgui.ImVec4(0.3, 0.8, 1, 1), "Cached")
|
||||
else:
|
||||
imgui.text_disabled(" - ")
|
||||
if is_cached: imgui.text_colored(imgui.ImVec4(0, 1, 0, 1), "Y")
|
||||
else: imgui.text_colored(imgui.ImVec4(0.5, 0.5, 0.5, 1), "-")
|
||||
|
||||
if imgui.button("Add Files to Inventory"):
|
||||
r = hide_tk_root(); paths = filedialog.askopenfilenames(); r.destroy()
|
||||
for p in paths:
|
||||
if p not in [f.path if hasattr(f, 'path') else f for f in app.files]: app.files.append(models.FileItem(path=p))
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button("Clear Selection##inv"):
|
||||
for f in app.files: f.selected = False
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button("Remove from Inventory"):
|
||||
app.files = [f for f in app.files if not f.selected]
|
||||
|
||||
imgui.end_table()
|
||||
if to_remove_idx != -1: app.files.pop(to_remove_idx)
|
||||
|
||||
imgui.dummy(imgui.ImVec2(0, 5))
|
||||
if imgui.button("Add Files to Inventory"):
|
||||
r = hide_tk_root(); paths = filedialog.askopenfilenames(); r.destroy()
|
||||
for p in paths:
|
||||
if p not in [f.path if hasattr(f, 'path') else f for f in app.files]: app.files.append(models.FileItem(path=p))
|
||||
|
||||
imgui.separator()
|
||||
|
||||
if imgui.collapsing_header("Screenshots", imgui.TreeNodeFlags_.default_open):
|
||||
with imscope.child("Shots_child", -1, -1, True):
|
||||
with imscope.child("Shots_child", -1, 150, True):
|
||||
to_rem_shot = -1
|
||||
for i, s in enumerate(app.screenshots):
|
||||
if imgui.button(f"x##s{i}"):
|
||||
app.screenshots.pop(i)
|
||||
break
|
||||
if imgui.button(f"x##s{i}"): to_rem_shot = i
|
||||
imgui.same_line(); imgui.text(s)
|
||||
if to_rem_shot != -1: app.screenshots.pop(to_rem_shot)
|
||||
|
||||
if imgui.button("Add Screenshots##adds"):
|
||||
r = hide_tk_root(); paths = filedialog.askopenfilenames(filetypes=[("Images", "*.png *.jpg *.jpeg *.gif *.bmp *.webp"), ("All", "*.*")]); r.destroy()
|
||||
for p in paths:
|
||||
if p not in app.screenshots: app.screenshots.append(p)
|
||||
return
|
||||
|
||||
def render_files_panel(app: App, height_override: float = 0) -> None:
|
||||
if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_files_panel")
|
||||
imgui.text("Paths")
|
||||
imgui.same_line()
|
||||
imgui.text("| Base Dir:")
|
||||
imgui.same_line()
|
||||
imgui.set_next_item_width(-100)
|
||||
ch, app.ui_files_base_dir = imgui.input_text("##f_base", app.ui_files_base_dir)
|
||||
imgui.same_line()
|
||||
if imgui.button("Browse##fb"):
|
||||
r = hide_tk_root()
|
||||
d = filedialog.askdirectory()
|
||||
r.destroy()
|
||||
if d: app.ui_files_base_dir = d
|
||||
imgui.separator()
|
||||
# Calculate content-based height: use override if provided, else content-based
|
||||
if height_override > 0:
|
||||
child_h = height_override
|
||||
else:
|
||||
row_count = max(len(app.files), 1)
|
||||
child_h = min(row_count * 28 + 40, 300)
|
||||
# BEGIN f_paths child window
|
||||
imgui.begin_child("f_paths", imgui.ImVec2(0, child_h), True)
|
||||
if imgui.begin_table("files_table", 4, imgui.TableFlags_.resizable | imgui.TableFlags_.borders):
|
||||
imgui.table_setup_column("Actions", imgui.TableColumnFlags_.width_fixed, 40)
|
||||
imgui.table_setup_column("File Path", imgui.TableColumnFlags_.width_stretch)
|
||||
imgui.table_setup_column("Flags", imgui.TableColumnFlags_.width_fixed, 150)
|
||||
imgui.table_setup_column("Cache", imgui.TableColumnFlags_.width_fixed, 40)
|
||||
imgui.table_headers_row()
|
||||
|
||||
for i, f_item in enumerate(app.files):
|
||||
imgui.table_next_row()
|
||||
# Actions
|
||||
imgui.table_set_column_index(0)
|
||||
if imgui.button(f"x##f{i}"):
|
||||
app.files.pop(i)
|
||||
break
|
||||
# File Path
|
||||
imgui.table_set_column_index(1)
|
||||
imgui.text(f_item.path if hasattr(f_item, "path") else str(f_item))
|
||||
# Flags
|
||||
imgui.table_set_column_index(2)
|
||||
if hasattr(f_item, "auto_aggregate"):
|
||||
changed_agg, f_item.auto_aggregate = imgui.checkbox(f"Agg##a{i}", f_item.auto_aggregate)
|
||||
imgui.same_line()
|
||||
changed_full, f_item.force_full = imgui.checkbox(f"Full##f{i}", f_item.force_full)
|
||||
# Cache
|
||||
imgui.table_set_column_index(3)
|
||||
path = f_item.path if hasattr(f_item, "path") else str(f_item)
|
||||
is_cached = any(path in c for c in getattr(app, "_cached_files", []))
|
||||
if is_cached:
|
||||
imgui.text_colored("●", imgui.ImVec4(0, 1, 0, 1)) # Green dot
|
||||
else:
|
||||
imgui.text_disabled("○")
|
||||
imgui.end_table()
|
||||
imgui.end_child()
|
||||
if imgui.button("Add File(s)"):
|
||||
r = hide_tk_root()
|
||||
paths = filedialog.askopenfilenames()
|
||||
r.destroy()
|
||||
for p in paths:
|
||||
if p not in [f.path if hasattr(f, "path") else f for f in app.files]:
|
||||
app.files.append(models.FileItem(path=p))
|
||||
imgui.same_line()
|
||||
if imgui.button("Add Wildcard"):
|
||||
r = hide_tk_root()
|
||||
d = filedialog.askdirectory()
|
||||
r.destroy()
|
||||
if d: app.files.append(models.FileItem(path=str(Path(d) / "**" / "*")))
|
||||
|
||||
imgui.separator()
|
||||
from src import summarize
|
||||
stats = summarize._summary_cache.get_stats()
|
||||
imgui.text_disabled(f"Summary Cache: {stats['entries']} entries ({stats['size_bytes']} bytes)")
|
||||
imgui.same_line()
|
||||
if imgui.button("Clear Summary Cache##btn_clear_summary_cache"):
|
||||
app.controller._cb_clear_summary_cache()
|
||||
|
||||
if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_files_panel")
|
||||
|
||||
def render_screenshots_panel(app: App, height_override: float = 0) -> None:
|
||||
if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_screenshots_panel")
|
||||
imgui.text("Paths"); imgui.same_line(); imgui.text("| Base Dir:"); imgui.same_line();
|
||||
imgui.set_next_item_width(-100)
|
||||
ch, app.ui_shots_base_dir = imgui.input_text("##s_base", app.ui_shots_base_dir)
|
||||
imgui.same_line()
|
||||
if imgui.button("Browse##sb"):
|
||||
r = hide_tk_root(); d = filedialog.askdirectory(); r.destroy()
|
||||
if d: app.ui_shots_base_dir = d
|
||||
imgui.separator()
|
||||
# Calculate content-based height: use override if provided, else content-based
|
||||
if height_override > 0: shot_h = height_override
|
||||
else:
|
||||
shot_count = max(len(app.screenshots), 1)
|
||||
shot_h = min(shot_count * 28 + 40, 200)
|
||||
# BEGIN s_paths child window
|
||||
imgui.begin_child("s_paths", imgui.ImVec2(0, shot_h), True)
|
||||
for i, s in enumerate(app.screenshots):
|
||||
if imgui.button(f"x##s{i}"):
|
||||
app.screenshots.pop(i)
|
||||
break
|
||||
imgui.same_line(); imgui.text(s)
|
||||
imgui.end_child()
|
||||
if imgui.button("Add Screenshot(s)"):
|
||||
r = hide_tk_root()
|
||||
paths = filedialog.askopenfilenames(
|
||||
title="Select Screenshots",
|
||||
filetypes=[("Images", "*.png *.jpg *.jpeg *.gif *.bmp *.webp"), ("All", "*.*")],
|
||||
)
|
||||
r.destroy()
|
||||
for p in paths:
|
||||
if p not in app.screenshots: app.screenshots.append(p)
|
||||
if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_screenshots_panel")
|
||||
#endregion: Context Management
|
||||
|
||||
def render_context_batch_actions(app: App, total_lines: int, total_ast: int) -> None:
|
||||
imgui.text("Batch:")
|
||||
@@ -2914,166 +2803,7 @@ def render_context_composition_panel(app: App) -> None:
|
||||
render_context_screenshots(app)
|
||||
|
||||
def render_ast_inspector_modal(app: App) -> None:
|
||||
"""
|
||||
[C: tests/test_ast_inspector_extended.py:test_ast_inspector_line_range_parsing]
|
||||
"""
|
||||
if app._show_ast_inspector:
|
||||
imgui.open_popup('AST Inspector')
|
||||
app._show_ast_inspector = False
|
||||
|
||||
#region: AST Inspector
|
||||
imgui.set_next_window_size(imgui.ImVec2(1200, 800), imgui.Cond_.first_use_ever)
|
||||
expanded, opened = imgui.begin_popup_modal('AST Inspector', True, imgui.WindowFlags_.none)
|
||||
if opened:
|
||||
if expanded:
|
||||
if app.ui_inspecting_ast_file is None:
|
||||
imgui.close_current_popup()
|
||||
else:
|
||||
f_item = app.ui_inspecting_ast_file
|
||||
f_path = f_item.path if hasattr(f_item, "path") else str(f_item)
|
||||
|
||||
if f_path != app._cached_ast_file_path:
|
||||
outline = ""
|
||||
try:
|
||||
from src import mcp_client
|
||||
from pathlib import Path
|
||||
proj_dir = str(Path(app.controller.active_project_path).parent.resolve()) if getattr(app, 'controller', None) and app.controller.active_project_path else None
|
||||
mcp_client.configure([{"path": f_path}], [proj_dir] if proj_dir else None)
|
||||
|
||||
if f_path.lower().endswith('.py'): outline = mcp_client.py_get_code_outline(f_path)
|
||||
elif f_path.lower().endswith(('.c', '.h')): outline = mcp_client.ts_c_get_code_outline(f_path)
|
||||
else: outline = mcp_client.ts_cpp_get_code_outline(f_path)
|
||||
except Exception as e:
|
||||
outline = f"Error fetching outline: {e}"
|
||||
|
||||
app._cached_ast_nodes = []
|
||||
import re
|
||||
pattern = re.compile(r'^(\s*)\[(.*?)\] (.*?) \(Lines (\d+)-(\d+)\)')
|
||||
stack = [] # (indent, name)
|
||||
for line in outline.splitlines():
|
||||
m = pattern.match(line)
|
||||
if m:
|
||||
indent_str, kind, name, start_ln, end_ln = m.groups()
|
||||
indent = len(indent_str)
|
||||
while stack and stack[-1][0] >= indent: stack.pop()
|
||||
stack.append((indent, name))
|
||||
full_path = '::'.join([s[1] for s in stack])
|
||||
app._cached_ast_nodes.append({
|
||||
'indent': indent,
|
||||
'kind': kind,
|
||||
'name': name,
|
||||
'full_path': full_path,
|
||||
'start_line': int(start_ln),
|
||||
'end_line': int(end_ln)
|
||||
})
|
||||
try:
|
||||
content = mcp_client.read_file(f_path)
|
||||
app._cached_ast_file_lines = content.splitlines()
|
||||
except Exception:
|
||||
app._cached_ast_file_lines = ["Error loading file content."]
|
||||
app._cached_ast_file_path = f_path
|
||||
|
||||
imgui.text(f"Inspecting AST: {f_path}")
|
||||
imgui.separator()
|
||||
|
||||
avail = imgui.get_content_region_avail()
|
||||
table_height = max(100.0, avail.y - imgui.get_frame_height_with_spacing() - 10)
|
||||
#region: ast_dual_pane
|
||||
if imgui.begin_table('ast_dual_pane', 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders_inner_v, imgui.ImVec2(0, table_height)):
|
||||
imgui.table_next_column()
|
||||
|
||||
#region: LEFT COLUMN (Tree) ---
|
||||
imgui.begin_child("ast_tree_scroll", imgui.ImVec2(0, 0), True)
|
||||
if True:
|
||||
if not app._cached_ast_nodes: imgui.text("No AST nodes found or error fetching outline.")
|
||||
else:
|
||||
for node in app._cached_ast_nodes:
|
||||
indent = node['indent']
|
||||
kind = node['kind']
|
||||
name = node['name']
|
||||
full_path = node['full_path']
|
||||
|
||||
imgui.dummy(imgui.ImVec2(indent * 10, 0))
|
||||
imgui.same_line()
|
||||
imgui.text(f"[{kind}] {name}")
|
||||
|
||||
if imgui.is_item_hovered():
|
||||
app._hovered_ast_node = full_path
|
||||
|
||||
btn_width = 150
|
||||
avail_width = imgui.get_content_region_avail().x
|
||||
try:
|
||||
do_align = avail_width > btn_width
|
||||
except TypeError:
|
||||
do_align = False
|
||||
if do_align:
|
||||
imgui.same_line(imgui.get_window_width() - btn_width)
|
||||
else:
|
||||
imgui.same_line()
|
||||
|
||||
current_mode = f_item.ast_mask.get(full_path, 'hide')
|
||||
|
||||
imgui.push_id(full_path)
|
||||
if imgui.radio_button("Def", current_mode == 'def'): f_item.ast_mask[full_path] = 'def'
|
||||
imgui.same_line()
|
||||
if imgui.radio_button("Sig", current_mode == 'sig'): f_item.ast_mask[full_path] = 'sig'
|
||||
imgui.same_line()
|
||||
if imgui.radio_button("Hide", current_mode == 'hide'): f_item.ast_mask[full_path] = 'hide'
|
||||
imgui.pop_id()
|
||||
imgui.end_child()
|
||||
#endregion: LEFT COLUMN (Tree)
|
||||
|
||||
imgui.table_next_column()
|
||||
|
||||
#region: RIGHT COLUMN (Content) ---
|
||||
imgui.begin_child("ast_content_scroll", imgui.ImVec2(0, 0), True)
|
||||
if True:
|
||||
if not hasattr(app, '_cached_ast_file_lines') or not app._cached_ast_file_lines:
|
||||
imgui.text("No file content loaded.")
|
||||
else:
|
||||
draw_list = imgui.get_window_draw_list()
|
||||
for i, line_text in enumerate(app._cached_ast_file_lines):
|
||||
line_num = i + 1
|
||||
|
||||
# Prioritize the most specific node (deepest indent) that covers the line
|
||||
deepest_node = None
|
||||
for node in app._cached_ast_nodes:
|
||||
if node['start_line'] <= line_num <= node['end_line']:
|
||||
if deepest_node is None or node['indent'] > deepest_node['indent']: deepest_node = node
|
||||
|
||||
mode = 'hide'
|
||||
if deepest_node: mode = f_item.ast_mask.get(deepest_node['full_path'], 'hide')
|
||||
|
||||
pos = imgui.get_cursor_screen_pos()
|
||||
line_height = imgui.get_text_line_height()
|
||||
|
||||
if mode == 'def':
|
||||
# Green, alpha 0.2
|
||||
draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + imgui.get_content_region_avail().x, pos.y + line_height), imgui.get_color_u32(vec4(0, 255, 0, 0.2)))
|
||||
elif mode == 'sig':
|
||||
# Blue, alpha 0.2
|
||||
draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + imgui.get_content_region_avail().x, pos.y + line_height), imgui.get_color_u32(vec4(0, 0, 255, 0.2)))
|
||||
elif deepest_node and deepest_node['full_path'] == getattr(app, '_hovered_ast_node', None):
|
||||
# Yellow, alpha 0.3 for hover
|
||||
draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + imgui.get_content_region_avail().x, pos.y + line_height), imgui.get_color_u32(vec4(255, 255, 0, 0.3)))
|
||||
|
||||
imgui.text(f"{line_num:4} | {line_text}")
|
||||
imgui.end_child()
|
||||
#endregion: RIGHT COLUMN (Content) ---
|
||||
imgui.end_table()
|
||||
#endregion: ast_dual_pane
|
||||
|
||||
imgui.separator()
|
||||
|
||||
if imgui.button("Close", imgui.ImVec2(120, 0)):
|
||||
app.ui_inspecting_ast_file = None
|
||||
imgui.close_current_popup()
|
||||
|
||||
imgui.end_popup()
|
||||
|
||||
#endregion: AST Inspector
|
||||
|
||||
if not opened: app.ui_inspecting_ast_file = None
|
||||
pass
|
||||
|
||||
def render_save_workspace_profile_modal(app: App) -> None:
|
||||
if app._show_save_workspace_profile_modal:
|
||||
|
||||
Reference in New Issue
Block a user