refactor(ui): cull unused UI helpers and redundant modules
This commit is contained in:
+151
-221
@@ -6,239 +6,169 @@ from pathlib import Path
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DiffHunk:
|
class DiffHunk:
|
||||||
header: str
|
header: str
|
||||||
lines: List[str]
|
lines: List[str]
|
||||||
old_start: int
|
old_start: int
|
||||||
old_count: int
|
old_count: int
|
||||||
new_start: int
|
new_start: int
|
||||||
new_count: int
|
new_count: int
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DiffFile:
|
class DiffFile:
|
||||||
old_path: str
|
old_path: str
|
||||||
new_path: str
|
new_path: str
|
||||||
hunks: List[DiffHunk]
|
hunks: List[DiffHunk]
|
||||||
|
|
||||||
def parse_diff_header(line: str) -> tuple[Optional[str], Optional[str], Optional[tuple[int, int, int, int]]]:
|
|
||||||
if not line.startswith(("--- ", "+++ ")):
|
|
||||||
return None, None, None
|
|
||||||
|
|
||||||
if line.startswith("--- "):
|
|
||||||
path = line[4:]
|
|
||||||
if path.startswith("a/"):
|
|
||||||
path = path[2:]
|
|
||||||
return path, None, None
|
|
||||||
elif line.startswith("+++ "):
|
|
||||||
path = line[4:]
|
|
||||||
if path.startswith("b/"):
|
|
||||||
path = path[2:]
|
|
||||||
return None, path, None
|
|
||||||
return None, None, None
|
|
||||||
|
|
||||||
def parse_hunk_header(line: str) -> Optional[tuple[int, int, int, int]]:
|
def parse_hunk_header(line: str) -> Optional[tuple[int, int, int, int]]:
|
||||||
"""
|
"""
|
||||||
[C: tests/test_diff_viewer.py:test_parse_hunk_header]
|
[C: tests/test_diff_viewer.py:test_parse_hunk_header]
|
||||||
"""
|
"""
|
||||||
if not line.startswith("@@"):
|
if not line.startswith("@@"):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
parts = line.split()
|
parts = line.split()
|
||||||
if len(parts) < 2:
|
if len(parts) < 2:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
old_part = parts[1][1:]
|
old_part = parts[1][1:]
|
||||||
new_part = parts[2][1:]
|
new_part = parts[2][1:]
|
||||||
|
|
||||||
old_parts = old_part.split(",")
|
old_parts = old_part.split(",")
|
||||||
new_parts = new_part.split(",")
|
new_parts = new_part.split(",")
|
||||||
|
|
||||||
old_start = int(old_parts[0])
|
old_start = int(old_parts[0])
|
||||||
old_count = int(old_parts[1]) if len(old_parts) > 1 else 1
|
old_count = int(old_parts[1]) if len(old_parts) > 1 else 1
|
||||||
new_start = int(new_parts[0])
|
new_start = int(new_parts[0])
|
||||||
new_count = int(new_parts[1]) if len(new_parts) > 1 else 1
|
new_count = int(new_parts[1]) if len(new_parts) > 1 else 1
|
||||||
|
|
||||||
return (old_start, old_count, new_start, new_count)
|
return (old_start, old_count, new_start, new_count)
|
||||||
|
|
||||||
def parse_diff(diff_text: str) -> List[DiffFile]:
|
def parse_diff(diff_text: str) -> List[DiffFile]:
|
||||||
"""
|
"""
|
||||||
[C: src/gui_2.py:App.request_patch_from_tier4, tests/test_diff_viewer.py:test_diff_line_classification, tests/test_diff_viewer.py:test_parse_diff_empty, tests/test_diff_viewer.py:test_parse_diff_none, tests/test_diff_viewer.py:test_parse_diff_with_context, tests/test_diff_viewer.py:test_parse_multiple_files, tests/test_diff_viewer.py:test_parse_simple_diff, tests/test_diff_viewer.py:test_render_diff_text_immediate]
|
[C: src/gui_2.py:App.request_patch_from_tier4, tests/test_diff_viewer.py:test_diff_line_classification, tests/test_diff_viewer.py:test_parse_diff_empty, tests/test_diff_viewer.py:test_parse_diff_none, tests/test_diff_viewer.py:test_parse_diff_with_context, tests/test_diff_viewer.py:test_parse_multiple_files, tests/test_diff_viewer.py:test_parse_simple_diff, tests/test_diff_viewer.py:test_render_diff_text_immediate]
|
||||||
"""
|
"""
|
||||||
if not diff_text or not diff_text.strip():
|
if not diff_text or not diff_text.strip():
|
||||||
return []
|
return []
|
||||||
|
|
||||||
files: List[DiffFile] = []
|
files: List[DiffFile] = []
|
||||||
current_file: Optional[DiffFile] = None
|
current_file: Optional[DiffFile] = None
|
||||||
current_hunk: Optional[DiffHunk] = None
|
current_hunk: Optional[DiffHunk] = None
|
||||||
|
|
||||||
for line in diff_text.split("\n"):
|
for line in diff_text.split("\n"):
|
||||||
if line.startswith("--- "):
|
if line.startswith("--- "):
|
||||||
if current_file:
|
if current_file:
|
||||||
if current_hunk:
|
|
||||||
current_file.hunks.append(current_hunk)
|
|
||||||
current_hunk = None
|
|
||||||
files.append(current_file)
|
|
||||||
|
|
||||||
path = line[4:]
|
|
||||||
if path.startswith("a/"):
|
|
||||||
path = path[2:]
|
|
||||||
current_file = DiffFile(old_path=path, new_path="", hunks=[])
|
|
||||||
|
|
||||||
elif line.startswith("+++ ") and current_file:
|
|
||||||
path = line[4:]
|
|
||||||
if path.startswith("b/"):
|
|
||||||
path = path[2:]
|
|
||||||
current_file.new_path = path
|
|
||||||
|
|
||||||
elif line.startswith("@@") and current_file:
|
|
||||||
if current_hunk:
|
|
||||||
current_file.hunks.append(current_hunk)
|
|
||||||
|
|
||||||
hunk_info = parse_hunk_header(line)
|
|
||||||
if hunk_info:
|
|
||||||
old_start, old_count, new_start, new_count = hunk_info
|
|
||||||
current_hunk = DiffHunk(
|
|
||||||
header=line,
|
|
||||||
lines=[],
|
|
||||||
old_start=old_start,
|
|
||||||
old_count=old_count,
|
|
||||||
new_start=new_start,
|
|
||||||
new_count=new_count
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
current_hunk = DiffHunk(
|
|
||||||
header=line,
|
|
||||||
lines=[],
|
|
||||||
old_start=0,
|
|
||||||
old_count=0,
|
|
||||||
new_start=0,
|
|
||||||
new_count=0
|
|
||||||
)
|
|
||||||
|
|
||||||
elif current_hunk is not None:
|
|
||||||
current_hunk.lines.append(line)
|
|
||||||
|
|
||||||
elif line and not line.startswith("diff ") and not line.startswith("index "):
|
|
||||||
pass
|
|
||||||
|
|
||||||
if current_file:
|
|
||||||
if current_hunk:
|
if current_hunk:
|
||||||
current_file.hunks.append(current_hunk)
|
current_file.hunks.append(current_hunk)
|
||||||
|
current_hunk = None
|
||||||
files.append(current_file)
|
files.append(current_file)
|
||||||
|
|
||||||
return files
|
path = line[4:]
|
||||||
|
if path.startswith("a/"):
|
||||||
def format_diff_for_display(diff_files: List[DiffFile]) -> str:
|
path = path[2:]
|
||||||
output = []
|
current_file = DiffFile(old_path=path, new_path="", hunks=[])
|
||||||
for df in diff_files:
|
|
||||||
output.append(f"File: {df.old_path}")
|
elif line.startswith("+++ ") and current_file:
|
||||||
for hunk in df.hunks:
|
path = line[4:]
|
||||||
output.append(f" {hunk.header}")
|
if path.startswith("b/"):
|
||||||
for line in hunk.lines:
|
path = path[2:]
|
||||||
output.append(f" {line}")
|
current_file.new_path = path
|
||||||
return "\n".join(output)
|
|
||||||
|
elif line.startswith("@@") and current_file:
|
||||||
|
if current_hunk:
|
||||||
|
current_file.hunks.append(current_hunk)
|
||||||
|
|
||||||
|
hunk_info = parse_hunk_header(line)
|
||||||
|
if hunk_info:
|
||||||
|
old_start, old_count, new_start, new_count = hunk_info
|
||||||
|
current_hunk = DiffHunk(
|
||||||
|
header=line,
|
||||||
|
lines=[],
|
||||||
|
old_start=old_start,
|
||||||
|
old_count=old_count,
|
||||||
|
new_start=new_start,
|
||||||
|
new_count=new_count
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
current_hunk = DiffHunk(
|
||||||
|
header=line,
|
||||||
|
lines=[],
|
||||||
|
old_start=0,
|
||||||
|
old_count=0,
|
||||||
|
new_start=0,
|
||||||
|
new_count=0
|
||||||
|
)
|
||||||
|
|
||||||
|
elif current_hunk is not None:
|
||||||
|
current_hunk.lines.append(line)
|
||||||
|
|
||||||
|
elif line and not line.startswith("diff ") and not line.startswith("index "):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if current_file:
|
||||||
|
if current_hunk:
|
||||||
|
current_file.hunks.append(current_hunk)
|
||||||
|
files.append(current_file)
|
||||||
|
|
||||||
|
return files
|
||||||
|
|
||||||
def get_line_color(line: str) -> Optional[str]:
|
def get_line_color(line: str) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
[C: tests/test_diff_viewer.py:test_get_line_color]
|
[C: tests/test_diff_viewer.py:test_get_line_color]
|
||||||
"""
|
"""
|
||||||
if line.startswith("+"):
|
if line.startswith("+"):
|
||||||
return "green"
|
return "green"
|
||||||
elif line.startswith("-"):
|
elif line.startswith("-"):
|
||||||
return "red"
|
return "red"
|
||||||
elif line.startswith("@@"):
|
elif line.startswith("@@"):
|
||||||
return "cyan"
|
return "cyan"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def render_diff_text_immediate(diff_files: List[DiffFile]) -> List[tuple[str, Optional[str]]]:
|
|
||||||
"""
|
|
||||||
[C: tests/test_diff_viewer.py:test_render_diff_text_immediate]
|
|
||||||
"""
|
|
||||||
output: List[tuple[str, Optional[str]]] = []
|
|
||||||
for df in diff_files:
|
|
||||||
output.append((f"File: {df.old_path}", "white"))
|
|
||||||
for hunk in df.hunks:
|
|
||||||
output.append((hunk.header, "cyan"))
|
|
||||||
for line in hunk.lines:
|
|
||||||
color = get_line_color(line)
|
|
||||||
output.append((line, color))
|
|
||||||
return output
|
|
||||||
|
|
||||||
def create_backup(file_path: str) -> Optional[str]:
|
|
||||||
"""
|
|
||||||
[C: tests/test_diff_viewer.py:test_create_backup, tests/test_diff_viewer.py:test_create_backup_nonexistent]
|
|
||||||
"""
|
|
||||||
path = Path(file_path)
|
|
||||||
if not path.exists():
|
|
||||||
return None
|
|
||||||
backup_path = path.with_suffix(path.suffix + ".backup")
|
|
||||||
shutil.copy2(path, backup_path)
|
|
||||||
return str(backup_path)
|
|
||||||
|
|
||||||
def apply_patch_to_file(patch_text: str, base_dir: str = ".") -> Tuple[bool, str]:
|
def apply_patch_to_file(patch_text: str, base_dir: str = ".") -> Tuple[bool, str]:
|
||||||
"""
|
"""
|
||||||
[C: src/gui_2.py:App._apply_pending_patch, tests/test_diff_viewer.py:test_apply_patch_simple, tests/test_diff_viewer.py:test_apply_patch_with_context]
|
[C: src/gui_2.py:App._apply_pending_patch, tests/test_diff_viewer.py:test_apply_patch_simple, tests/test_diff_viewer.py:test_apply_patch_with_context]
|
||||||
"""
|
"""
|
||||||
import difflib
|
import difflib
|
||||||
|
|
||||||
|
diff_files = parse_diff(patch_text)
|
||||||
|
if not diff_files:
|
||||||
|
return False, "No valid diff found"
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for df in diff_files:
|
||||||
|
file_path = Path(base_dir) / df.old_path
|
||||||
|
if not file_path.exists():
|
||||||
|
results.append(f"File not found: {file_path}")
|
||||||
|
continue
|
||||||
|
|
||||||
diff_files = parse_diff(patch_text)
|
try:
|
||||||
if not diff_files:
|
with open(file_path, "r", encoding="utf-8") as f:
|
||||||
return False, "No valid diff found"
|
original_lines = f.read().splitlines(keepends=True)
|
||||||
|
|
||||||
results = []
|
new_lines = original_lines.copy()
|
||||||
for df in diff_files:
|
offset = 0
|
||||||
file_path = Path(base_dir) / df.old_path
|
|
||||||
if not file_path.exists():
|
for hunk in df.hunks:
|
||||||
results.append(f"File not found: {file_path}")
|
hunk_old_start = hunk.old_start - 1
|
||||||
continue
|
hunk_old_count = hunk.old_count
|
||||||
|
|
||||||
try:
|
replace_start = hunk_old_start + offset
|
||||||
with open(file_path, "r", encoding="utf-8") as f:
|
replace_count = hunk_old_count
|
||||||
original_lines = f.read().splitlines(keepends=True)
|
|
||||||
|
hunk_new_content: List[str] = []
|
||||||
new_lines = original_lines.copy()
|
for line in hunk.lines:
|
||||||
offset = 0
|
if line.startswith("+") and not line.startswith("+++"):
|
||||||
|
hunk_new_content.append(line[1:] + "\n")
|
||||||
for hunk in df.hunks:
|
elif line.startswith(" ") or (line and not line.startswith(("-", "+", "@@"))):
|
||||||
hunk_old_start = hunk.old_start - 1
|
hunk_new_content.append(line + "\n")
|
||||||
hunk_old_count = hunk.old_count
|
|
||||||
|
new_lines = new_lines[:replace_start] + hunk_new_content + new_lines[replace_start + replace_count:]
|
||||||
replace_start = hunk_old_start + offset
|
offset += len(hunk_new_content) - replace_count
|
||||||
replace_count = hunk_old_count
|
|
||||||
|
with open(file_path, "w", encoding="utf-8", newline="") as f:
|
||||||
hunk_new_content: List[str] = []
|
f.writelines(new_lines)
|
||||||
for line in hunk.lines:
|
|
||||||
if line.startswith("+") and not line.startswith("+++"):
|
results.append(f"Patched: {file_path}")
|
||||||
hunk_new_content.append(line[1:] + "\n")
|
except Exception as e:
|
||||||
elif line.startswith(" ") or (line and not line.startswith(("-", "+", "@@"))):
|
return False, f"Error patching {file_path}: {e}"
|
||||||
hunk_new_content.append(line + "\n")
|
|
||||||
|
return True, "\n".join(results)
|
||||||
new_lines = new_lines[:replace_start] + hunk_new_content + new_lines[replace_start + replace_count:]
|
|
||||||
offset += len(hunk_new_content) - replace_count
|
|
||||||
|
|
||||||
with open(file_path, "w", encoding="utf-8", newline="") as f:
|
|
||||||
f.writelines(new_lines)
|
|
||||||
|
|
||||||
results.append(f"Patched: {file_path}")
|
|
||||||
except Exception as e:
|
|
||||||
return False, f"Error patching {file_path}: {e}"
|
|
||||||
|
|
||||||
return True, "\n".join(results)
|
|
||||||
|
|
||||||
def restore_from_backup(file_path: str) -> bool:
|
|
||||||
"""
|
|
||||||
[C: tests/test_diff_viewer.py:test_restore_from_backup]
|
|
||||||
"""
|
|
||||||
backup_path = Path(str(file_path) + ".backup")
|
|
||||||
if not backup_path.exists():
|
|
||||||
return False
|
|
||||||
|
|
||||||
shutil.copy2(backup_path, file_path)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def cleanup_backup(file_path: str) -> None:
|
|
||||||
"""
|
|
||||||
[C: tests/test_diff_viewer.py:test_cleanup_backup]
|
|
||||||
"""
|
|
||||||
backup_path = Path(str(file_path) + ".backup")
|
|
||||||
if backup_path.exists():
|
|
||||||
backup_path.unlink()
|
|
||||||
|
|||||||
@@ -325,8 +325,6 @@ class WorkerContext:
|
|||||||
persona_id: Optional[str] = None
|
persona_id: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
@dataclass
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Metadata:
|
class Metadata:
|
||||||
id: str
|
id: str
|
||||||
@@ -442,79 +440,6 @@ class ExternalEditorConfig:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class TextEditorConfig:
|
|
||||||
name: str
|
|
||||||
path: str
|
|
||||||
diff_args: List[str] = field(default_factory=list)
|
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
[C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
"name": self.name,
|
|
||||||
"path": self.path,
|
|
||||||
"diff_args": self.diff_args,
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls, data: Dict[str, Any]) -> "TextEditorConfig":
|
|
||||||
"""
|
|
||||||
[C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
|
|
||||||
"""
|
|
||||||
return cls(
|
|
||||||
name=data["name"],
|
|
||||||
path=data["path"],
|
|
||||||
diff_args=data.get("diff_args", []),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ExternalEditorConfig:
|
|
||||||
editors: Dict[str, TextEditorConfig] = field(default_factory=dict)
|
|
||||||
default_editor: Optional[str] = None
|
|
||||||
|
|
||||||
def get_default(self) -> Optional[TextEditorConfig]:
|
|
||||||
"""
|
|
||||||
[C: tests/test_external_editor.py:TestExternalEditorConfig.test_get_default_fallback_to_first, tests/test_external_editor.py:TestExternalEditorConfig.test_get_default_returns_configured, tests/test_external_editor.py:TestExternalEditorConfig.test_get_default_returns_none_when_empty]
|
|
||||||
"""
|
|
||||||
if self.default_editor and self.default_editor in self.editors:
|
|
||||||
return self.editors[self.default_editor]
|
|
||||||
if self.editors:
|
|
||||||
return next(iter(self.editors.values()))
|
|
||||||
return None
|
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
[C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
"editors": {k: v.to_dict() for k, v in self.editors.items()},
|
|
||||||
"default_editor": self.default_editor,
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls, data: Dict[str, Any]) -> "ExternalEditorConfig":
|
|
||||||
"""
|
|
||||||
[C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags]
|
|
||||||
"""
|
|
||||||
editors = {}
|
|
||||||
for name, ed_data in data.get("editors", {}).items():
|
|
||||||
if isinstance(ed_data, dict):
|
|
||||||
editors[name] = TextEditorConfig(
|
|
||||||
name=name,
|
|
||||||
path=ed_data.get("path", ""),
|
|
||||||
diff_args=ed_data.get("diff_args", []),
|
|
||||||
)
|
|
||||||
elif isinstance(ed_data, str):
|
|
||||||
editors[name] = TextEditorConfig(name=name, path=ed_data)
|
|
||||||
return cls(
|
|
||||||
editors=editors,
|
|
||||||
default_editor=data.get("default_editor"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TrackState:
|
class TrackState:
|
||||||
metadata: Metadata
|
metadata: Metadata
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
# src/native_orchestrator.py
|
|
||||||
# DEPRECATED: This module is legacy and is being replaced by src/orchestrator_pm.py
|
|
||||||
# and the MMA multi-agent conductor system. Avoid using this for new features.
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Optional
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
from src import paths
|
|
||||||
|
|
||||||
def read_plan(track_id: str, base_dir: str = ".") -> str:
|
|
||||||
"""
|
|
||||||
Reads the implementation plan (plan.md) for a track.
|
|
||||||
[C: tests/test_native_orchestrator.py:test_read_plan_nonexistent, tests/test_native_orchestrator.py:test_write_and_read_plan]
|
|
||||||
"""
|
|
||||||
plan_path = paths.get_track_state_dir(track_id, base_dir) / "plan.md"
|
|
||||||
if not plan_path.exists():
|
|
||||||
return ""
|
|
||||||
return plan_path.read_text(encoding="utf-8")
|
|
||||||
|
|
||||||
def write_plan(track_id: str, content: str, base_dir: str = ".") -> None:
|
|
||||||
"""
|
|
||||||
Writes the implementation plan (plan.md) for a track.
|
|
||||||
[C: tests/test_native_orchestrator.py:test_write_and_read_plan]
|
|
||||||
"""
|
|
||||||
plan_path = paths.get_track_state_dir(track_id, base_dir) / "plan.md"
|
|
||||||
plan_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
plan_path.write_text(content, encoding="utf-8")
|
|
||||||
|
|
||||||
def parse_plan_tasks(content: str) -> list[dict[str, str]]:
|
|
||||||
"""
|
|
||||||
Parses the tasks from a plan.md file.
|
|
||||||
[C: tests/test_native_orchestrator.py:test_parse_plan_tasks]
|
|
||||||
"""
|
|
||||||
tasks = []
|
|
||||||
for line in content.split("\n"):
|
|
||||||
stripped = line.strip()
|
|
||||||
if stripped.startswith("- ["):
|
|
||||||
checked = "[x]" in stripped
|
|
||||||
text = stripped[4:] if len(stripped) > 4 else ""
|
|
||||||
tasks.append({"text": text, "completed": checked})
|
|
||||||
return tasks
|
|
||||||
|
|
||||||
def read_metadata(track_id: str, base_dir: str = ".") -> dict:
|
|
||||||
"""
|
|
||||||
Reads the metadata (metadata.json) for a track.
|
|
||||||
[C: tests/test_native_orchestrator.py:test_read_metadata_nonexistent, tests/test_native_orchestrator.py:test_write_and_read_metadata]
|
|
||||||
"""
|
|
||||||
meta_path = paths.get_track_state_dir(track_id, base_dir) / "metadata.json"
|
|
||||||
if not meta_path.exists():
|
|
||||||
return {}
|
|
||||||
return json.loads(meta_path.read_text(encoding="utf-8"))
|
|
||||||
|
|
||||||
def write_metadata(track_id: str, data: dict, base_dir: str = ".") -> None:
|
|
||||||
"""
|
|
||||||
Writes the metadata (metadata.json) for a track.
|
|
||||||
[C: tests/test_native_orchestrator.py:test_write_and_read_metadata]
|
|
||||||
"""
|
|
||||||
meta_path = paths.get_track_state_dir(track_id, base_dir) / "metadata.json"
|
|
||||||
meta_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
meta_path.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
|
||||||
|
|
||||||
def get_track_dir(track_id: str, base_dir: str = ".") -> Path:
|
|
||||||
"""
|
|
||||||
Returns the state directory for a specific track.
|
|
||||||
[C: tests/test_native_orchestrator.py:test_get_track_dir]
|
|
||||||
"""
|
|
||||||
return paths.get_track_state_dir(track_id, base_dir)
|
|
||||||
|
|
||||||
def get_archive_dir(base_dir: str = ".") -> Path:
|
|
||||||
"""
|
|
||||||
Returns the central archive directory for completed tracks.
|
|
||||||
[C: src/orchestrator_pm.py:get_track_history_summary, tests/test_native_orchestrator.py:test_get_archive_dir, tests/test_paths.py:test_conductor_dir_project_relative]
|
|
||||||
"""
|
|
||||||
return paths.get_archive_dir(base_dir)
|
|
||||||
|
|
||||||
class NativeOrchestrator:
|
|
||||||
def __init__(self, base_dir: str = "."):
|
|
||||||
self.base_dir = Path(base_dir)
|
|
||||||
self._conductor = None
|
|
||||||
|
|
||||||
def load_track(self, track_id: str) -> dict:
|
|
||||||
"""
|
|
||||||
Load track from metadata.json
|
|
||||||
[C: tests/test_native_orchestrator.py:test_native_orchestrator_class]
|
|
||||||
"""
|
|
||||||
return read_metadata(track_id, str(self.base_dir))
|
|
||||||
|
|
||||||
def save_track(self, track_id: str, data: dict) -> None:
|
|
||||||
"""
|
|
||||||
Persist track metadata
|
|
||||||
[C: tests/test_native_orchestrator.py:test_native_orchestrator_class]
|
|
||||||
"""
|
|
||||||
write_metadata(track_id, data, str(self.base_dir))
|
|
||||||
|
|
||||||
def load_plan(self, track_id: str) -> str:
|
|
||||||
"""
|
|
||||||
Load plan.md content
|
|
||||||
[C: tests/test_native_orchestrator.py:test_native_orchestrator_class]
|
|
||||||
"""
|
|
||||||
return read_plan(track_id, str(self.base_dir))
|
|
||||||
|
|
||||||
def save_plan(self, track_id: str, content: str) -> None:
|
|
||||||
"""
|
|
||||||
Persist plan.md content
|
|
||||||
[C: tests/test_native_orchestrator.py:test_native_orchestrator_class]
|
|
||||||
"""
|
|
||||||
write_plan(track_id, content, str(self.base_dir))
|
|
||||||
|
|
||||||
def get_track_tasks(self, track_id: str) -> list[dict]:
|
|
||||||
"""
|
|
||||||
Get parsed task list from plan.md
|
|
||||||
[C: tests/test_native_orchestrator.py:test_native_orchestrator_class]
|
|
||||||
"""
|
|
||||||
content = self.load_plan(track_id)
|
|
||||||
return parse_plan_tasks(content)
|
|
||||||
|
|
||||||
def generate_tickets(self, brief: str, module_skeletons: str = "") -> list[dict]:
|
|
||||||
"""
|
|
||||||
Tier 2: Generate tickets from brief
|
|
||||||
[C: tests/test_conductor_tech_lead.py:TestConductorTechLead.test_generate_tickets_retry_failure, tests/test_conductor_tech_lead.py:TestConductorTechLead.test_generate_tickets_retry_success, tests/test_conductor_tech_lead.py:TestConductorTechLead.test_generate_tickets_success, tests/test_orchestration_logic.py:test_generate_tickets]
|
|
||||||
"""
|
|
||||||
from src import conductor_tech_lead
|
|
||||||
return conductor_tech_lead.generate_tickets(brief, module_skeletons)
|
|
||||||
|
|
||||||
def execute_ticket(self, ticket: dict, context: str) -> str:
|
|
||||||
"""Tier 3: Execute single ticket"""
|
|
||||||
from src import ai_client
|
|
||||||
return ai_client.send(context, ticket.get("description", ""), str(self.base_dir))
|
|
||||||
|
|
||||||
def analyze_error(self, error: str) -> str:
|
|
||||||
"""Tier 4: Analyze error"""
|
|
||||||
from src import ai_client
|
|
||||||
return ai_client.run_tier4_analysis(error)
|
|
||||||
|
|
||||||
def run_tier4_patch(self, error: str, file_context: str) -> str:
|
|
||||||
"""Tier 4: Generate patch for error"""
|
|
||||||
from src import ai_client
|
|
||||||
return ai_client.run_tier4_patch_generation(error, file_context)
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
import OpenGL.GL as gl
|
|
||||||
|
|
||||||
class ShaderManager:
|
|
||||||
def __init__(self):
|
|
||||||
self.program = None
|
|
||||||
self.bg_program = None
|
|
||||||
self.pp_program = None
|
|
||||||
|
|
||||||
def compile_shader(self, vertex_src: str, fragment_src: str) -> int:
|
|
||||||
"""
|
|
||||||
[C: tests/test_shader_manager.py:test_shader_manager_initialization_and_compilation]
|
|
||||||
"""
|
|
||||||
program = gl.glCreateProgram()
|
|
||||||
|
|
||||||
def _compile(src, shader_type):
|
|
||||||
shader = gl.glCreateShader(shader_type)
|
|
||||||
gl.glShaderSource(shader, src)
|
|
||||||
gl.glCompileShader(shader)
|
|
||||||
|
|
||||||
if not gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS):
|
|
||||||
info_log = gl.glGetShaderInfoLog(shader)
|
|
||||||
if hasattr(info_log, "decode"):
|
|
||||||
info_log = info_log.decode()
|
|
||||||
raise RuntimeError(f"Shader compilation failed: {info_log}")
|
|
||||||
return shader
|
|
||||||
|
|
||||||
vert_shader = _compile(vertex_src, gl.GL_VERTEX_SHADER)
|
|
||||||
frag_shader = _compile(fragment_src, gl.GL_FRAGMENT_SHADER)
|
|
||||||
|
|
||||||
gl.glAttachShader(program, vert_shader)
|
|
||||||
gl.glAttachShader(program, frag_shader)
|
|
||||||
gl.glLinkProgram(program)
|
|
||||||
|
|
||||||
if not gl.glGetProgramiv(program, gl.GL_LINK_STATUS):
|
|
||||||
info_log = gl.glGetProgramInfoLog(program)
|
|
||||||
if hasattr(info_log, "decode"):
|
|
||||||
info_log = info_log.decode()
|
|
||||||
raise RuntimeError(f"Program linking failed: {info_log}")
|
|
||||||
|
|
||||||
gl.glDeleteShader(vert_shader)
|
|
||||||
gl.glDeleteShader(frag_shader)
|
|
||||||
|
|
||||||
self.program = program
|
|
||||||
return program
|
|
||||||
|
|
||||||
def update_uniforms(self, uniforms: dict):
|
|
||||||
"""
|
|
||||||
[C: tests/test_shader_manager.py:test_shader_manager_uniform_update]
|
|
||||||
"""
|
|
||||||
if self.program is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
for name, value in uniforms.items():
|
|
||||||
loc = gl.glGetUniformLocation(self.program, name)
|
|
||||||
if loc == -1:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(value, float):
|
|
||||||
gl.glUniform1f(loc, value)
|
|
||||||
elif isinstance(value, int):
|
|
||||||
gl.glUniform1i(loc, value)
|
|
||||||
elif isinstance(value, (list, tuple)):
|
|
||||||
if len(value) == 2:
|
|
||||||
gl.glUniform2f(loc, value[0], value[1])
|
|
||||||
elif len(value) == 3:
|
|
||||||
gl.glUniform3f(loc, value[0], value[1], value[2])
|
|
||||||
elif len(value) == 4:
|
|
||||||
gl.glUniform4f(loc, value[0], value[1], value[2], value[3])
|
|
||||||
|
|
||||||
def setup_background_shader(self):
|
|
||||||
"""
|
|
||||||
[C: tests/test_dynamic_background.py:test_dynamic_background_rendering]
|
|
||||||
"""
|
|
||||||
vertex_src = """
|
|
||||||
#version 330 core
|
|
||||||
const vec2 positions[4] = vec2[](
|
|
||||||
vec2(-1.0, -1.0),
|
|
||||||
vec2( 1.0, -1.0),
|
|
||||||
vec2(-1.0, 1.0),
|
|
||||||
vec2( 1.0, 1.0)
|
|
||||||
);
|
|
||||||
void main() {
|
|
||||||
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
fragment_src = """
|
|
||||||
#version 330 core
|
|
||||||
uniform float u_time;
|
|
||||||
uniform vec2 u_resolution;
|
|
||||||
out vec4 FragColor;
|
|
||||||
void main() {
|
|
||||||
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
|
|
||||||
vec3 col = 0.5 + 0.5 * cos(u_time + uv.xyx + vec3(0, 2, 4));
|
|
||||||
FragColor = vec4(col, 1.0);
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
self.bg_program = self.compile_shader(vertex_src, fragment_src)
|
|
||||||
|
|
||||||
def render_background(self, width, height, time):
|
|
||||||
"""
|
|
||||||
[C: tests/test_dynamic_background.py:test_dynamic_background_rendering]
|
|
||||||
"""
|
|
||||||
if not self.bg_program:
|
|
||||||
return
|
|
||||||
gl.glUseProgram(self.bg_program)
|
|
||||||
u_time_loc = gl.glGetUniformLocation(self.bg_program, "u_time")
|
|
||||||
if u_time_loc != -1:
|
|
||||||
gl.glUniform1f(u_time_loc, float(time))
|
|
||||||
u_res_loc = gl.glGetUniformLocation(self.bg_program, "u_resolution")
|
|
||||||
if u_res_loc != -1:
|
|
||||||
gl.glUniform2f(u_res_loc, float(width), float(height))
|
|
||||||
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
|
||||||
gl.glUseProgram(0)
|
|
||||||
|
|
||||||
def setup_post_process_shader(self):
|
|
||||||
"""
|
|
||||||
[C: tests/test_post_process.py:TestPostProcess.test_setup_post_process_shader]
|
|
||||||
"""
|
|
||||||
vertex_src = """
|
|
||||||
#version 330 core
|
|
||||||
const vec2 positions[4] = vec2[](
|
|
||||||
vec2(-1.0, -1.0),
|
|
||||||
vec2( 1.0, -1.0),
|
|
||||||
vec2(-1.0, 1.0),
|
|
||||||
vec2( 1.0, 1.0)
|
|
||||||
);
|
|
||||||
const vec2 uvs[4] = vec2[](
|
|
||||||
vec2(0.0, 0.0),
|
|
||||||
vec2(1.0, 0.0),
|
|
||||||
vec2(0.0, 1.0),
|
|
||||||
vec2(1.0, 1.0)
|
|
||||||
);
|
|
||||||
out vec2 v_uv;
|
|
||||||
void main() {
|
|
||||||
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
|
|
||||||
v_uv = uvs[gl_VertexID];
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
fragment_src = """
|
|
||||||
#version 330 core
|
|
||||||
in vec2 v_uv;
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_time;
|
|
||||||
out vec4 FragColor;
|
|
||||||
void main() {
|
|
||||||
vec4 color = texture(u_texture, v_uv);
|
|
||||||
float scanline = sin(v_uv.y * 800.0 + u_time * 2.0) * 0.04;
|
|
||||||
color.rgb -= scanline;
|
|
||||||
FragColor = color;
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
self.pp_program = self.compile_shader(vertex_src, fragment_src)
|
|
||||||
|
|
||||||
def render_post_process(self, texture_id, width, height, time):
|
|
||||||
"""
|
|
||||||
[C: tests/test_post_process.py:TestPostProcess.test_render_post_process]
|
|
||||||
"""
|
|
||||||
if not self.pp_program:
|
|
||||||
return
|
|
||||||
gl.glUseProgram(self.pp_program)
|
|
||||||
gl.glActiveTexture(gl.GL_TEXTURE0)
|
|
||||||
gl.glBindTexture(gl.GL_TEXTURE_2D, texture_id)
|
|
||||||
u_tex_loc = gl.glGetUniformLocation(self.pp_program, "u_texture")
|
|
||||||
if u_tex_loc != -1:
|
|
||||||
gl.glUniform1i(u_tex_loc, 0)
|
|
||||||
u_time_loc = gl.glGetUniformLocation(self.pp_program, "u_time")
|
|
||||||
if u_time_loc != -1:
|
|
||||||
gl.glUniform1f(u_time_loc, float(time))
|
|
||||||
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
|
||||||
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
|
|
||||||
gl.glUseProgram(0)
|
|
||||||
+35
-71
@@ -1,74 +1,38 @@
|
|||||||
from imgui_bundle import imgui
|
from imgui_bundle import imgui
|
||||||
|
|
||||||
def draw_soft_shadow(draw_list: imgui.ImDrawList, p_min: imgui.ImVec2, p_max: imgui.ImVec2, color: imgui.ImVec4, shadow_size: float = 10.0, rounding: float = 0.0) -> None:
|
def draw_soft_shadow(draw_list: imgui.ImDrawList, p_min: imgui.ImVec2, p_max: imgui.ImVec2, color: imgui.ImVec4, shadow_size: float = 10.0, rounding: float = 0.0) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Simulates a soft shadow effect by drawing multiple concentric rounded rectangles
|
Simulates a soft shadow effect by drawing multiple concentric rounded rectangles
|
||||||
with decreasing alpha values. This is a faux-shader effect using primitive batching.
|
with decreasing alpha values. This is a faux-shader effect using primitive batching.
|
||||||
"""
|
"""
|
||||||
r, g, b, a = color.x, color.y, color.z, color.w
|
r, g, b, a = color.x, color.y, color.z, color.w
|
||||||
steps = int(shadow_size)
|
steps = int(shadow_size)
|
||||||
if steps <= 0:
|
if steps <= 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
alpha_step = a / steps
|
alpha_step = a / steps
|
||||||
|
|
||||||
for i in range(steps):
|
for i in range(steps):
|
||||||
current_alpha = a - (i * alpha_step)
|
current_alpha = a - (i * alpha_step)
|
||||||
# Apply an easing function (e.g., cubic) for a smoother shadow falloff
|
# Apply an easing function (e.g., cubic) for a smoother shadow falloff
|
||||||
current_alpha = current_alpha * (1.0 - (i / steps)**2)
|
current_alpha = current_alpha * (1.0 - (i / steps)**2)
|
||||||
|
|
||||||
if current_alpha <= 0.01:
|
if current_alpha <= 0.01:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
expand = float(i)
|
expand = float(i)
|
||||||
|
|
||||||
c_min = imgui.ImVec2(p_min.x - expand, p_min.y - expand)
|
c_min = imgui.ImVec2(p_min.x - expand, p_min.y - expand)
|
||||||
c_max = imgui.ImVec2(p_max.x + expand, p_max.y + expand)
|
c_max = imgui.ImVec2(p_max.x + expand, p_max.y + expand)
|
||||||
|
|
||||||
u32_color = imgui.get_color_u32(imgui.ImVec4(r, g, b, current_alpha))
|
u32_color = imgui.get_color_u32(imgui.ImVec4(r, g, b, current_alpha))
|
||||||
|
|
||||||
draw_list.add_rect(
|
draw_list.add_rect(
|
||||||
c_min,
|
c_min,
|
||||||
c_max,
|
c_max,
|
||||||
u32_color,
|
u32_color,
|
||||||
rounding + expand if rounding > 0 else 0.0,
|
rounding + expand if rounding > 0 else 0.0,
|
||||||
flags=imgui.ImDrawFlags_.round_corners_all if rounding > 0 else imgui.ImDrawFlags_.none,
|
flags=imgui.ImDrawFlags_.round_corners_all if rounding > 0 else imgui.ImDrawFlags_.none,
|
||||||
thickness=1.0
|
thickness=1.0
|
||||||
)
|
)
|
||||||
|
|
||||||
def apply_faux_acrylic_glass(draw_list: imgui.ImDrawList, p_min: imgui.ImVec2, p_max: imgui.ImVec2, base_color: imgui.ImVec4, rounding: float = 0.0) -> None:
|
|
||||||
"""
|
|
||||||
|
|
||||||
Simulates a faux acrylic/glass effect by drawing a semi-transparent base,
|
|
||||||
a gradient overlay for 'shine', and a subtle inner border for 'edge glow'.
|
|
||||||
"""
|
|
||||||
r, g, b, a = base_color.x, base_color.y, base_color.z, base_color.w
|
|
||||||
|
|
||||||
# 1. Base tinted semi-transparent fill (fake blur base)
|
|
||||||
fill_color = imgui.get_color_u32(imgui.ImVec4(r, g, b, a * 0.7))
|
|
||||||
draw_list.add_rect_filled(
|
|
||||||
p_min, p_max, fill_color, rounding,
|
|
||||||
flags=imgui.ImDrawFlags_.round_corners_all if rounding > 0 else imgui.ImDrawFlags_.none
|
|
||||||
)
|
|
||||||
|
|
||||||
# 2. Gradient overlay to simulate light scattering (acrylic reflection)
|
|
||||||
shine_top = imgui.get_color_u32(imgui.ImVec4(1.0, 1.0, 1.0, 0.15))
|
|
||||||
shine_bot = imgui.get_color_u32(imgui.ImVec4(1.0, 1.0, 1.0, 0.0))
|
|
||||||
# We can't do rounded corners with add_rect_filled_multicolor easily, but we can do it with clip rects or just draw it directly if rounding=0
|
|
||||||
# For now, we'll just draw a subtle overlay in the upper half
|
|
||||||
if rounding == 0:
|
|
||||||
draw_list.add_rect_filled_multicolor(
|
|
||||||
p_min, imgui.ImVec2(p_max.x, p_min.y + (p_max.y - p_min.y) * 0.5),
|
|
||||||
shine_top, shine_top, shine_bot, shine_bot
|
|
||||||
)
|
|
||||||
|
|
||||||
# 3. Inner bright border to simulate "glass edge" refraction
|
|
||||||
inner_glow = imgui.get_color_u32(imgui.ImVec4(1.0, 1.0, 1.0, 0.2))
|
|
||||||
draw_list.add_rect(
|
|
||||||
imgui.ImVec2(p_min.x + 1, p_min.y + 1),
|
|
||||||
imgui.ImVec2(p_max.x - 1, p_max.y - 1),
|
|
||||||
inner_glow, rounding,
|
|
||||||
flags=imgui.ImDrawFlags_.round_corners_all if rounding > 0 else imgui.ImDrawFlags_.none,
|
|
||||||
thickness=1.0
|
|
||||||
)
|
|
||||||
|
|||||||
-424
@@ -1,424 +0,0 @@
|
|||||||
# theme.py
|
|
||||||
"""
|
|
||||||
Theming support for manual_slop GUI.
|
|
||||||
|
|
||||||
Palettes
|
|
||||||
--------
|
|
||||||
Each palette is a dict mapping semantic names to (R,G,B) or (R,G,B,A) tuples.
|
|
||||||
The names correspond to dpg theme colour / style constants.
|
|
||||||
|
|
||||||
Font handling
|
|
||||||
-------------
|
|
||||||
Call apply_font(path, size) to load a TTF and bind it as the global default.
|
|
||||||
Call set_scale(factor) to set the global font scale (DPI scaling).
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-----
|
|
||||||
import theme
|
|
||||||
theme.apply("10x") # apply a named palette
|
|
||||||
theme.apply_font("C:/Windows/Fonts/CascadiaCode.ttf", 15)
|
|
||||||
theme.set_scale(1.25)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import dearpygui.dearpygui as dpg
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------ palettes
|
|
||||||
|
|
||||||
# Colour key names match the DPG mvThemeCol_* constants (string lookup below).
|
|
||||||
# Only keys that differ from DPG defaults need to be listed.
|
|
||||||
|
|
||||||
_PALETTES: dict[str, dict[str, Any]] = {
|
|
||||||
"DPG Default": {}, # empty = reset to DPG built-in defaults
|
|
||||||
"10x Dark": {
|
|
||||||
# Window / frame chrome
|
|
||||||
"WindowBg": ( 34, 32, 28),
|
|
||||||
"ChildBg": ( 30, 28, 24),
|
|
||||||
"PopupBg": ( 35, 30, 20),
|
|
||||||
"Border": ( 60, 55, 50),
|
|
||||||
"BorderShadow": ( 0, 0, 0, 0),
|
|
||||||
"FrameBg": ( 45, 42, 38),
|
|
||||||
"FrameBgHovered": ( 60, 56, 50),
|
|
||||||
"FrameBgActive": ( 75, 70, 62),
|
|
||||||
# Title bars
|
|
||||||
"TitleBg": ( 40, 35, 25),
|
|
||||||
"TitleBgActive": ( 60, 45, 15),
|
|
||||||
"TitleBgCollapsed": ( 30, 27, 20),
|
|
||||||
# Menu bar
|
|
||||||
"MenuBarBg": ( 35, 30, 20),
|
|
||||||
# Scrollbar
|
|
||||||
"ScrollbarBg": ( 30, 28, 24),
|
|
||||||
"ScrollbarGrab": ( 80, 78, 72),
|
|
||||||
"ScrollbarGrabHovered": (100, 100, 92),
|
|
||||||
"ScrollbarGrabActive": (120, 118, 110),
|
|
||||||
# Check marks / radio buttons
|
|
||||||
"CheckMark": (194, 164, 74),
|
|
||||||
# Sliders
|
|
||||||
"SliderGrab": (126, 78, 14),
|
|
||||||
"SliderGrabActive": (194, 140, 30),
|
|
||||||
# Buttons
|
|
||||||
"Button": ( 83, 76, 60),
|
|
||||||
"ButtonHovered": (126, 78, 14),
|
|
||||||
"ButtonActive": (115, 90, 70),
|
|
||||||
# Headers (collapsing headers, selectables, listbox items)
|
|
||||||
"Header": ( 83, 76, 60),
|
|
||||||
"HeaderHovered": (126, 78, 14),
|
|
||||||
"HeaderActive": (115, 90, 70),
|
|
||||||
# Separator
|
|
||||||
"Separator": ( 70, 65, 55),
|
|
||||||
"SeparatorHovered": (126, 78, 14),
|
|
||||||
"SeparatorActive": (194, 164, 74),
|
|
||||||
# Resize grip
|
|
||||||
"ResizeGrip": ( 60, 55, 44),
|
|
||||||
"ResizeGripHovered": (126, 78, 14),
|
|
||||||
"ResizeGripActive": (194, 164, 74),
|
|
||||||
# Tab bar
|
|
||||||
"Tab": ( 83, 83, 70),
|
|
||||||
"TabHovered": (126, 77, 25),
|
|
||||||
"TabActive": (126, 77, 25),
|
|
||||||
"TabUnfocused": ( 60, 58, 50),
|
|
||||||
"TabUnfocusedActive": ( 90, 80, 55),
|
|
||||||
# Docking
|
|
||||||
"DockingPreview": (126, 78, 14, 180),
|
|
||||||
"DockingEmptyBg": ( 20, 20, 20),
|
|
||||||
# Text
|
|
||||||
"Text": (200, 200, 200),
|
|
||||||
"TextDisabled": (130, 130, 120),
|
|
||||||
# Input text cursor / selection
|
|
||||||
"TextSelectedBg": ( 59, 86, 142, 180),
|
|
||||||
# Plot / table lines
|
|
||||||
"TableHeaderBg": ( 55, 50, 38),
|
|
||||||
"TableBorderStrong": ( 70, 65, 55),
|
|
||||||
"TableBorderLight": ( 50, 47, 42),
|
|
||||||
"TableRowBg": ( 0, 0, 0, 0),
|
|
||||||
"TableRowBgAlt": ( 40, 38, 34, 40),
|
|
||||||
# Misc
|
|
||||||
"NavHighlight": (126, 78, 14),
|
|
||||||
"NavWindowingHighlight":(194, 164, 74, 180),
|
|
||||||
"NavWindowingDimBg": ( 20, 20, 20, 80),
|
|
||||||
"ModalWindowDimBg": ( 10, 10, 10, 100),
|
|
||||||
},
|
|
||||||
"Nord Dark": {
|
|
||||||
"WindowBg": ( 36, 41, 49),
|
|
||||||
"ChildBg": ( 30, 34, 42),
|
|
||||||
"PopupBg": ( 36, 41, 49),
|
|
||||||
"Border": ( 59, 66, 82),
|
|
||||||
"BorderShadow": ( 0, 0, 0, 0),
|
|
||||||
"FrameBg": ( 46, 52, 64),
|
|
||||||
"FrameBgHovered": ( 59, 66, 82),
|
|
||||||
"FrameBgActive": ( 67, 76, 94),
|
|
||||||
"TitleBg": ( 36, 41, 49),
|
|
||||||
"TitleBgActive": ( 59, 66, 82),
|
|
||||||
"TitleBgCollapsed": ( 30, 34, 42),
|
|
||||||
"MenuBarBg": ( 46, 52, 64),
|
|
||||||
"ScrollbarBg": ( 30, 34, 42),
|
|
||||||
"ScrollbarGrab": ( 76, 86, 106),
|
|
||||||
"ScrollbarGrabHovered": ( 94, 129, 172),
|
|
||||||
"ScrollbarGrabActive": (129, 161, 193),
|
|
||||||
"CheckMark": (136, 192, 208),
|
|
||||||
"SliderGrab": ( 94, 129, 172),
|
|
||||||
"SliderGrabActive": (129, 161, 193),
|
|
||||||
"Button": ( 59, 66, 82),
|
|
||||||
"ButtonHovered": ( 94, 129, 172),
|
|
||||||
"ButtonActive": (129, 161, 193),
|
|
||||||
"Header": ( 59, 66, 82),
|
|
||||||
"HeaderHovered": ( 94, 129, 172),
|
|
||||||
"HeaderActive": (129, 161, 193),
|
|
||||||
"Separator": ( 59, 66, 82),
|
|
||||||
"SeparatorHovered": ( 94, 129, 172),
|
|
||||||
"SeparatorActive": (136, 192, 208),
|
|
||||||
"ResizeGrip": ( 59, 66, 82),
|
|
||||||
"ResizeGripHovered": ( 94, 129, 172),
|
|
||||||
"ResizeGripActive": (136, 192, 208),
|
|
||||||
"Tab": ( 46, 52, 64),
|
|
||||||
"TabHovered": ( 94, 129, 172),
|
|
||||||
"TabActive": ( 76, 86, 106),
|
|
||||||
"TabUnfocused": ( 36, 41, 49),
|
|
||||||
"TabUnfocusedActive": ( 59, 66, 82),
|
|
||||||
"DockingPreview": ( 94, 129, 172, 180),
|
|
||||||
"DockingEmptyBg": ( 20, 22, 28),
|
|
||||||
"Text": (216, 222, 233),
|
|
||||||
"TextDisabled": (116, 128, 150),
|
|
||||||
"TextSelectedBg": ( 94, 129, 172, 180),
|
|
||||||
"TableHeaderBg": ( 59, 66, 82),
|
|
||||||
"TableBorderStrong": ( 76, 86, 106),
|
|
||||||
"TableBorderLight": ( 59, 66, 82),
|
|
||||||
"TableRowBg": ( 0, 0, 0, 0),
|
|
||||||
"TableRowBgAlt": ( 46, 52, 64, 40),
|
|
||||||
"NavHighlight": (136, 192, 208),
|
|
||||||
"ModalWindowDimBg": ( 10, 12, 16, 100),
|
|
||||||
},
|
|
||||||
"Monokai": {
|
|
||||||
"WindowBg": ( 39, 40, 34),
|
|
||||||
"ChildBg": ( 34, 35, 29),
|
|
||||||
"PopupBg": ( 39, 40, 34),
|
|
||||||
"Border": ( 60, 61, 52),
|
|
||||||
"BorderShadow": ( 0, 0, 0, 0),
|
|
||||||
"FrameBg": ( 50, 51, 44),
|
|
||||||
"FrameBgHovered": ( 65, 67, 56),
|
|
||||||
"FrameBgActive": ( 80, 82, 68),
|
|
||||||
"TitleBg": ( 39, 40, 34),
|
|
||||||
"TitleBgActive": ( 73, 72, 62),
|
|
||||||
"TitleBgCollapsed": ( 30, 31, 26),
|
|
||||||
"MenuBarBg": ( 50, 51, 44),
|
|
||||||
"ScrollbarBg": ( 34, 35, 29),
|
|
||||||
"ScrollbarGrab": ( 80, 80, 72),
|
|
||||||
"ScrollbarGrabHovered": (102, 217, 39),
|
|
||||||
"ScrollbarGrabActive": (166, 226, 46),
|
|
||||||
"CheckMark": (166, 226, 46),
|
|
||||||
"SliderGrab": (102, 217, 39),
|
|
||||||
"SliderGrabActive": (166, 226, 46),
|
|
||||||
"Button": ( 73, 72, 62),
|
|
||||||
"ButtonHovered": (249, 38, 114),
|
|
||||||
"ButtonActive": (198, 30, 92),
|
|
||||||
"Header": ( 73, 72, 62),
|
|
||||||
"HeaderHovered": (249, 38, 114),
|
|
||||||
"HeaderActive": (198, 30, 92),
|
|
||||||
"Separator": ( 60, 61, 52),
|
|
||||||
"SeparatorHovered": (249, 38, 114),
|
|
||||||
"SeparatorActive": (166, 226, 46),
|
|
||||||
"ResizeGrip": ( 73, 72, 62),
|
|
||||||
"ResizeGripHovered": (249, 38, 114),
|
|
||||||
"ResizeGripActive": (166, 226, 46),
|
|
||||||
"Tab": ( 73, 72, 62),
|
|
||||||
"TabHovered": (249, 38, 114),
|
|
||||||
"TabActive": (249, 38, 114),
|
|
||||||
"TabUnfocused": ( 50, 51, 44),
|
|
||||||
"TabUnfocusedActive": ( 90, 88, 76),
|
|
||||||
"DockingPreview": (249, 38, 114, 180),
|
|
||||||
"DockingEmptyBg": ( 20, 20, 18),
|
|
||||||
"Text": (248, 248, 242),
|
|
||||||
"TextDisabled": (117, 113, 94),
|
|
||||||
"TextSelectedBg": (249, 38, 114, 150),
|
|
||||||
"TableHeaderBg": ( 60, 61, 52),
|
|
||||||
"TableBorderStrong": ( 73, 72, 62),
|
|
||||||
"TableBorderLight": ( 55, 56, 48),
|
|
||||||
"TableRowBg": ( 0, 0, 0, 0),
|
|
||||||
"TableRowBgAlt": ( 50, 51, 44, 40),
|
|
||||||
"NavHighlight": (166, 226, 46),
|
|
||||||
"ModalWindowDimBg": ( 10, 10, 8, 100),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
PALETTE_NAMES: list[str] = list(_PALETTES.keys())
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------ colour key -> mvThemeCol_* mapping
|
|
||||||
|
|
||||||
# Maps our friendly name -> dpg constant name
|
|
||||||
_COL_MAP: dict[str, str] = {
|
|
||||||
"Text": "mvThemeCol_Text",
|
|
||||||
"TextDisabled": "mvThemeCol_TextDisabled",
|
|
||||||
"WindowBg": "mvThemeCol_WindowBg",
|
|
||||||
"ChildBg": "mvThemeCol_ChildBg",
|
|
||||||
"PopupBg": "mvThemeCol_PopupBg",
|
|
||||||
"Border": "mvThemeCol_Border",
|
|
||||||
"BorderShadow": "mvThemeCol_BorderShadow",
|
|
||||||
"FrameBg": "mvThemeCol_FrameBg",
|
|
||||||
"FrameBgHovered": "mvThemeCol_FrameBgHovered",
|
|
||||||
"FrameBgActive": "mvThemeCol_FrameBgActive",
|
|
||||||
"TitleBg": "mvThemeCol_TitleBg",
|
|
||||||
"TitleBgActive": "mvThemeCol_TitleBgActive",
|
|
||||||
"TitleBgCollapsed": "mvThemeCol_TitleBgCollapsed",
|
|
||||||
"MenuBarBg": "mvThemeCol_MenuBarBg",
|
|
||||||
"ScrollbarBg": "mvThemeCol_ScrollbarBg",
|
|
||||||
"ScrollbarGrab": "mvThemeCol_ScrollbarGrab",
|
|
||||||
"ScrollbarGrabHovered": "mvThemeCol_ScrollbarGrabHovered",
|
|
||||||
"ScrollbarGrabActive": "mvThemeCol_ScrollbarGrabActive",
|
|
||||||
"CheckMark": "mvThemeCol_CheckMark",
|
|
||||||
"SliderGrab": "mvThemeCol_SliderGrab",
|
|
||||||
"SliderGrabActive": "mvThemeCol_SliderGrabActive",
|
|
||||||
"Button": "mvThemeCol_Button",
|
|
||||||
"ButtonHovered": "mvThemeCol_ButtonHovered",
|
|
||||||
"ButtonActive": "mvThemeCol_ButtonActive",
|
|
||||||
"Header": "mvThemeCol_Header",
|
|
||||||
"HeaderHovered": "mvThemeCol_HeaderHovered",
|
|
||||||
"HeaderActive": "mvThemeCol_HeaderActive",
|
|
||||||
"Separator": "mvThemeCol_Separator",
|
|
||||||
"SeparatorHovered": "mvThemeCol_SeparatorHovered",
|
|
||||||
"SeparatorActive": "mvThemeCol_SeparatorActive",
|
|
||||||
"ResizeGrip": "mvThemeCol_ResizeGrip",
|
|
||||||
"ResizeGripHovered": "mvThemeCol_ResizeGripHovered",
|
|
||||||
"ResizeGripActive": "mvThemeCol_ResizeGripActive",
|
|
||||||
"Tab": "mvThemeCol_Tab",
|
|
||||||
"TabHovered": "mvThemeCol_TabHovered",
|
|
||||||
"TabActive": "mvThemeCol_TabActive",
|
|
||||||
"TabUnfocused": "mvThemeCol_TabUnfocused",
|
|
||||||
"TabUnfocusedActive": "mvThemeCol_TabUnfocusedActive",
|
|
||||||
"DockingPreview": "mvThemeCol_DockingPreview",
|
|
||||||
"DockingEmptyBg": "mvThemeCol_DockingEmptyBg",
|
|
||||||
"TextSelectedBg": "mvThemeCol_TextSelectedBg",
|
|
||||||
"TableHeaderBg": "mvThemeCol_TableHeaderBg",
|
|
||||||
"TableBorderStrong": "mvThemeCol_TableBorderStrong",
|
|
||||||
"TableBorderLight": "mvThemeCol_TableBorderLight",
|
|
||||||
"TableRowBg": "mvThemeCol_TableRowBg",
|
|
||||||
"TableRowBgAlt": "mvThemeCol_TableRowBgAlt",
|
|
||||||
"NavHighlight": "mvThemeCol_NavHighlight",
|
|
||||||
"NavWindowingHighlight": "mvThemeCol_NavWindowingHighlight",
|
|
||||||
"NavWindowingDimBg": "mvThemeCol_NavWindowingDimBg",
|
|
||||||
"ModalWindowDimBg": "mvThemeCol_ModalWindowDimBg",
|
|
||||||
}
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------ state
|
|
||||||
|
|
||||||
_current_theme_tag: str | None = None
|
|
||||||
_current_font_tag: str | None = None
|
|
||||||
_font_registry_tag: str | None = None
|
|
||||||
_current_palette: str = "DPG Default"
|
|
||||||
_current_font_path: str = ""
|
|
||||||
_current_font_size: float = 14.0
|
|
||||||
_current_scale: float = 1.0
|
|
||||||
_shader_config: dict[str, Any] = {
|
|
||||||
"crt": False,
|
|
||||||
"bloom": False,
|
|
||||||
"bg": "none",
|
|
||||||
"custom_window_frame": False,
|
|
||||||
}
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------ public API
|
|
||||||
|
|
||||||
def get_palette_names() -> list[str]:
|
|
||||||
return list(_PALETTES.keys())
|
|
||||||
|
|
||||||
def get_current_palette() -> str:
|
|
||||||
return _current_palette
|
|
||||||
|
|
||||||
def get_current_font_path() -> str:
|
|
||||||
return _current_font_path
|
|
||||||
|
|
||||||
def get_current_font_size() -> float:
|
|
||||||
return _current_font_size
|
|
||||||
|
|
||||||
def get_current_scale() -> float:
|
|
||||||
return _current_scale
|
|
||||||
|
|
||||||
def get_shader_config(key: str) -> Any:
|
|
||||||
"""
|
|
||||||
Get a specific shader configuration value.
|
|
||||||
[C: tests/test_shader_config.py:test_shader_config_parsing]
|
|
||||||
"""
|
|
||||||
return _shader_config.get(key)
|
|
||||||
|
|
||||||
def get_window_frame_config() -> bool:
|
|
||||||
"""
|
|
||||||
Get the window frame configuration.
|
|
||||||
[C: tests/test_shader_config.py:test_shader_config_parsing]
|
|
||||||
"""
|
|
||||||
return _shader_config.get("custom_window_frame", False)
|
|
||||||
|
|
||||||
def get_palette_colours(name: str) -> dict[str, Any]:
|
|
||||||
"""Return a copy of the colour dict for the named palette."""
|
|
||||||
return dict(_PALETTES.get(name, {}))
|
|
||||||
|
|
||||||
def apply(palette_name: str, overrides: dict[str, Any] | None = None) -> None:
|
|
||||||
"""
|
|
||||||
|
|
||||||
Build a global DPG theme from the named palette plus optional per-colour
|
|
||||||
overrides, and bind it as the default theme.
|
|
||||||
|
|
||||||
overrides: {colour_key: (R,G,B) or (R,G,B,A)} — merged on top of palette.
|
|
||||||
[C: src/theme_2.py:apply_current, src/theme_2.py:set_child_transparency, src/theme_2.py:set_transparency, tests/test_theme.py:test_theme_apply_sets_rounding_and_padding]
|
|
||||||
"""
|
|
||||||
global _current_theme_tag, _current_palette
|
|
||||||
_current_palette = palette_name
|
|
||||||
colours = dict(_PALETTES.get(palette_name, {}))
|
|
||||||
if overrides:
|
|
||||||
colours.update(overrides)
|
|
||||||
# Delete the old theme if one exists
|
|
||||||
if _current_theme_tag is not None:
|
|
||||||
try:
|
|
||||||
dpg.delete_item(_current_theme_tag)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
_current_theme_tag = None
|
|
||||||
if palette_name == "DPG Default" and not overrides:
|
|
||||||
# Bind an empty theme to reset to DPG defaults
|
|
||||||
with dpg.theme() as t:
|
|
||||||
with dpg.theme_component(dpg.mvAll):
|
|
||||||
pass
|
|
||||||
dpg.bind_theme(t)
|
|
||||||
_current_theme_tag = t
|
|
||||||
return
|
|
||||||
with dpg.theme() as t:
|
|
||||||
with dpg.theme_component(dpg.mvAll):
|
|
||||||
for name, colour in colours.items():
|
|
||||||
const_name = _COL_MAP.get(name)
|
|
||||||
if const_name is None:
|
|
||||||
continue
|
|
||||||
const = getattr(dpg, const_name, None)
|
|
||||||
if const is None:
|
|
||||||
continue
|
|
||||||
# Ensure 4-tuple
|
|
||||||
if len(colour) == 3:
|
|
||||||
colour = (*colour, 255)
|
|
||||||
dpg.add_theme_color(const, colour)
|
|
||||||
dpg.bind_theme(t)
|
|
||||||
_current_theme_tag = t
|
|
||||||
|
|
||||||
def apply_font(font_path: str, size: float = 14.0) -> None:
|
|
||||||
"""
|
|
||||||
|
|
||||||
Load the TTF at font_path at the given point size and bind it globally.
|
|
||||||
Safe to call multiple times. Uses a single persistent font_registry; only
|
|
||||||
the font *item* tag is tracked. Passing an empty path or a missing file
|
|
||||||
resets to the DPG built-in font.
|
|
||||||
"""
|
|
||||||
global _current_font_tag, _current_font_path, _current_font_size, _font_registry_tag
|
|
||||||
_current_font_path = font_path
|
|
||||||
_current_font_size = size
|
|
||||||
if not font_path or not Path(font_path).exists():
|
|
||||||
# Reset to default built-in font
|
|
||||||
dpg.bind_font(0)
|
|
||||||
_current_font_tag = None
|
|
||||||
return
|
|
||||||
# Create the registry once
|
|
||||||
if _font_registry_tag is None or not dpg.does_item_exist(_font_registry_tag):
|
|
||||||
with dpg.font_registry() as reg:
|
|
||||||
_font_registry_tag = reg
|
|
||||||
# Delete previous custom font item only (not the registry)
|
|
||||||
if _current_font_tag is not None:
|
|
||||||
try:
|
|
||||||
dpg.delete_item(_current_font_tag)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
_current_font_tag = None
|
|
||||||
font = dpg.add_font(font_path, size, parent=_font_registry_tag)
|
|
||||||
_current_font_tag = font
|
|
||||||
dpg.bind_font(font)
|
|
||||||
|
|
||||||
def set_scale(factor: float) -> None:
|
|
||||||
"""
|
|
||||||
Set the global Dear PyGui font/UI scale factor.
|
|
||||||
[C: src/theme_2.py:apply_current]
|
|
||||||
"""
|
|
||||||
global _current_scale
|
|
||||||
_current_scale = factor
|
|
||||||
dpg.set_global_font_scale(factor)
|
|
||||||
|
|
||||||
def save_to_config(config: dict[str, Any]) -> None:
|
|
||||||
"""Persist theme settings into the config dict under [theme]."""
|
|
||||||
config.setdefault("theme", {})
|
|
||||||
config["theme"]["palette"] = _current_palette
|
|
||||||
config["theme"]["font_path"] = _current_font_path
|
|
||||||
config["theme"]["font_size"] = _current_font_size
|
|
||||||
config["theme"]["scale"] = _current_scale
|
|
||||||
|
|
||||||
def load_from_config(config: dict[str, Any]) -> None:
|
|
||||||
"""
|
|
||||||
Read [theme] from config and apply everything.
|
|
||||||
[C: tests/test_shader_config.py:test_shader_config_parsing]
|
|
||||||
"""
|
|
||||||
t = config.get("theme", {})
|
|
||||||
palette = t.get("palette", "DPG Default")
|
|
||||||
font_path = t.get("font_path", "")
|
|
||||||
font_size = float(t.get("font_size", 14.0))
|
|
||||||
scale = float(t.get("scale", 1.0))
|
|
||||||
apply(palette)
|
|
||||||
if font_path:
|
|
||||||
apply_font(font_path, font_size)
|
|
||||||
set_scale(scale)
|
|
||||||
global _shader_config
|
|
||||||
_shader_config["crt"] = t.get("shader_crt", False)
|
|
||||||
_shader_config["bloom"] = t.get("shader_bloom", False)
|
|
||||||
_shader_config["bg"] = t.get("shader_bg", "none")
|
|
||||||
_shader_config["custom_window_frame"] = t.get("custom_window_frame", False)
|
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import tempfile
|
import tempfile
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from src.diff_viewer import (
|
from src.diff_viewer import (
|
||||||
parse_diff, DiffFile, DiffHunk, parse_hunk_header,
|
parse_diff, DiffFile, DiffHunk, parse_hunk_header,
|
||||||
get_line_color, render_diff_text_immediate,
|
get_line_color, apply_patch_to_file
|
||||||
create_backup, apply_patch_to_file, restore_from_backup, cleanup_backup
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_parse_diff_empty() -> None:
|
def test_parse_diff_empty() -> None:
|
||||||
@@ -95,35 +94,6 @@ def test_get_line_color() -> None:
|
|||||||
assert get_line_color("@@ -1,3 +1,4 @@") == "cyan"
|
assert get_line_color("@@ -1,3 +1,4 @@") == "cyan"
|
||||||
assert get_line_color(" context") == None
|
assert get_line_color(" context") == None
|
||||||
|
|
||||||
def test_render_diff_text_immediate() -> None:
|
|
||||||
diff_text = """--- a/test.py
|
|
||||||
+++ b/test.py
|
|
||||||
@@ -1 +1 @@
|
|
||||||
-old
|
|
||||||
+new"""
|
|
||||||
diff_files = parse_diff(diff_text)
|
|
||||||
output = render_diff_text_immediate(diff_files)
|
|
||||||
|
|
||||||
assert len(output) > 0
|
|
||||||
assert ("File: test.py", "white") in output
|
|
||||||
assert ("@@ -1 +1 @@", "cyan") in output
|
|
||||||
assert ("-old", "red") in output
|
|
||||||
assert ("+new", "green") in output
|
|
||||||
|
|
||||||
def test_create_backup() -> None:
|
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
|
||||||
test_file = Path(tmpdir) / "test.py"
|
|
||||||
test_file.write_text("original content\n")
|
|
||||||
|
|
||||||
backup_path = create_backup(str(test_file))
|
|
||||||
assert backup_path is not None
|
|
||||||
assert Path(backup_path).exists()
|
|
||||||
assert Path(backup_path).read_text() == "original content\n"
|
|
||||||
|
|
||||||
def test_create_backup_nonexistent() -> None:
|
|
||||||
result = create_backup("/nonexistent/file.py")
|
|
||||||
assert result is None
|
|
||||||
|
|
||||||
def test_apply_patch_simple() -> None:
|
def test_apply_patch_simple() -> None:
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
test_file = Path(tmpdir) / "test.py"
|
test_file = Path(tmpdir) / "test.py"
|
||||||
@@ -158,24 +128,3 @@ def test_apply_patch_with_context() -> None:
|
|||||||
content = test_file.read_text()
|
content = test_file.read_text()
|
||||||
assert "line one" in content
|
assert "line one" in content
|
||||||
assert "line two" in content
|
assert "line two" in content
|
||||||
|
|
||||||
def test_restore_from_backup() -> None:
|
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
|
||||||
test_file = Path(tmpdir) / "test.py"
|
|
||||||
test_file.write_text("modified\n")
|
|
||||||
backup_file = test_file.with_suffix(".py.backup")
|
|
||||||
backup_file.write_text("original\n")
|
|
||||||
|
|
||||||
success = restore_from_backup(str(test_file))
|
|
||||||
assert success
|
|
||||||
assert test_file.read_text() == "original\n"
|
|
||||||
|
|
||||||
def test_cleanup_backup() -> None:
|
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
|
||||||
test_file = Path(tmpdir) / "test.py"
|
|
||||||
test_file.write_text("content\n")
|
|
||||||
backup_file = test_file.with_suffix(".py.backup")
|
|
||||||
backup_file.write_text("backup\n")
|
|
||||||
|
|
||||||
cleanup_backup(str(test_file))
|
|
||||||
assert not backup_file.exists()
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import pytest
|
|
||||||
from unittest.mock import patch, MagicMock
|
|
||||||
|
|
||||||
def test_dynamic_background_rendering():
|
|
||||||
# Mock OpenGL before importing
|
|
||||||
with patch("src.shader_manager.gl") as mock_gl:
|
|
||||||
from src.shader_manager import ShaderManager
|
|
||||||
|
|
||||||
# Setup mock return values
|
|
||||||
mock_gl.glCreateProgram.return_value = 1
|
|
||||||
mock_gl.glCreateShader.return_value = 2
|
|
||||||
mock_gl.glGetShaderiv.return_value = 1 # GL_TRUE
|
|
||||||
mock_gl.glGetProgramiv.return_value = 1 # GL_TRUE
|
|
||||||
mock_gl.glGetUniformLocation.return_value = 10
|
|
||||||
|
|
||||||
manager = ShaderManager()
|
|
||||||
manager.setup_background_shader()
|
|
||||||
|
|
||||||
# Verify background program was created
|
|
||||||
assert manager.bg_program == 1
|
|
||||||
assert mock_gl.glCreateProgram.called
|
|
||||||
|
|
||||||
# Render background
|
|
||||||
manager.render_background(800, 600, 1.0)
|
|
||||||
|
|
||||||
# Verify OpenGL calls
|
|
||||||
mock_gl.glUseProgram.assert_any_call(1)
|
|
||||||
mock_gl.glDrawArrays.assert_called_with(mock_gl.GL_TRIANGLE_STRIP, 0, 4)
|
|
||||||
mock_gl.glUseProgram.assert_any_call(0)
|
|
||||||
|
|
||||||
# Verify uniforms were updated
|
|
||||||
mock_gl.glUniform1f.assert_called()
|
|
||||||
mock_gl.glUniform2f.assert_called()
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import pytest
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
||||||
from src import native_orchestrator
|
|
||||||
|
|
||||||
def test_read_plan_nonexistent():
|
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
|
||||||
result = native_orchestrator.read_plan("nonexistent_track", tmpdir)
|
|
||||||
assert result == ""
|
|
||||||
|
|
||||||
def test_write_and_read_plan():
|
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
|
||||||
content = "# Test Plan\n- [x] Task 1\n- [ ] Task 2"
|
|
||||||
native_orchestrator.write_plan("test_track", content, tmpdir)
|
|
||||||
result = native_orchestrator.read_plan("test_track", tmpdir)
|
|
||||||
assert result == content
|
|
||||||
|
|
||||||
def test_parse_plan_tasks():
|
|
||||||
content = """# Plan
|
|
||||||
- [x] Completed task
|
|
||||||
- [ ] Pending task
|
|
||||||
- [x] Another done"""
|
|
||||||
tasks = native_orchestrator.parse_plan_tasks(content)
|
|
||||||
assert len(tasks) == 3
|
|
||||||
assert tasks[0]["completed"] == True
|
|
||||||
assert tasks[1]["completed"] == False
|
|
||||||
assert tasks[2]["completed"] == True
|
|
||||||
|
|
||||||
def test_read_metadata_nonexistent():
|
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
|
||||||
result = native_orchestrator.read_metadata("nonexistent_track", tmpdir)
|
|
||||||
assert result == {}
|
|
||||||
|
|
||||||
def test_write_and_read_metadata():
|
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
|
||||||
data = {"name": "Test Track", "status": "active"}
|
|
||||||
native_orchestrator.write_metadata("test_track", data, tmpdir)
|
|
||||||
result = native_orchestrator.read_metadata("test_track", tmpdir)
|
|
||||||
assert result["name"] == "Test Track"
|
|
||||||
assert result["status"] == "active"
|
|
||||||
|
|
||||||
def test_get_track_dir():
|
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
|
||||||
track_dir = native_orchestrator.get_track_dir("my_track", tmpdir)
|
|
||||||
expected = Path(tmpdir) / "conductor" / "tracks" / "my_track"
|
|
||||||
assert track_dir == expected
|
|
||||||
|
|
||||||
def test_get_archive_dir():
|
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
|
||||||
archive_dir = native_orchestrator.get_archive_dir(tmpdir)
|
|
||||||
expected = Path(tmpdir) / "conductor" / "archive"
|
|
||||||
assert archive_dir == expected
|
|
||||||
|
|
||||||
def test_native_orchestrator_class():
|
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
|
||||||
orch = native_orchestrator.NativeOrchestrator(tmpdir)
|
|
||||||
assert orch.base_dir == Path(tmpdir)
|
|
||||||
|
|
||||||
data = {"name": "Test"}
|
|
||||||
orch.save_track("track1", data)
|
|
||||||
loaded = orch.load_track("track1")
|
|
||||||
assert loaded["name"] == "Test"
|
|
||||||
|
|
||||||
plan_content = "# Plan\n- [x] Done"
|
|
||||||
orch.save_plan("track1", plan_content)
|
|
||||||
loaded_plan = orch.load_plan("track1")
|
|
||||||
assert loaded_plan == plan_content
|
|
||||||
|
|
||||||
tasks = orch.get_track_tasks("track1")
|
|
||||||
assert len(tasks) == 1
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import unittest
|
|
||||||
from unittest.mock import MagicMock, patch
|
|
||||||
import sys
|
|
||||||
|
|
||||||
# Mock OpenGL.GL before importing ShaderManager
|
|
||||||
gl_mock = MagicMock()
|
|
||||||
# Setup some constants
|
|
||||||
gl_mock.GL_VERTEX_SHADER = 0x8B31
|
|
||||||
gl_mock.GL_FRAGMENT_SHADER = 0x8B30
|
|
||||||
gl_mock.GL_COMPILE_STATUS = 0x8B81
|
|
||||||
gl_mock.GL_LINK_STATUS = 0x8B82
|
|
||||||
gl_mock.GL_TEXTURE0 = 0x84C0
|
|
||||||
gl_mock.GL_TEXTURE_2D = 0x0DE1
|
|
||||||
gl_mock.GL_TRIANGLE_STRIP = 0x0005
|
|
||||||
|
|
||||||
opengl_mock = MagicMock()
|
|
||||||
sys.modules['OpenGL'] = opengl_mock
|
|
||||||
sys.modules['OpenGL.GL'] = gl_mock
|
|
||||||
opengl_mock.GL = gl_mock
|
|
||||||
|
|
||||||
from src.shader_manager import ShaderManager
|
|
||||||
|
|
||||||
class TestPostProcess(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
gl_mock.reset_mock()
|
|
||||||
# Mock return values for shader compilation
|
|
||||||
gl_mock.glCreateProgram.return_value = 1
|
|
||||||
gl_mock.glCreateShader.return_value = 2
|
|
||||||
gl_mock.glGetShaderiv.return_value = 1 # GL_TRUE
|
|
||||||
gl_mock.glGetProgramiv.return_value = 1 # GL_TRUE
|
|
||||||
gl_mock.glGetUniformLocation.return_value = 10
|
|
||||||
|
|
||||||
def test_setup_post_process_shader(self):
|
|
||||||
sm = ShaderManager()
|
|
||||||
sm.setup_post_process_shader()
|
|
||||||
self.assertEqual(sm.pp_program, 1)
|
|
||||||
gl_mock.glCreateProgram.assert_called()
|
|
||||||
gl_mock.glLinkProgram.assert_called_with(1)
|
|
||||||
|
|
||||||
def test_render_post_process(self):
|
|
||||||
sm = ShaderManager()
|
|
||||||
sm.pp_program = 1
|
|
||||||
sm.render_post_process(texture_id=5, width=800, height=600, time=1.0)
|
|
||||||
|
|
||||||
gl_mock.glUseProgram.assert_any_call(1)
|
|
||||||
gl_mock.glActiveTexture.assert_called_with(gl_mock.GL_TEXTURE0)
|
|
||||||
gl_mock.glBindTexture.assert_any_call(gl_mock.GL_TEXTURE_2D, 5)
|
|
||||||
gl_mock.glUniform1f.assert_called()
|
|
||||||
gl_mock.glDrawArrays.assert_called_with(gl_mock.GL_TRIANGLE_STRIP, 0, 4)
|
|
||||||
gl_mock.glUseProgram.assert_any_call(0)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import pytest
|
|
||||||
from unittest.mock import patch
|
|
||||||
from src import theme
|
|
||||||
|
|
||||||
def test_shader_config_parsing():
|
|
||||||
config = {
|
|
||||||
"theme": {
|
|
||||||
"shader_crt": True,
|
|
||||||
"shader_bloom": False,
|
|
||||||
"shader_bg": "noise",
|
|
||||||
"custom_window_frame": True
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
with patch("src.theme.apply"), \
|
|
||||||
patch("src.theme.apply_font"), \
|
|
||||||
patch("src.theme.set_scale"):
|
|
||||||
theme.load_from_config(config)
|
|
||||||
|
|
||||||
assert theme.get_shader_config("crt") is True
|
|
||||||
assert theme.get_shader_config("bloom") is False
|
|
||||||
assert theme.get_shader_config("bg") == "noise"
|
|
||||||
assert theme.get_window_frame_config() is True
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import pytest
|
|
||||||
from unittest.mock import patch, MagicMock
|
|
||||||
|
|
||||||
def test_shader_manager_initialization_and_compilation():
|
|
||||||
# Import inside test to allow patching OpenGL before import if needed
|
|
||||||
# In this case, we patch the OpenGL.GL functions used by ShaderManager
|
|
||||||
with patch("src.shader_manager.gl") as mock_gl:
|
|
||||||
mock_gl.glCreateProgram.return_value = 1
|
|
||||||
mock_gl.glCreateShader.return_value = 2
|
|
||||||
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
|
|
||||||
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
|
|
||||||
|
|
||||||
from src.shader_manager import ShaderManager
|
|
||||||
|
|
||||||
manager = ShaderManager()
|
|
||||||
|
|
||||||
# Basic vertex and fragment shader source
|
|
||||||
vert_src = "void main() {}"
|
|
||||||
frag_src = "void main() {}"
|
|
||||||
|
|
||||||
program_id = manager.compile_shader(vert_src, frag_src)
|
|
||||||
|
|
||||||
assert program_id == 1
|
|
||||||
assert mock_gl.glCreateProgram.called
|
|
||||||
assert mock_gl.glCreateShader.called
|
|
||||||
|
|
||||||
def test_shader_manager_uniform_update():
|
|
||||||
# Mock OpenGL.GL functions
|
|
||||||
with patch("src.shader_manager.gl") as mock_gl:
|
|
||||||
from src.shader_manager import ShaderManager
|
|
||||||
manager = ShaderManager()
|
|
||||||
# Set a mock program ID
|
|
||||||
manager.program = 1
|
|
||||||
|
|
||||||
# Mock glGetUniformLocation to return some valid locations
|
|
||||||
# u_time -> 10, u_resolution -> 20
|
|
||||||
def mock_get_loc(prog, name):
|
|
||||||
if name == "u_time": return 10
|
|
||||||
if name == "u_resolution": return 20
|
|
||||||
return -1
|
|
||||||
|
|
||||||
mock_gl.glGetUniformLocation.side_effect = mock_get_loc
|
|
||||||
|
|
||||||
# Call the method
|
|
||||||
manager.update_uniforms({"u_time": 1.5, "u_resolution": (800, 600)})
|
|
||||||
|
|
||||||
# Assert calls
|
|
||||||
mock_gl.glGetUniformLocation.assert_any_call(1, "u_time")
|
|
||||||
mock_gl.glGetUniformLocation.assert_any_call(1, "u_resolution")
|
|
||||||
mock_gl.glUniform1f.assert_called_once_with(10, 1.5)
|
|
||||||
mock_gl.glUniform2f.assert_called_once_with(20, 800, 600)
|
|
||||||
Reference in New Issue
Block a user