From 7ea52cbbe8a59b7e5baf29b1c913833efd645bab Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 5 Jun 2026 00:02:46 -0400 Subject: [PATCH] style(themes): compact TOML formatting and lift semantic colors --- config.toml | 31 ++++- manualslop_layout.ini | 56 ++++----- scripts/compact_themes.py | 35 ++++++ scripts/lift_colors.py | 108 ++++++++++++++++++ scripts/standardize_themes.py | 82 ++++++++++++++ src/gui_2.py | 208 +++++++++++++++++----------------- src/theme_2.py | 182 ++++++++++++++++------------- src/theme_models.py | 19 +++- themes/10x_dark.toml | 82 ++++++++------ themes/binks.toml | 88 ++++++++------ themes/gruvbox_dark.toml | 80 +++++++------ themes/monokai.toml | 110 ++++++++++-------- themes/moss.toml | 80 +++++++------ themes/nord_dark.toml | 110 ++++++++++-------- themes/solarized_dark.toml | 80 +++++++------ themes/solarized_light.toml | 80 +++++++------ 16 files changed, 915 insertions(+), 516 deletions(-) create mode 100644 scripts/compact_themes.py create mode 100644 scripts/lift_colors.py create mode 100644 scripts/standardize_themes.py diff --git a/config.toml b/config.toml index b5194190..9241dfc7 100644 --- a/config.toml +++ b/config.toml @@ -16,7 +16,7 @@ paths = [ "C:/projects/manual_slop/manual_slop.toml", "C:/projects/Pikuma/ps1-ai/pikuma_ps1.toml", ] -active = "C:/projects/Pikuma/ps1-ai/pikuma_ps1.toml" +active = "C:/projects/gencpp/.ai/gencpp_sloppy.toml" [gui] separate_message_panel = true @@ -62,13 +62,38 @@ Diagnostics = false "Undo/Redo History" = false [theme] -palette = "moss" +palette = "gruvbox_dark" font_path = "C:/projects/manual_slop/assets/fonts/MapleMono-Regular.ttf" font_size = 20.0 -scale = 1.0 +scale = 1.0199999809265137 transparency = 1.0 child_transparency = 1.0 +[theme.tone_mapping.moss] +brightness = 1.059999942779541 +contrast = 0.5799999833106995 +gamma = 1.059999942779541 + +[theme.tone_mapping.Binks] +brightness = 0.5600000023841858 +contrast = 0.7900000214576721 +gamma = 2.2100000381469727 + +[theme.tone_mapping.gray_variations] +brightness = 0.7699999809265137 +contrast = 0.7200000286102295 +gamma = 0.6899999976158142 + +[theme.tone_mapping.solarized_light] +brightness = 0.6899999976158142 +contrast = 0.8600000143051147 +gamma = 0.7699999809265137 + +[theme.tone_mapping."10x Dark"] +brightness = 0.75 +contrast = 1.0 +gamma = 1.0 + [mma] max_workers = 4 diff --git a/manualslop_layout.ini b/manualslop_layout.ini index c4bdcbc1..7925f145 100644 --- a/manualslop_layout.ini +++ b/manualslop_layout.ini @@ -44,20 +44,20 @@ Collapsed=0 DockId=0x00000010,0 [Window][Message] -Pos=1196,28 -Size=1362,1548 +Pos=1216,28 +Size=1603,1709 Collapsed=0 DockId=0x00000006,0 [Window][Response] Pos=0,28 -Size=1194,1548 +Size=1214,1709 Collapsed=0 DockId=0x00000010,5 [Window][Tool Calls] -Pos=1196,28 -Size=1362,1548 +Pos=1216,28 +Size=1603,1709 Collapsed=0 DockId=0x00000006,3 @@ -76,10 +76,10 @@ Collapsed=0 DockId=0xAFC85805,2 [Window][Theme] -Pos=0,28 -Size=1194,1548 +Pos=0,29 +Size=737,1195 Collapsed=0 -DockId=0x00000010,0 +DockId=0x00000010,1 [Window][Text Viewer - Entry #7] Pos=379,324 @@ -105,26 +105,26 @@ Collapsed=0 DockId=0x0000000D,0 [Window][Discussion Hub] -Pos=1196,28 -Size=1362,1548 +Pos=739,29 +Size=974,1195 Collapsed=0 -DockId=0x00000006,1 +DockId=0x00000006,0 [Window][Operations Hub] Pos=0,28 -Size=1194,1548 +Size=1214,1709 Collapsed=0 DockId=0x00000010,4 [Window][Files & Media] -Pos=0,28 -Size=1194,1548 +Pos=0,29 +Size=737,1195 Collapsed=0 DockId=0x00000010,2 [Window][AI Settings] -Pos=0,28 -Size=1194,1548 +Pos=0,29 +Size=737,1195 Collapsed=0 DockId=0x00000010,3 @@ -140,8 +140,8 @@ Collapsed=0 DockId=0x00000006,2 [Window][Log Management] -Pos=1196,28 -Size=1362,1548 +Pos=1216,28 +Size=1603,1709 Collapsed=0 DockId=0x00000006,2 @@ -409,10 +409,10 @@ Collapsed=0 DockId=0x00000006,1 [Window][Project Settings] -Pos=0,28 -Size=1194,1548 +Pos=0,29 +Size=737,1195 Collapsed=0 -DockId=0x00000010,1 +DockId=0x00000010,0 [Window][Undo/Redo History] Pos=678,28 @@ -682,10 +682,10 @@ Column 1 Width=80 Column 2 Width=150 [Table][0x7804123E,3] -RefScale=20 -Column 0 Width=99 +RefScale=21 +Column 0 Width=103 Column 1 Weight=1.0000 -Column 2 Width=627 +Column 2 Width=658 [Table][0x09B0112E,3] RefScale=20 @@ -829,13 +829,13 @@ Column 4 Weight=1.0000 DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02 -DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,28 Size=2558,1548 Split=X +DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,29 Size=1713,1195 Split=X DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=2357,1183 Split=X DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2 - DockNode ID=0x00000005 Parent=0x0000000B SizeRef=1194,1681 Split=Y Selected=0x3F1379AF - DockNode ID=0x00000010 Parent=0x00000005 SizeRef=983,1140 CentralNode=1 Selected=0x418C7449 + DockNode ID=0x00000005 Parent=0x0000000B SizeRef=737,1681 Split=Y Selected=0x3F1379AF + DockNode ID=0x00000010 Parent=0x00000005 SizeRef=983,1140 CentralNode=1 Selected=0x8CA2375C DockNode ID=0x00000011 Parent=0x00000005 SizeRef=983,184 Selected=0x432BAE4E - DockNode ID=0x00000006 Parent=0x0000000B SizeRef=1362,1681 Selected=0x2C0206CE + DockNode ID=0x00000006 Parent=0x0000000B SizeRef=974,1681 Selected=0x6F2B5B04 DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6 DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=488,1183 Selected=0x3AEC3498 diff --git a/scripts/compact_themes.py b/scripts/compact_themes.py new file mode 100644 index 00000000..13066d25 --- /dev/null +++ b/scripts/compact_themes.py @@ -0,0 +1,35 @@ +import os +import tomllib +from pathlib import Path + +def compact_toml(path: Path): + with open(path, "rb") as f: + data = tomllib.load(f) + + lines = [] + # Write top level fields + if "name" in data: lines.append(f'name = "{data["name"]}"') + if "syntax_palette" in data: lines.append(f'syntax_palette = "{data["syntax_palette"]}"') + if "description" in data: lines.append(f'description = "{data["description"]}"') + lines.append("") + + # Write colors table + if "colors" in data: + lines.append("[colors]") + colors = data["colors"] + # Find max key length for alignment + max_key = max(len(k) for k in colors.keys()) if colors else 0 + + # Sort keys to keep it predictable + for k in sorted(colors.keys()): + v = colors[k] + # Format as compact array [R, G, B] + val_str = f"[{v[0]:3}, {v[1]:3}, {v[2]:3}]" + lines.append(f"{k:<{max_key}} = {val_str}") + + path.write_text("\n".join(lines) + "\n", encoding="utf-8") + print(f"Compacted {path}") + +if __name__ == "__main__": + for f in Path("themes").glob("*.toml"): + compact_toml(f) diff --git a/scripts/lift_colors.py b/scripts/lift_colors.py new file mode 100644 index 00000000..c066a35c --- /dev/null +++ b/scripts/lift_colors.py @@ -0,0 +1,108 @@ +import re +from pathlib import Path + +# Mapping regex -> replacement +# Note: we use theme.get_color("semantic", alpha=...) + +REPLACEMENTS = [ + # status_error + (r'imgui\.ImVec4\(1\.0, 0\.0, 0\.0, 0\.2\)', 'theme.get_color("status_error", alpha=0.2)'), + (r'imgui\.ImVec4\(1, 0, 0, 1\)', 'theme.get_color("status_error")'), + (r'imgui\.ImVec4\(1, 0\.3, 0\.3, ([\d\.]+)\)', r'theme.get_color("status_error", alpha=\1)'), + (r'imgui\.ImVec4\(1, 0\.3, 0\.3, 1\)', 'theme.get_color("status_error")'), + (r'imgui\.ImVec4\(1, 0\.4, 0\.4, 1\)', 'theme.get_color("status_error")'), + (r'vec4\(255, 100, 100\)', 'theme.get_color("status_error")'), + (r'vec4\(255, 72, 64, ([\d\.]+)\)', r'theme.get_color("status_error", alpha=\1)'), + (r'vec4\(255, 72, 64\)', 'theme.get_color("status_error")'), + (r'vec4\(255, 77, 77\)', 'theme.get_color("status_error")'), + (r'vec4\(230, 51, 51\)', 'theme.get_color("status_error")'), + (r'vec4\(240, 80, 80\)', 'theme.get_color("status_error")'), + (r'vec4\(255, 120, 120\)', 'theme.get_color("status_error")'), + (r'vec4\(255, 50, 50, ([\d\.]+)\)', r'theme.get_color("status_error", alpha=\1)'), + (r'vec4\(255, 100, 100, ([\d\.]+)\)', r'theme.get_color("status_error", alpha=\1)'), + + # status_warning + (r'imgui\.ImVec4\(1\.0, 0\.85, 0\.2, 0\.15\)', 'theme.get_color("status_warning", alpha=0.15)'), + (r'imgui\.ImVec4\(1\.0, 0\.85, 0\.2, 1\.0\)', 'theme.get_color("status_warning")'), + (r'imgui\.ImVec4\(1, 0\.5, 0, 1\)', 'theme.get_color("status_warning")'), + (r'imgui\.ImVec4\(1, 1, 0, 1\)', 'theme.get_color("status_warning")'), + (r'imgui\.ImVec4\(1\.0, 1\.0, 0\.0, 1\.0\)', 'theme.get_color("status_warning")'), + (r'imgui\.ImVec4\(1\.0, 0\.5, 0\.0, 1\.0\)', 'theme.get_color("status_warning")'), + (r'imgui\.ImVec4\(1\.0, 0\.3, 0\.0, 1\.0\)', 'theme.get_color("status_warning")'), + (r'vec4\(255, 152, 48\)', 'theme.get_color("status_warning")'), + (r'vec4\(255, 200, 100\)', 'theme.get_color("status_warning")'), + (r'vec4\(200, 200, 100\)', 'theme.get_color("status_warning")'), + (r'vec4\(255, 230, 77\)', 'theme.get_color("status_warning")'), + (r'vec4\(255, 255, 100\)', 'theme.get_color("status_warning")'), + (r'vec4\(240, 200, 80\)', 'theme.get_color("status_warning")'), + (r'vec4\(255, 150, 50\)', 'theme.get_color("status_warning")'), + (r'vec4\(200, 180, 100\)', 'theme.get_color("status_warning")'), + + # status_success + (r'vec4\(80, 255, 80\)', 'theme.get_color("status_success")'), + (r'vec4\(80, 255, 80, ([\d\.]+)\)', r'theme.get_color("status_success", alpha=\1)'), + (r'imgui\.ImVec4\(0, 1, 0, 1\)', 'theme.get_color("status_success")'), + (r'imgui\.ImVec4\(0\.2, 0\.9, 0\.2, 1\)', 'theme.get_color("status_success")'), + (r'imgui\.ImVec4\(0\.2, 0\.8, 0\.2, 1\.0\)', 'theme.get_color("status_success")'), + (r'imgui\.ImVec4\(0\.39, 1\.0, 0\.39, ([\d\.]+)\)', r'theme.get_color("status_success", alpha=\1)'), + (r'imgui\.ImVec4\(0\.3, 0\.8, 0\.3, 1\)', 'theme.get_color("status_success")'), + (r'vec4\(100, 255, 100\)', 'theme.get_color("status_success")'), + (r'vec4\(120, 220, 120\)', 'theme.get_color("status_success")'), + (r'vec4\(0, 255, 0, ([\d\.]+)\)', r'theme.get_color("status_success", alpha=\1)'), + (r'vec4\(51, 230, 51\)', 'theme.get_color("status_success")'), + + # status_info + (r'imgui\.ImVec4\(0, 1, 1, 1\)', 'theme.get_color("status_info")'), + (r'imgui\.ImVec4\(0\.3, 0\.8, 1, 1\)', 'theme.get_color("status_info")'), + (r'imgui\.ImVec4\(0\.4, 0\.6, 0\.7, 1\.0\)', 'theme.get_color("status_info")'), + (r'vec4\(77, 178, 255\)', 'theme.get_color("status_info")'), + (r'vec4\(100, 150, 180\)', 'theme.get_color("status_info")'), + + # text_disabled + (r'imgui\.ImVec4\(0\.7, 0\.7, 0\.7, 1\)', 'theme.get_color("text_disabled")'), + (r'vec4\(180, 180, 180\)', 'theme.get_color("text_disabled")'), + (r'vec4\(160, 160, 160\)', 'theme.get_color("text_disabled")'), + (r'vec4\(200, 220, 160\)', 'theme.get_color("text_disabled")'), + + # bubble backgrounds + (r'imgui\.ImVec4\(0\.15, 0\.14, 0\.10, 0\.7\)', 'theme.get_color("bubble_vendor", alpha=0.7)'), + (r'vec4\(50, 40, 20\)', 'theme.get_color("bubble_vendor")'), + + # Slices + (r'vec4\(255, 165, 0, 0\.2\)', 'theme.get_color("slice_manual", alpha=0.2)'), + (r'vec4\(0, 255, 0, 0\.15\)', 'theme.get_color("slice_auto", alpha=0.15)'), + (r'vec4\(100, 100, 255, 0\.3\)', 'theme.get_color("slice_selection", alpha=0.3)'), + (r'imgui\.ImVec4\(1\.0, 0\.65, 0, 0\.2\)', 'theme.get_color("slice_manual", alpha=0.2)'), + (r'imgui\.ImVec4\(0, 1\.0, 0, 0\.1\)', 'theme.get_color("slice_auto", alpha=0.1)'), + (r'imgui\.ImVec4\(0\.4, 0\.4, 1\.0, 0\.3\)', 'theme.get_color("slice_selection", alpha=0.3)'), + (r'imgui\.ImVec4\(0, 1\.0, 0, 0\.15\)', 'theme.get_color("slice_auto", alpha=0.15)'), + (r'imgui\.ImVec4\(0, 0, 1\.0, 0\.15\)', 'theme.get_color("slice_selection", alpha=0.15)'), + (r'imgui\.ImVec4\(1\.0, 1\.0, 0, 0\.2\)', 'theme.get_color("status_warning", alpha=0.2)'), +] + +def apply_lift(path: Path): + content = path.read_text(encoding="utf-8") + original = content + + for pattern, replacement in REPLACEMENTS: + content = re.sub(pattern, replacement, content) + + # Specialized replacements + # MMA status block + content = re.sub(r'if app\.mma_status == "idle": status_col = imgui\.ImVec4\(0\.7, 0\.7, 0\.7, 1\)', + 'if app.mma_status == "idle": status_col = theme.get_color("text_disabled")', content) + content = re.sub(r'elif app\.mma_status == "running": status_col = vec4\(80, 255, 80\) if is_nerv else imgui\.ImVec4\(1, 1, 0, 1\)', + 'elif app.mma_status == "running": status_col = theme.get_color("status_warning")', content) + content = re.sub(r'elif app\.mma_status == "done": status_col = imgui\.ImVec4\(0, 1, 0, 1\)', + 'elif app.mma_status == "done": status_col = theme.get_color("status_success")', content) + content = re.sub(r'elif app\.mma_status == "error": status_col = vec4\(255, 72, 64\) if is_nerv else imgui\.ImVec4\(1, 0, 0, 1\)', + 'elif app.mma_status == "error": status_col = theme.get_color("status_error")', content) + content = re.sub(r'elif app\.mma_status == "paused": status_col = imgui\.ImVec4\(1, 0\.5, 0, 1\)', + 'elif app.mma_status == "paused": status_col = theme.get_color("status_warning")', content) + + if content != original: + path.write_text(content, encoding="utf-8") + print(f"Lifted colors in {path}") + +if __name__ == "__main__": + apply_lift(Path("src/gui_2.py")) diff --git a/scripts/standardize_themes.py b/scripts/standardize_themes.py new file mode 100644 index 00000000..d5859591 --- /dev/null +++ b/scripts/standardize_themes.py @@ -0,0 +1,82 @@ +import os +import tomllib +import tomli_w +from pathlib import Path + +# Get all keys from ThemePalette defaults +from src.theme_models import ThemePalette + +default_palette = ThemePalette() +ALL_KEYS = list(default_palette.to_dict().keys()) + +DARK_SEMANTIC = { + "status_success": [80, 255, 80], + "status_warning": [255, 152, 48], + "status_error": [255, 72, 64], + "status_info": [0, 255, 255], + "bubble_user": [30, 45, 75], + "bubble_ai": [35, 65, 45], + "bubble_vendor": [65, 55, 30], + "bubble_system": [25, 25, 25], + "slice_manual": [255, 165, 0], + "slice_auto": [0, 255, 0], + "slice_selection": [100, 100, 255], + "diff_added": [51, 230, 51], + "diff_removed": [230, 51, 51], + "diff_header": [77, 178, 255], +} + +LIGHT_SEMANTIC = { + "status_success": [40, 180, 40], + "status_warning": [200, 140, 0], + "status_error": [200, 40, 40], + "status_info": [40, 100, 200], + "bubble_user": [220, 230, 255], + "bubble_ai": [220, 255, 220], + "bubble_vendor": [255, 240, 200], + "bubble_system": [240, 240, 240], + "slice_manual": [255, 200, 0], + "slice_auto": [80, 255, 80], + "slice_selection": [180, 180, 255], + "diff_added": [40, 180, 40], + "diff_removed": [200, 40, 40], + "diff_header": [40, 100, 200], +} + +def standardize_theme(path: Path): + with open(path, "rb") as f: + data = tomllib.load(f) + + name = data.get("name", path.stem) + syntax = data.get("syntax_palette", "dark") + colors = data.get("colors", {}) + + is_light = "light" in name.lower() or syntax == "light" + semantic = LIGHT_SEMANTIC if is_light else DARK_SEMANTIC + + # Merge semantic + for k, v in semantic.items(): + if k not in colors: + colors[k] = v + + # Ensure all keys are present (optional, but good for completeness) + # for k in ALL_KEYS: + # if k not in colors: + # # We use defaults from ThemePalette anyway, so no need to bloat + # pass + + new_data = { + "name": name, + "syntax_palette": syntax, + "description": data.get("description", ""), + "colors": colors + } + + with open(path, "wb") as f: + tomli_w.dump(new_data, f) + print(f"Standardized {path}") + +if __name__ == "__main__": + themes_dir = Path("themes") + for f in themes_dir.glob("*.toml"): + standardize_theme(f) diff --git a/src/gui_2.py b/src/gui_2.py index 305376f4..958dcb40 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -80,22 +80,23 @@ def hide_tk_root() -> Tk: def vec4(r: float, g: float, b: float, a: float = 1.0) -> imgui.ImVec4: return imgui.ImVec4(r/255.0, g/255.0, b/255.0, a) -C_OUT: imgui.ImVec4 = vec4(100, 200, 255) -C_IN: imgui.ImVec4 = vec4(140, 255, 160) -C_REQ: imgui.ImVec4 = vec4(255, 220, 100) -C_RES: imgui.ImVec4 = vec4(180, 255, 180) -C_TC: imgui.ImVec4 = vec4(255, 180, 80) -C_TR: imgui.ImVec4 = vec4(180, 220, 255) -C_TRS: imgui.ImVec4 = vec4(200, 180, 255) -C_LBL: imgui.ImVec4 = vec4(180, 180, 180) -C_VAL: imgui.ImVec4 = vec4(220, 220, 220) -C_KEY: imgui.ImVec4 = vec4(140, 200, 255) -C_NUM: imgui.ImVec4 = vec4(180, 255, 180) -C_TRM: imgui.ImVec4 = vec4(160, 160, 150) # Trimmed/Cruft -C_SUB: imgui.ImVec4 = vec4(220, 200, 120) +# Standard Color Constants (now bound to the theming system) +def C_OUT() -> imgui.ImVec4: return theme.get_color("status_info") +def C_IN() -> imgui.ImVec4: return theme.get_color("status_success") +def C_REQ() -> imgui.ImVec4: return theme.get_color("status_warning") +def C_RES() -> imgui.ImVec4: return theme.get_color("status_success") +def C_TC() -> imgui.ImVec4: return theme.get_color("status_warning") +def C_TR() -> imgui.ImVec4: return theme.get_color("status_info") +def C_TRS() -> imgui.ImVec4: return theme.get_color("status_info") +def C_LBL() -> imgui.ImVec4: return theme.get_color("text_disabled") +def C_VAL() -> imgui.ImVec4: return theme.get_color("text") +def C_KEY() -> imgui.ImVec4: return theme.get_color("status_info") +def C_NUM() -> imgui.ImVec4: return theme.get_color("status_success") +def C_TRM() -> imgui.ImVec4: return theme.get_color("text_disabled") +def C_SUB() -> imgui.ImVec4: return theme.get_color("text_disabled") -DIR_COLORS: dict[str, imgui.ImVec4] = {"OUT": C_OUT, "IN": C_IN} -KIND_COLORS: dict[str, imgui.ImVec4] = {"request": C_REQ, "response": C_RES, "tool_call": C_TC, "tool_result": C_TR, "tool_result_send": C_TRS} +DIR_COLORS: dict[str, typing.Callable[[], imgui.ImVec4]] = {"OUT": C_OUT, "IN": C_IN} +KIND_COLORS: dict[str, typing.Callable[[], imgui.ImVec4]] = {"request": C_REQ, "response": C_RES, "tool_call": C_TC, "tool_result": C_TR, "tool_result_send": C_TRS} HEAVY_KEYS: set[str] = {"message", "text", "script", "output", "content"} def render_text_viewer(app: App, label: str, content: str, text_type: str = 'text', force_open: bool = False, id_suffix: str = "") -> None: @@ -783,7 +784,7 @@ class App: if self.perf_profiling_enabled: self.perf_monitor.start_component("_gui_func") try: if self.is_viewing_prior_session: - with imscope.style_color(imgui.Col_.window_bg, vec4(50, 40, 20)): + with imscope.style_color(imgui.Col_.window_bg, theme.get_color("bubble_vendor")): render_main_interface(self) else: render_main_interface(self) @@ -1378,9 +1379,9 @@ def render_usage_analytics_panel(app: App) -> None: if app.controller.rag_config and app.controller.rag_config.enabled: # imgui.same_line() status = app.controller.rag_status - if status == "indexing...": color = vec4(100, 255, 100) - elif status == "error": color = vec4(255, 100, 100) - else: color = vec4(180, 180, 180) + if status == "indexing...": color = theme.get_color("status_success") + elif status == "error": color = theme.get_color("status_error") + else: color = theme.get_color("text_disabled") imgui.text_colored(color, f"[RAG: {status}]") if imgui.is_item_hovered(): imgui.set_tooltip(f"RAG is enabled. Status: {status}. Click to rebuild index.") @@ -1448,7 +1449,7 @@ def render_diagnostics_panel(app: App) -> None: imgui.text(comp_name) imgui.table_next_column() if avg_val > 10.0: - imgui.text_colored(imgui.ImVec4(1.0, 0.2, 0.2, 1.0), f"{avg_val:.2f}") + imgui.text_colored(theme.get_color("status_error"), f"{avg_val:.2f}") else: imgui.text(f"{avg_val:.2f}") imgui.table_next_column() @@ -1512,7 +1513,7 @@ def render_cache_panel(app: App) -> None: ttl_pct = (ttl_remaining / ttl_total * 100) if ttl_total > 0 else 0 imgui.text(f"Age: {age_str}") imgui.text(f"TTL: {remaining_str} ({ttl_pct:.0f}%)") - color = imgui.ImVec4(0.2, 0.8, 0.2, 1.0) + color = theme.get_color("status_success") if ttl_pct < 20: color = imgui.ImVec4(1.0, 0.2, 0.2, 1.0) elif ttl_pct < 50: color = imgui.ImVec4(1.0, 0.8, 0.0, 1.0) imgui.push_style_color(imgui.Col_.plot_histogram, color) @@ -1558,7 +1559,7 @@ def render_tool_analytics_panel(app: App) -> None: imgui.table_set_column_index(2) imgui.text(f"{avg_time:.0f}") imgui.table_set_column_index(3) - if fail_pct > 0: imgui.text_colored(imgui.ImVec4(1.0, 0.2, 0.2, 1.0), f"{fail_pct:.0f}%") + if fail_pct > 0: imgui.text_colored(theme.get_color("status_error"), f"{fail_pct:.0f}%") else: imgui.text("0%") imgui.end_table() if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_tool_analytics_panel") @@ -1588,9 +1589,9 @@ def render_token_budget_panel(app: App) -> None: current = stats.get("estimated_prompt_tokens", stats.get("total_tokens", 0)) limit = stats.get("max_prompt_tokens", 0) headroom = stats.get("headroom_tokens", max(0, limit - current)) - if pct < 50.0: color = imgui.ImVec4(0.2, 0.8, 0.2, 1.0) - elif pct < 80.0: color = imgui.ImVec4(1.0, 0.8, 0.0, 1.0) - else: color = imgui.ImVec4(1.0, 0.2, 0.2, 1.0) + if pct < 50.0: color = theme.get_color("status_success") + elif pct < 80.0: color = theme.get_color("status_warning") + else: color = theme.get_color("status_error") imgui.push_style_color(imgui.Col_.plot_histogram, color) imgui.progress_bar(pct / 100.0, imgui.ImVec2(-1, 0), f"{pct:.1f}%") imgui.pop_style_color() @@ -1629,14 +1630,14 @@ def render_token_budget_panel(app: App) -> None: imgui.table_set_column_index(0); render_selectable_label(app, f"tier_{tier}", tier, width=-1) imgui.table_set_column_index(1); render_selectable_label(app, f"model_{tier}", model.split("-")[0], width=-1) imgui.table_set_column_index(2); render_selectable_label(app, f"tokens_{tier}", f"{tokens:,}", width=-1) - imgui.table_set_column_index(3); render_selectable_label(app, f"cost_{tier}", f"${cost:.4f}", width=-1, color=imgui.ImVec4(0.2, 0.9, 0.2, 1)) + imgui.table_set_column_index(3); render_selectable_label(app, f"cost_{tier}", f"${cost:.4f}", width=-1, color=theme.get_color("status_success")) imgui.end_table() tier_total = sum(cost_tracker.estimate_cost(stats.get('model', ''), stats.get('input', 0), stats.get('output', 0)) for stats in app.mma_tier_usage.values()) - render_selectable_label(app, "session_total_cost", f"Session Total: ${tier_total:.4f}", width=-1, color=imgui.ImVec4(0, 1, 0, 1)) + render_selectable_label(app, "session_total_cost", f"Session Total: ${tier_total:.4f}", width=-1, color=theme.get_color("status_success")) else: imgui.text_disabled("No MMA tier usage data") if stats.get("would_trim"): - imgui.text_colored(imgui.ImVec4(1.0, 0.3, 0.0, 1.0), "WARNING: Next call will trim history") + imgui.text_colored(theme.get_color("status_warning"), "WARNING: Next call will trim history") trimmable = stats.get("trimmable_turns", 0) if trimmable: imgui.text_disabled(f"Trimmable turns: {trimmable}") msgs = stats.get("messages") @@ -1704,7 +1705,7 @@ def render_log_management(app: App) -> None: imgui.table_next_column() whitelisted = s_data.get("whitelisted", False) if whitelisted: - imgui.text_colored(vec4(255, 215, 0), "YES") + imgui.text_colored(theme.get_color("status_warning"), "YES") else: imgui.text("NO") metadata = s_data.get("metadata") or {} @@ -2015,10 +2016,10 @@ def render_agent_tools_panel(app: App) -> None: if app.ui_tool_filter_category != "All" and app.ui_tool_filter_category != cat_name: continue if imgui.tree_node(cat_name): for tool in tools: - if tool.weight >= 5: imgui.text_colored(vec4(255, 100, 100), "[HIGH]"); imgui.same_line() - elif tool.weight == 4: imgui.text_colored(vec4(255, 255, 100), "[PREF]"); imgui.same_line() - elif tool.weight == 2: imgui.text_colored(vec4(255, 150, 50), "[REJECT]"); imgui.same_line() - elif tool.weight <= 1: imgui.text_colored(vec4(180, 180, 180), "[LOW]"); imgui.same_line() + if tool.weight >= 5: imgui.text_colored(theme.get_color("status_error"), "[HIGH]"); imgui.same_line() + elif tool.weight == 4: imgui.text_colored(theme.get_color("status_warning"), "[PREF]"); imgui.same_line() + elif tool.weight == 2: imgui.text_colored(theme.get_color("status_warning"), "[REJECT]"); imgui.same_line() + elif tool.weight <= 1: imgui.text_colored(theme.get_color("text_disabled"), "[LOW]"); imgui.same_line() imgui.text(tool.name); imgui.same_line(180) @@ -2197,9 +2198,9 @@ def render_base_prompt_diff_modal(app: App) -> None: else: imgui.begin_child("base_prompt_diff_scroll", imgui.ImVec2(800, 500), True) for line in diff: - if line.startswith("+++") or line.startswith("---") or line.startswith("@@"): imgui.text_colored(vec4(77, 178, 255), line.rstrip()) - elif line.startswith("+"): imgui.text_colored(vec4(51, 230, 51), line.rstrip()) - elif line.startswith("-"): imgui.text_colored(vec4(230, 51, 51), line.rstrip()) + if line.startswith("+++") or line.startswith("---") or line.startswith("@@"): imgui.text_colored(theme.get_color("diff_header"), line.rstrip()) + elif line.startswith("+"): imgui.text_colored(theme.get_color("diff_added"), line.rstrip()) + elif line.startswith("-"): imgui.text_colored(theme.get_color("diff_removed"), line.rstrip()) else: imgui.text(line.rstrip()) imgui.end_child() @@ -2740,9 +2741,9 @@ def render_files_and_media(app: App) -> None: imgui.table_set_column_index(2) if in_context: - imgui.text_colored(imgui.ImVec4(0.3, 0.8, 0.3, 1), "Active") + imgui.text_colored(theme.get_color("status_success"), "Active") elif is_cached: - imgui.text_colored(imgui.ImVec4(0.3, 0.8, 1, 1), "Cached") + imgui.text_colored(theme.get_color("status_info"), "Cached") else: imgui.text_disabled(" - ") imgui.end_table() @@ -3048,23 +3049,23 @@ def render_ast_inspector_modal(app: App) -> None: mode = 'hide' if deepest_node: mode = getattr(f_item, 'ast_mask', {}).get(deepest_node['full_path'], 'hide') if mode == 'def': - draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(imgui.ImVec4(0, 1.0, 0, 0.15))) + draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_auto", alpha=0.15))) elif mode == 'sig': - draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(imgui.ImVec4(0, 0, 1.0, 0.15))) + draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_selection", alpha=0.15))) elif deepest_node and deepest_node['full_path'] == getattr(app, '_hovered_ast_node', None): - draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(imgui.ImVec4(1.0, 1.0, 0, 0.2))) + draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("status_warning", alpha=0.2))) # 2. Slice Highlight if hasattr(f_item, 'custom_slices'): is_auto = any(slc['start_line'] <= line_num <= slc['end_line'] for slc in f_item.custom_slices if slc.get('tag') == 'auto-ast') is_man = any(slc['start_line'] <= line_num <= slc['end_line'] for slc in f_item.custom_slices if slc.get('tag') != 'auto-ast') - if is_man: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(imgui.ImVec4(1.0, 0.65, 0, 0.2))) - elif is_auto and mode == 'hide': draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(imgui.ImVec4(0, 1.0, 0, 0.1))) + if is_man: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_manual", alpha=0.2))) + elif is_auto and mode == 'hide': draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_auto", alpha=0.1))) # 3. Active Selection Highlight if getattr(app, '_slice_sel_start', -1) != -1 and getattr(app, '_slice_sel_end', -1) != -1: s, e = min(app._slice_sel_start, app._slice_sel_end), max(app._slice_sel_start, app._slice_sel_end) - if s <= line_num <= e: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(imgui.ImVec4(0.4, 0.4, 1.0, 0.3))) + if s <= line_num <= e: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + avail_width, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_selection", alpha=0.3))) imgui.selectable(f"{line_num:4} | {line_text}##ln{line_num}", False) if imgui.is_item_clicked(): app._slice_sel_start = line_num; app._slice_sel_end = line_num @@ -3234,7 +3235,7 @@ def render_context_files_table(app: App) -> None: imgui.end_popup() if hasattr(f_item, "custom_slices") and f_item.custom_slices: imgui.same_line() - imgui.text_colored(imgui.ImVec4(1.0, 0.5, 0.0, 1.0), "[Slices Active]") + imgui.text_colored(theme.get_color("status_warning"), "[Slices Active]") def render_context_presets(app: App) -> None: presets = app.controller.project.get('context_presets', {}) @@ -3389,7 +3390,7 @@ def render_thinking_trace(app: App, entry: dict, segments: list[dict], entry_ind if not segments: return # Tint thinking trace background slightly differently - with imscope.style_color(imgui.Col_.child_bg, imgui.ImVec4(0.15, 0.14, 0.10, 0.7)), \ + with imscope.style_color(imgui.Col_.child_bg, theme.get_color("bubble_vendor", alpha=0.7)), \ theme.ai_text_style(): with imscope.indent(): show_content = True @@ -3458,7 +3459,7 @@ def render_discussion_entry(app: App, entry: dict, index: int) -> None: if usage: inp, out, cache = usage.get("input_tokens", 0), usage.get("output_tokens", 0), usage.get("cache_read_input_tokens", 0) u_str = f" in:{inp} out:{out}" + (f" cache:{cache}" if cache else "") - imgui.same_line(); imgui.text_colored(imgui.ImVec4(0.4, 0.6, 0.7, 1.0), u_str) + imgui.same_line(); imgui.text_colored(theme.get_color("status_info"), u_str) if collapsed: imgui.same_line() @@ -3584,10 +3585,10 @@ def render_session_insights_panel(app: App) -> None: completed = insights.get('completed_tickets', 0) efficiency = insights.get('efficiency', 0) def render_prior_session_view(app: App) -> None: - with imscope.style_color(imgui.Col_.child_bg, vec4(50, 40, 20)): + with imscope.style_color(imgui.Col_.child_bg, theme.get_color("bubble_vendor")): if imgui.button("Exit Prior Session"): app.controller.cb_exit_prior_session(); app._comms_log_dirty = True imgui.same_line() - imgui.text_colored(vec4(200, 180, 100), f"({len(app.prior_disc_entries)} entries)") + imgui.text_colored(theme.get_color("status_warning"), f"({len(app.prior_disc_entries)} entries)") imgui.separator() avail = imgui.get_content_region_avail() with imscope.child("prior_scroll", imgui.ImVec2(avail.x, avail.y), imgui.WindowFlags_.horizontal_scrollbar): @@ -3597,12 +3598,12 @@ def render_prior_session_view(app: App) -> None: if imgui.button("+" if collapsed else "-"): entry["collapsed"] = not collapsed imgui.same_line(); role, ts = entry.get("role", "??"), entry.get("ts", "") imgui.text_colored(C_LBL, f"[{role}]") - if ts: imgui.same_line(); imgui.text_colored(vec4(160, 160, 160), str(ts)) + if ts: imgui.same_line(); imgui.text_colored(theme.get_color("text_disabled"), str(ts)) content = entry.get("content", "") if collapsed: imgui.same_line(); preview = content.replace("\n", " ")[:80] if len(content) > 80: preview += "..." - imgui.text_colored(vec4(180, 180, 180), preview) + imgui.text_colored(theme.get_color("text_disabled"), preview) else: with theme.ai_text_style(): markdown_helper.render(content, context_id=f'prior_disc_{idx}') @@ -3649,8 +3650,8 @@ def render_synthesis_panel(app: App) -> None: def render_comms_history_panel(app: App) -> None: if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_comms_history_panel") - st_col = vec4(200, 220, 160) - if theme.is_nerv_active(): st_col = vec4(80, 255, 80) # DATA_GREEN for status in NERV + st_col = theme.get_color("text_disabled") + if theme.is_nerv_active(): st_col = theme.get_color("status_success") # DATA_GREEN for status in NERV imgui.text_colored(st_col, f"Status: {app.ai_status}") imgui.same_line() if imgui.button("Clear##comms"): @@ -3693,7 +3694,7 @@ def render_comms_history_panel(app: App) -> None: # Row 1: #Idx TS DIR KIND Provider/Model [Tier] imgui.text_colored(C_LBL, f"#{i_display}"); imgui.same_line() - imgui.text_colored(vec4(160, 160, 160), ts) + imgui.text_colored(theme.get_color("text_disabled"), ts) latency = entry.get("latency") or entry.get("metadata", {}).get("latency") if latency: @@ -3703,7 +3704,7 @@ def render_comms_history_panel(app: App) -> None: ticket_id = entry.get("mma_ticket_id") if ticket_id: imgui.same_line() - imgui.text_colored(vec4(255, 120, 120), f"[{ticket_id}]") + imgui.text_colored(theme.get_color("status_error"), f"[{ticket_id}]") imgui.same_line() d_col = DIR_COLORS.get(direction, C_VAL) imgui.text_colored(d_col, direction); imgui.same_line() @@ -3857,7 +3858,7 @@ def render_discussion_metadata(app: App) -> None: total_out += entry["usage"].get("output_tokens", 0) total_cache += entry["usage"].get("cache_read_input_tokens", 0) if total_in > 0 or total_out > 0: - imgui.text_colored(vec4(100, 150, 180), f"Discussion Tokens: {total_in} In | {total_out} Out | {total_cache} Cache") + imgui.text_colored(theme.get_color("status_info"), f"Discussion Tokens: {total_in} In | {total_out} Out | {total_cache} Cache") imgui.separator() imgui.text_colored(C_LBL, "commit:"); imgui.same_line() @@ -4053,7 +4054,7 @@ def render_vendor_state(app: App) -> None: imgui.table_setup_column("Value", imgui.TableColumnFlags_.width_stretch) imgui.table_setup_column("State", imgui.TableColumnFlags_.width_fixed, 60) imgui.table_headers_row() - state_colors = {"ok": vec4(120, 220, 120), "warn": vec4(240, 200, 80), "error": vec4(240, 80, 80), "info": vec4(180, 180, 180)} + state_colors = {"ok": theme.get_color("status_success"), "warn": theme.get_color("status_warning"), "error": theme.get_color("status_error"), "info": theme.get_color("text_disabled")} for m in metrics: imgui.table_next_row() imgui.table_next_column(); imgui.text(m.label) @@ -4061,7 +4062,7 @@ def render_vendor_state(app: App) -> None: imgui.text(m.value) if imgui.is_item_hovered(): imgui.set_tooltip(m.tooltip) imgui.table_next_column() - imgui.text_colored(state_colors.get(m.state, vec4(180, 180, 180)), m.state) + imgui.text_colored(state_colors.get(m.state, theme.get_color("text_disabled")), m.state) imgui.end_table() def render_message_panel(app: App) -> None: @@ -4071,8 +4072,7 @@ def render_message_panel(app: App) -> None: if is_live: val = math.sin(time.time() * 10 * math.pi) alpha = 1.0 if val > 0 else 0.0 - c = imgui.ImVec4(0.39, 1.0, 0.39, alpha) - if theme.is_nerv_active(): c = vec4(80, 255, 80, alpha) # DATA_GREEN for LIVE in NERV + c = theme.get_color("status_success", alpha=alpha) imgui.text_colored(c, "LIVE") imgui.separator() ch, app.ui_ai_input = imgui.input_text_multiline("##ai_in", app.ui_ai_input, imgui.ImVec2(-1, -40)) @@ -4323,12 +4323,12 @@ def render_text_viewer_window(app: App) -> None: is_auto_sliced = any(slc['start_line'] <= line_num <= slc['end_line'] for slc in app.ui_editing_slices_file.custom_slices if slc.get('tag') == 'auto-ast') is_manual_sliced = any(slc['start_line'] <= line_num <= slc['end_line'] for slc in app.ui_editing_slices_file.custom_slices if slc.get('tag') != 'auto-ast') - if is_manual_sliced: 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, 165, 0, 0.2))) - elif is_auto_sliced: 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.15))) + if is_manual_sliced: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + imgui.get_content_region_avail().x, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_manual", alpha=0.2))) + elif is_auto_sliced: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + imgui.get_content_region_avail().x, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_auto", alpha=0.15))) if app._slice_sel_start != -1 and app._slice_sel_end != -1: s, e = min(app._slice_sel_start, app._slice_sel_end), max(app._slice_sel_start, app._slice_sel_end) - if s <= line_num <= e: 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(100, 100, 255, 0.3))) + if s <= line_num <= e: draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + imgui.get_content_region_avail().x, pos.y + line_height), imgui.get_color_u32(theme.get_color("slice_selection", alpha=0.3))) imgui.selectable(f"{line_num:4} | {line_text}##ln{line_num}", False) if imgui.is_item_clicked(): app._slice_sel_start = line_num; app._slice_sel_end = line_num if imgui.is_item_hovered(imgui.HoveredFlags_.allow_when_blocked_by_active_item) and imgui.is_mouse_down(0): app._slice_sel_end = line_num @@ -4340,7 +4340,7 @@ def render_text_viewer_window(app: App) -> None: if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_text_viewer_ced") app._text_viewer_editor.render(f"##ced_{app.text_viewer_title}", imgui.ImVec2(-1, -1)) if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_text_viewer_ced") - except Exception as e: imgui.text_colored(vec4(255, 100, 100), f"CED Error: {e}"); imgui.text_unformatted(app.text_viewer_content) + except Exception as e: imgui.text_colored(theme.get_color("status_error"), f"CED Error: {e}"); imgui.text_unformatted(app.text_viewer_content) else: with imscope.child("tv_scroll", -1, -1, True): if app.text_viewer_wrap: imgui.push_text_wrap_pos(imgui.get_content_region_avail().x) @@ -4358,23 +4358,23 @@ def render_patch_modal(app: App) -> None: p_max = imgui.ImVec2(p_min.x + imgui.get_window_size().x, p_min.y + imgui.get_window_size().y) shaders.draw_soft_shadow(imgui.get_background_draw_list(), p_min, p_max, imgui.ImVec4(0, 0, 0, 0.6), 25.0, 6.0) - imgui.text_colored(vec4(255, 230, 77), "Tier 4 QA Generated a Patch") + imgui.text_colored(theme.get_color("status_warning"), "Tier 4 QA Generated a Patch") imgui.separator() if app._pending_patch_files: imgui.text("Files to modify:") for f in app._pending_patch_files: imgui.text(f" - {f}") imgui.separator() if app._patch_error_message: - imgui.text_colored(vec4(255, 77, 77), f"Error: {app._patch_error_message}") + imgui.text_colored(theme.get_color("status_error"), f"Error: {app._patch_error_message}") imgui.separator() imgui.text("Diff Preview:") imgui.begin_child("patch_diff_scroll", imgui.ImVec2(-1, 280), True) if app._pending_patch_text: diff_lines = app._pending_patch_text.split("\n") for line in diff_lines: - if line.startswith("+++") or line.startswith("---") or line.startswith("@@"): imgui.text_colored(vec4(77, 178, 255), line) - elif line.startswith("+"): imgui.text_colored(vec4(51, 230, 51), line) - elif line.startswith("-"): imgui.text_colored(vec4(230, 51, 51), line) + if line.startswith("+++") or line.startswith("---") or line.startswith("@@"): imgui.text_colored(theme.get_color("diff_header"), line) + elif line.startswith("+"): imgui.text_colored(theme.get_color("diff_added"), line) + elif line.startswith("-"): imgui.text_colored(theme.get_color("diff_removed"), line) else: imgui.text(line) imgui.end_child() imgui.separator() @@ -4454,7 +4454,7 @@ def render_approve_script_modal(app: App) -> None: if not dlg: imgui.close_current_popup() else: imgui.text("The AI wants to run the following PowerShell script:") - imgui.text_colored(vec4(200, 200, 100), f"base_dir: {dlg._base_dir}") + imgui.text_colored(theme.get_color("status_warning"), f"base_dir: {dlg._base_dir}") imgui.separator() # Checkbox to toggle full preview inside modal if not hasattr(app, 'ui_approve_modal_preview'): app.ui_approve_modal_preview = False @@ -4632,13 +4632,13 @@ def render_error_tint(app: App) -> None: if not HotReloader.is_error_state: return draw_list = imgui.get_background_draw_list() display_size = imgui.get_io().display_size - # Translucent red: (1.0, 0.0, 0.0, 0.2) - tint_col = imgui.get_color_u32(imgui.ImVec4(1.0, 0.0, 0.0, 0.2)) + # Translucent red + tint_col = imgui.get_color_u32(theme.get_color("status_error", alpha=0.2)) draw_list.add_rect_filled(imgui.ImVec2(0, 0), display_size, tint_col) if app.perf_profiling_enabled: imgui.set_next_window_pos(imgui.ImVec2(10, 50)) with imscope.window("Hot Reload Error", None, imgui.WindowFlags_.always_auto_resize | imgui.WindowFlags_.no_title_bar): - imgui.text_colored(imgui.ImVec4(1, 0, 0, 1), "HOT RELOAD ERROR") + imgui.text_colored(theme.get_color("status_error"), "HOT RELOAD ERROR") imgui.text_wrapped(HotReloader.last_error or "Unknown error") @@ -4651,14 +4651,14 @@ def render_project_stale_tint(app: App) -> None: if not app.controller.is_project_stale(): return draw_list = imgui.get_background_draw_list() display_size = imgui.get_io().display_size - tint_col = imgui.get_color_u32(imgui.ImVec4(1.0, 0.85, 0.2, 0.15)) + tint_col = imgui.get_color_u32(theme.get_color("status_warning", alpha=0.15)) draw_list.add_rect_filled(imgui.ImVec2(0, 0), display_size, tint_col) pending = app.controller._project_switch_pending_path or app.controller.active_project_path imgui.set_next_window_pos(imgui.ImVec2(10, 50)) with imscope.window("Project Stale", None, imgui.WindowFlags_.always_auto_resize | imgui.WindowFlags_.no_title_bar | imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_move): - imgui.text_colored(imgui.ImVec4(1.0, 0.85, 0.2, 1.0), "PROJECT SWITCHING") + imgui.text_colored(theme.get_color("status_warning"), "PROJECT SWITCHING") imgui.text_wrapped(f"Loading: {Path(pending).stem if pending else '?'}") imgui.text_wrapped("UI is read-only until the switch completes. You can still browse tabs.") @@ -4701,7 +4701,7 @@ def render_mma_dashboard(app: App) -> None: render_mma_focus_selector(app) imgui.separator() if app.is_viewing_prior_session: - c = vec4(255, 152, 48) if theme.is_nerv_active() else vec4(255, 200, 100) + c = theme.get_color("status_warning") if theme.is_nerv_active() else theme.get_color("status_warning") imgui.text_colored(c, "HISTORICAL VIEW - READ ONLY") if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_mma_dashboard") return @@ -4739,7 +4739,7 @@ def render_mma_modals(app: App) -> None: if not app._pending_ask_dialog or app._ask_tool_data is None: imgui.close_current_popup() else: tool_name = app._ask_tool_data.get("tool", "unknown"); tool_args = app._ask_tool_data.get("args", {}) - imgui.text("The AI wants to execute a tool:"); imgui.text_colored(vec4(200, 200, 100), f"Tool: {tool_name}"); imgui.separator() + imgui.text("The AI wants to execute a tool:"); imgui.text_colored(theme.get_color("status_warning"), f"Tool: {tool_name}"); imgui.separator() imgui.text("Arguments:"); imgui.begin_child("ask_args_child", imgui.ImVec2(400, 200), True); imgui.text_unformatted(json.dumps(tool_args, indent=2)); imgui.end_child() imgui.separator() if imgui.button("Approve", imgui.ImVec2(120, 0)): app._handle_approve_ask(); imgui.close_current_popup() @@ -4796,7 +4796,7 @@ def render_mma_modals(app: App) -> None: imgui.end_popup() # Cycle Detection if imgui.begin_popup_modal("Cycle Detected!", None, imgui.WindowFlags_.always_auto_resize)[0]: - imgui.text_colored(imgui.ImVec4(1, 0.3, 0.3, 1), "The dependency graph contains a cycle!") + imgui.text_colored(theme.get_color("status_error", alpha=1), "The dependency graph contains a cycle!") imgui.text("Please remove the circular dependency.") if imgui.button("OK"): imgui.close_current_popup() imgui.end_popup() @@ -4812,16 +4812,16 @@ def render_mma_track_summary(app: App) -> None: total_cost = sum(cost_tracker.estimate_cost(u.get('model','unknown'), u.get('input',0), u.get('output',0)) for u in app.mma_tier_usage.values()) imgui.text("Track:"); imgui.same_line(); imgui.text_colored(C_VAL, track_name); imgui.same_line(); imgui.text(" | Status:"); imgui.same_line() if app.mma_status == "paused": - imgui.text_colored(vec4(255, 152, 48) if is_nerv else imgui.ImVec4(1, 0.5, 0, 1), "PIPELINE PAUSED"); imgui.same_line() + imgui.text_colored(theme.get_color("status_warning") if is_nerv else theme.get_color("status_warning"), "PIPELINE PAUSED"); imgui.same_line() status_col = imgui.ImVec4(1, 1, 1, 1) - if app.mma_status == "idle": status_col = imgui.ImVec4(0.7, 0.7, 0.7, 1) - elif app.mma_status == "running": status_col = vec4(80, 255, 80) if is_nerv else imgui.ImVec4(1, 1, 0, 1) - elif app.mma_status == "done": status_col = imgui.ImVec4(0, 1, 0, 1) - elif app.mma_status == "error": status_col = vec4(255, 72, 64) if is_nerv else imgui.ImVec4(1, 0, 0, 1) - elif app.mma_status == "paused": status_col = imgui.ImVec4(1, 0.5, 0, 1) - imgui.text_colored(status_col, app.mma_status.upper()); imgui.same_line(); imgui.text(" | Cost:"); imgui.same_line(); imgui.text_colored(imgui.ImVec4(0, 1, 0, 1), f"${total_cost:,.4f}") + if app.mma_status == "idle": status_col = theme.get_color("text_disabled") + elif app.mma_status == "running": status_col = theme.get_color("status_success") if is_nerv else theme.get_color("status_warning") + elif app.mma_status == "done": status_col = theme.get_color("status_success") + elif app.mma_status == "error": status_col = theme.get_color("status_error") if is_nerv else theme.get_color("status_error") + elif app.mma_status == "paused": status_col = theme.get_color("status_warning") + imgui.text_colored(status_col, app.mma_status.upper()); imgui.same_line(); imgui.text(" | Cost:"); imgui.same_line(); imgui.text_colored(theme.get_color("status_success"), f"${total_cost:,.4f}") perc = track_stats["percentage"] / 100.0 - p_color = imgui.ImVec4(1, 0, 0, 1) if track_stats["percentage"] < 33 else (imgui.ImVec4(1, 1, 0, 1) if track_stats["percentage"] < 66 else imgui.ImVec4(0, 1, 0, 1)) + p_color = theme.get_color("status_error") if track_stats["percentage"] < 33 else (theme.get_color("status_warning") if track_stats["percentage"] < 66 else theme.get_color("status_success")) imgui.push_style_color(imgui.Col_.plot_histogram, p_color); imgui.progress_bar(perc, imgui.ImVec2(-1, 0), f"{track_stats['percentage']:.1f}%"); imgui.pop_style_color() if imgui.begin_table("ticket_stats_breakdown", 4): for lbl, val in [("Completed:", track_stats["completed"]), ("In Progress:", track_stats["in_progress"]), ("Blocked:", track_stats["blocked"]), ("Todo:", track_stats["todo"])]: @@ -4857,10 +4857,10 @@ def render_mma_track_browser(app: App) -> None: for track in app.tracks: imgui.table_next_row(); imgui.table_next_column(); imgui.text(track.get("title", "Untitled")); imgui.table_next_column() status = track.get("status", "unknown").lower() - c = imgui.ImVec4(0.7, 0.7, 0.7, 1) if status == "new" else (vec4(80, 255, 80) if status == "active" and theme.is_nerv_active() else (imgui.ImVec4(1, 1, 0, 1) if status == "active" else (imgui.ImVec4(0, 1, 0, 1) if status == "done" else (imgui.ImVec4(1, 0, 0, 1) if status == "blocked" else imgui.ImVec4(1, 1, 1, 1))))) + c = theme.get_color("text_disabled") if status == "new" else (theme.get_color("status_success") if status == "active" and theme.is_nerv_active() else (theme.get_color("status_warning") if status == "active" else (theme.get_color("status_success") if status == "done" else (theme.get_color("status_error") if status == "blocked" else imgui.ImVec4(1, 1, 1, 1))))) imgui.text_colored(c, status.upper()); imgui.table_next_column() prog = track.get("progress", 0.0) - p_c = imgui.ImVec4(1, 0, 0, 1) if prog < 0.33 else (imgui.ImVec4(1, 1, 0, 1) if prog < 0.66 else imgui.ImVec4(0, 1, 0, 1)) + p_c = theme.get_color("status_error") if prog < 0.33 else (theme.get_color("status_warning") if prog < 0.66 else theme.get_color("status_success")) imgui.push_style_color(imgui.Col_.plot_histogram, p_c); imgui.progress_bar(prog, imgui.ImVec2(-1, 0), f"{int(prog*100)}%"); imgui.pop_style_color(); imgui.table_next_column() if imgui.button(f"Load##{track.get('id')}"): app._cb_load_track(str(track.get("id") or "")) imgui.end_table() @@ -4902,9 +4902,9 @@ def render_mma_global_controls(app: App) -> None: if imgui.button("Reload GUI"): success = app._trigger_hot_reload() if success: - imgui.text_colored(imgui.ImVec4(0, 1, 0, 1), "Reloaded!") + imgui.text_colored(theme.get_color("status_success"), "Reloaded!") else: - imgui.text_colored(imgui.ImVec4(1, 0, 0, 1), f"Error: {app._hot_reload_error or 'Unknown'}") + imgui.text_colored(theme.get_color("status_error"), f"Error: {app._hot_reload_error or 'Unknown'}") imgui.same_line(); imgui.text_disabled("(Ctrl+Alt+R)") def render_mma_usage_section(app: App) -> None: @@ -4999,7 +4999,7 @@ def render_tier_stream_panel(app: App, tier_key: str, stream_key: str | None) -> """ if app.perf_profiling_enabled: app.perf_monitor.start_component("_render_tier_stream_panel") if app.is_viewing_prior_session: - imgui.text_colored(vec4(255, 200, 100), "HISTORICAL VIEW - READ ONLY") + imgui.text_colored(theme.get_color("status_warning"), "HISTORICAL VIEW - READ ONLY") if app.perf_profiling_enabled: app.perf_monitor.end_component("_render_tier_stream_panel") return if stream_key is not None: @@ -5024,11 +5024,11 @@ def render_tier_stream_panel(app: App, tier_key: str, stream_key: str | None) -> ticket_id = key.split(": ", 1)[-1] if ": " in key else key status = worker_status.get(key, "unknown") if status == "running": - imgui.text_colored(imgui.ImVec4(1, 1, 0, 1), f"{ticket_id} [{status}]") + imgui.text_colored(theme.get_color("status_warning"), f"{ticket_id} [{status}]") elif status == "completed": - imgui.text_colored(imgui.ImVec4(0, 1, 0, 1), f"{ticket_id} [{status}]") + imgui.text_colored(theme.get_color("status_success"), f"{ticket_id} [{status}]") elif status == "failed": - imgui.text_colored(imgui.ImVec4(1, 0, 0, 1), f"{ticket_id} [{status}]") + imgui.text_colored(theme.get_color("status_error"), f"{ticket_id} [{status}]") else: imgui.text(f"{ticket_id} [{status}]") imgui.begin_child(f"##tier3_{ticket_id}_scroll", imgui.ImVec2(-1, 150), True) @@ -5156,9 +5156,9 @@ def render_ticket_queue(app: App) -> None: imgui.table_next_column() prio = t.get('priority', 'medium') - p_col = vec4(180, 180, 180) # gray - if prio == 'high': _col = vec4(255, 100, 100) # red - elif prio == 'medium': p_col = vec4(255, 255, 100) # yellow + p_col = theme.get_color("text_disabled") # gray + if prio == 'high': _col = theme.get_color("status_error") # red + elif prio == 'medium': p_col = theme.get_color("status_warning") # yellow imgui.push_style_color(imgui.Col_.text, p_col) if imgui.begin_combo(f"##prio_{tid}", prio, imgui.ComboFlags_.height_small): @@ -5184,7 +5184,7 @@ def render_ticket_queue(app: App) -> None: # Status imgui.table_next_column() status = t.get('status', 'todo') - if t.get('model_override'): imgui.text_colored(imgui.ImVec4(1, 0.5, 0, 1), f"{status} [{t.get('model_override')}]") + if t.get('model_override'): imgui.text_colored(theme.get_color("status_warning"), f"{status} [{t.get('model_override')}]") else: imgui.text(t.get('status', 'todo')) # Description @@ -5223,14 +5223,14 @@ def render_task_dag_panel(app: App) -> None: # 4. Task DAG Visualizer int_id = abs(hash(tid)) ed.begin_node(ed.NodeId(int_id)) if getattr(app, "ui_project_execution_mode", "native") == "beads": - imgui.text_colored(imgui.ImVec4(0, 1, 1, 1), "[B] ") + imgui.text_colored(theme.get_color("status_info"), "[B] ") imgui.same_line() imgui.text_colored(C_KEY, f"Ticket: {tid}") status = t.get('status', 'todo') s_col = C_VAL if status == 'done' or status == 'complete': s_col = C_IN elif status == 'in_progress' or status == 'running': s_col = C_OUT - elif status == 'error': s_col = imgui.ImVec4(1, 0.4, 0.4, 1) + elif status == 'error': s_col = theme.get_color("status_error") imgui.text("Status: ") imgui.same_line() imgui.text_colored(s_col, status) @@ -5359,7 +5359,7 @@ def render_beads_tab(app: App) -> None: missing = [] if not dolt_path: missing.append("'dolt'") if not bd_path: missing.append("'bd'") - imgui.text_colored(imgui.ImVec4(1, 0.5, 0, 1), f"Warning: {', '.join(missing)} not found in PATH.") + imgui.text_colored(theme.get_color("status_warning"), f"Warning: {', '.join(missing)} not found in PATH.") imgui.text_wrapped("Beads mode requires Dolt and the Beads (bd) CLI tools.") if getattr(app, "ui_project_execution_mode", "native") == "beads": @@ -5385,7 +5385,7 @@ def render_beads_tab(app: App) -> None: imgui.text(str(b.title)) imgui.end_table() except Exception as e: - imgui.text_colored(imgui.ImVec4(1, 0, 0, 1), f"Error loading beads: {e}") + imgui.text_colored(theme.get_color("status_error"), f"Error loading beads: {e}") def render_mma_focus_selector(app: App) -> None: """ @@ -5409,7 +5409,7 @@ def render_empty_context_modal(app: App) -> None: app.show_empty_context_modal = False if imgui.begin_popup_modal("Empty Context Warning", True, imgui.WindowFlags_.always_auto_resize)[0]: - imgui.text_colored(imgui.ImVec4(1.0, 1.0, 0.0, 1.0), "WARNING: Empty Context Composition") + imgui.text_colored(theme.get_color("status_warning"), "WARNING: Empty Context Composition") imgui.text("You are attempting to generate a response without any files selected.") imgui.text("This may result in poor AI performance or loss of project context.") imgui.separator() @@ -5436,7 +5436,7 @@ def render_context_modals(app: App) -> None: imgui.separator() imgui.begin_child("missing_files_list", imgui.ImVec2(0, 150), True) for f in app.missing_context_files: - imgui.text_colored(imgui.ImVec4(1, 0.4, 0.4, 1), f) + imgui.text_colored(theme.get_color("status_error"), f) imgui.end_child() imgui.separator() diff --git a/src/theme_2.py b/src/theme_2.py index 507547fb..fb37a976 100644 --- a/src/theme_2.py +++ b/src/theme_2.py @@ -10,6 +10,7 @@ Scale uses imgui.get_style().font_scale_main. from imgui_bundle import imgui, hello_imgui from typing import Any, Optional +import typing from contextlib import nullcontext from src import imgui_scopes as imscope import src.theme_nerv @@ -22,22 +23,12 @@ from src.theme_models import ThemeFile, load_themes_from_dir, load_themes_from_t # Each palette maps imgui color enum values to (R, G, B, A) floats [0..1]. # Only keys that differ from the ImGui dark defaults need to be listed. +# ------------------------------------------------------------------ internal helpers def _c(r: int, g: int, b: int, a: int = 255) -> tuple[float, float, float, float]: - """ - Convert 0-255 RGBA to 0.0-1.0 floats. - [C: src/theme_nerv.py:module] - """ + """Convert 0-255 RGBA to 0.0-1.0 floats.""" return (r / 255.0, g / 255.0, b / 255.0, a / 255.0) -_BUILTIN_PALETTES: dict[str, dict[int, tuple]] = { - "ImGui Dark": {}, # empty = use imgui dark defaults - "NERV": {}, -} - -_TOML_PALETTES: dict[str, ThemeFile] = {} -_TOML_COLOUR_CACHE: dict[str, dict[int, tuple[float, float, float, float]]] = {} - def _hex(rgb: tuple[int, int, int]) -> tuple[float, float, float, float]: return (rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0, 1.0) @@ -52,19 +43,26 @@ def _build_imgui_colour_dict(theme: ThemeFile) -> dict[int, tuple[float, float, out[enum_val] = _hex(rgb) return out - -def get_palette_names() -> list[str]: - """Returns a list of all available palettes, including hello_imgui built-ins - and TOML-loaded themes.""" - names = list(_BUILTIN_PALETTES.keys()) - names.extend(sorted(_TOML_PALETTES.keys())) - hi_themes = [name for name in dir(hello_imgui.ImGuiTheme_) if not name.startswith('_') and name != 'count'] - hi_themes = [n for n in hi_themes if not hasattr(int, n)] - names.extend(sorted(hi_themes)) - return names +def _build_semantic_colour_dict(theme: ThemeFile) -> dict[str, tuple[float, float, float, float]]: + out: dict[str, tuple[float, float, float, float]] = {} + palette_dict = theme.palette.to_dict() + from imgui_bundle import imgui + for col_name, rgb in palette_dict.items(): + if not hasattr(imgui.Col_, col_name): + out[col_name] = _hex(rgb) + return out # ------------------------------------------------------------------ state +_BUILTIN_PALETTES: dict[str, dict[int, tuple]] = { + "ImGui Dark": {}, + "NERV": {}, +} + +_TOML_PALETTES: dict[str, ThemeFile] = {} +_TOML_COLOUR_CACHE: dict[str, dict[int, tuple[float, float, float, float]]] = {} +_TOML_SEMANTIC_CACHE: dict[str, dict[str, tuple[float, float, float, float]]] = {} + _current_palette: str = "10x Dark" _current_font_path: str = "fonts/Inter-Regular.ttf" _current_font_size: float = 16.0 @@ -141,13 +139,50 @@ def set_child_transparency(val: float) -> None: _child_transparency = val apply(_current_palette) -def apply(palette_name: str) -> None: - """ +def get_color(name: str, alpha: float = 1.0) -> imgui.ImVec4: + """Return a tone-mapped semantic color from the current palette.""" + palette_name = _current_palette + if palette_name in _TOML_SEMANTIC_CACHE: + d = _TOML_SEMANTIC_CACHE[palette_name] + if name in d: + rgba = list(d[name]) + rgba[3] = alpha + return imgui.ImVec4(*_tone_map(tuple(rgba), palette_name)) - - Apply a named palette by setting all ImGui style colors and applying global professional styling. - [C: tests/test_theme.py:test_theme_apply_sets_rounding_and_padding] - """ + # Hardcoded fallbacks if not in TOML (matches ThemePalette defaults) + fallbacks = { + "status_success": (80, 255, 80), + "status_warning": (255, 152, 48), + "status_error": (255, 72, 64), + "status_info": (0, 255, 255), + "bubble_user": (30, 45, 75), + "bubble_ai": (35, 65, 45), + "bubble_vendor": (65, 55, 30), + "bubble_system": (25, 25, 25), + "slice_manual": (255, 165, 0), + "slice_auto": (0, 255, 0), + "slice_selection": (100, 100, 255), + "diff_added": (51, 230, 51), + "diff_removed": (230, 51, 51), + "diff_header": (77, 178, 255), + } + if name in fallbacks: + rgb = fallbacks[name] + return imgui.ImVec4(*_tone_map((rgb[0]/255.0, rgb[1]/255.0, rgb[2]/255.0, alpha), palette_name)) + + return imgui.ImVec4(1, 1, 1, alpha) + +def get_role_tint(role: str) -> imgui.ImVec4: + """Returns a subtle background tint color based on the message role.""" + mapping = { + "User": "bubble_user", + "AI": "bubble_ai", + "Vendor API": "bubble_vendor", + } + return get_color(mapping.get(role, "bubble_system"), alpha=0.6) + +def apply(palette_name: str) -> None: + """Apply a named palette by setting all ImGui style colors and professional styling.""" global _current_palette _current_palette = palette_name if palette_name == 'NERV': @@ -175,13 +210,10 @@ def apply(palette_name: str) -> None: elif hasattr(hello_imgui.ImGuiTheme_, palette_name): theme_enum = getattr(hello_imgui.ImGuiTheme_, palette_name) hello_imgui.apply_theme(theme_enum) - # hello_imgui doesn't expose the underlying dict easily to tone-map after-the-fact - # without re-reading every enum. For now, BUILTIN and TOML themes get full TM. else: - # Fallback imgui.style_colors_dark() - # 2. Apply our "Subtle Rounding" professional tweaks on top of ANY theme + # 2. Professional tweaks style = imgui.get_style() style.window_rounding = 6.0 style.child_rounding = 4.0 @@ -194,44 +226,50 @@ def apply(palette_name: str) -> None: style.frame_border_size = 1.0 style.popup_border_size = 1.0 - # Apply transparency to WindowBg win_bg = style.color_(imgui.Col_.window_bg) win_bg.w = _transparency style.set_color_(imgui.Col_.window_bg, win_bg) - # Apply child/frame transparency for col_idx in [imgui.Col_.child_bg, imgui.Col_.frame_bg, imgui.Col_.popup_bg]: c = style.color_(col_idx) c.w = _child_transparency style.set_color_(col_idx, c) - # Spacing & Padding style.window_padding = imgui.ImVec2(8.0, 8.0) style.frame_padding = imgui.ImVec2(8.0, 4.0) style.item_spacing = imgui.ImVec2(8.0, 4.0) style.item_inner_spacing = imgui.ImVec2(4.0, 4.0) style.scrollbar_size = 14.0 - - # Rendering anti-aliasing (Shaders/Quality) style.anti_aliased_lines = True style.anti_aliased_fill = True style.anti_aliased_lines_use_tex = True # 3. Sync syntax palette and clear markdown render cache apply_syntax_palette(get_syntax_palette_for_theme(palette_name)) - import src.markdown_helper - src.markdown_helper.get_renderer().clear_cache() + try: + import src.markdown_helper + src.markdown_helper.get_renderer().clear_cache() + except (ImportError, AttributeError): + pass -def set_scale(factor: float) -> None: - """Set the global font/UI scale factor.""" - global _current_scale - _current_scale = factor - style = imgui.get_style() - style.font_scale_main = factor +def apply_current() -> None: + """Apply the loaded palette and scale.""" + apply(_current_palette) + set_scale(_current_scale) + +def get_palette_names() -> list[str]: + """Returns a list of all available palettes.""" + names = list(_BUILTIN_PALETTES.keys()) + names.extend(sorted(_TOML_PALETTES.keys())) + hi_themes = [name for name in dir(hello_imgui.ImGuiTheme_) if not name.startswith('_') and name != 'count'] + hi_themes = [n for n in hi_themes if not hasattr(int, n)] + names.extend(sorted(hi_themes)) + return names + +# ------------------------------------------------------------------ persistence def save_to_config(config: dict) -> None: - """Persist theme settings into the config dict under [theme].""" - import sys + """Persist theme settings into the config dict.""" config.setdefault("theme", {}) config["theme"]["palette"] = _current_palette config["theme"]["font_path"] = _current_font_path @@ -239,7 +277,6 @@ def save_to_config(config: dict) -> None: config["theme"]["scale"] = _current_scale config["theme"]["transparency"] = _transparency config["theme"]["child_transparency"] = _child_transparency - # Tone mapping tm = {} for p in set(list(_brightness.keys()) + list(_contrast.keys()) + list(_gamma.keys())): tm[p] = { @@ -248,49 +285,37 @@ def save_to_config(config: dict) -> None: "gamma": _gamma.get(p, 1.0) } config["theme"]["tone_mapping"] = tm - sys.stderr.write(f"[DEBUG theme_2] save_to_config: palette={_current_palette}\n") - sys.stderr.flush() def load_from_config(config: dict) -> None: - """Read [theme] from config. Font is handled separately at startup.""" + """Read [theme] from config.""" import sys global _current_font_path, _current_font_size, _current_scale, _current_palette, _transparency, _child_transparency, _brightness, _contrast, _gamma t = config.get("theme", {}) _current_palette = t.get("palette", "10x Dark") if _current_palette in ("", "DPG Default"): _current_palette = "10x Dark" - _current_font_path = t.get("font_path", "fonts/Inter-Regular.ttf") _current_font_size = float(t.get("font_size", 16.0)) _current_scale = float(t.get("scale", 1.0)) _transparency = float(t.get("transparency", 1.0)) _child_transparency = float(t.get("child_transparency", 1.0)) - # Tone mapping tm = t.get("tone_mapping", {}) _brightness = {p: float(v.get("brightness", 1.0)) for p, v in tm.items()} _contrast = {p: float(v.get("contrast", 1.0)) for p, v in tm.items()} _gamma = {p: float(v.get("gamma", 1.0)) for p, v in tm.items()} - sys.stderr.write(f"[DEBUG theme_2] load_from_config: palette={_current_palette}\n") - sys.stderr.flush() - -def apply_current() -> None: - """Apply the loaded palette and scale. Call after imgui context exists.""" - apply(_current_palette) - set_scale(_current_scale) +# ------------------------------------------------------------------ external themes def load_themes_from_disk() -> None: - """Load all themes from the global themes directory. Each *.toml file - in the directory is one theme. Idempotent - safe to call repeatedly. - Broken entries are logged and skipped.""" - global _TOML_PALETTES, _TOML_COLOUR_CACHE + """Load all themes from the global themes directory.""" + global _TOML_PALETTES, _TOML_COLOUR_CACHE, _TOML_SEMANTIC_CACHE themes_dir = get_global_themes_path() loaded: dict[str, ThemeFile] = {} if themes_dir.exists() and themes_dir.is_dir(): loaded.update(load_themes_from_dir(themes_dir, scope="global")) _TOML_PALETTES = loaded _TOML_COLOUR_CACHE = {name: _build_imgui_colour_dict(t) for name, t in loaded.items()} - + _TOML_SEMANTIC_CACHE = {name: _build_semantic_colour_dict(t) for name, t in loaded.items()} def get_syntax_palette_for_theme(theme_name: str) -> str: """Return the syntax palette name (one of dark/light/mariana/retro_blue) @@ -300,7 +325,6 @@ def get_syntax_palette_for_theme(theme_name: str) -> str: return _TOML_PALETTES[theme_name].syntax_palette return "dark" - def apply_syntax_palette(palette_name: str) -> None: """Set the default imgui_color_text_edit palette. palette_name must be one of: dark, light, mariana, retro_blue. No-op for unknown names.""" @@ -312,46 +336,42 @@ def apply_syntax_palette(palette_name: str) -> None: return ed.TextEditor.set_default_palette(palette_id) +# ------------------------------------------------------------------ font & scaling -load_themes_from_disk() +def set_scale(factor: float) -> None: + global _current_scale + _current_scale = factor + imgui.get_style().font_scale_main = factor def get_font_loading_params() -> tuple[str, float]: - """Return (font_path, font_size) for use during hello_imgui font loading callback.""" return _current_font_path, _current_font_size def get_tweaked_theme() -> hello_imgui.ImGuiTweakedTheme: - """Returns an ImGuiTweakedTheme object reflecting the current state.""" tt = hello_imgui.ImGuiTweakedTheme() if hasattr(hello_imgui.ImGuiTheme_, _current_palette): tt.theme = getattr(hello_imgui.ImGuiTheme_, _current_palette) else: tt.theme = hello_imgui.ImGuiTheme_.imgui_colors_dark - - # Sync tweaks tt.tweaks.rounding = 6.0 return tt +# ------------------------------------------------------------------ specialized colors + def ai_text_color() -> imgui.ImVec4: - """Returns DATA_GREEN if NERV is active, otherwise standard text color.""" if is_nerv_active(): return imgui.ImVec4(*DATA_GREEN) return imgui.get_style().color_(imgui.Col_.text) def ai_text_style(): - """Context manager for AI response text styling.""" return imscope.style_color(imgui.Col_.text, ai_text_color()) -def get_role_tint(role: str) -> imgui.ImVec4: """Returns a subtle background tint color based on the message role.""" - # Deep, low-saturation tints for distinct role hierarchy - if role == "User": return imgui.ImVec4(0.12, 0.18, 0.30, 0.6) # Midnight Blue - elif role == "AI": return imgui.ImVec4(0.14, 0.25, 0.18, 0.6) # Forest Green - elif role == "Vendor API": return imgui.ImVec4(0.25, 0.22, 0.12, 0.5) # Bronze - return imgui.ImVec4(0.1, 0.1, 0.1, 0.4) # Neutral System - def render_post_fx(width: float, height: float, ai_status: str, crt_enabled: bool) -> None: """Updates and renders the alert and CRT filters.""" _alert_pulsing.update(ai_status) _alert_pulsing.render(width, height) _crt_filter.enabled = crt_enabled - _crt_filter.render(width, height) \ No newline at end of file + _crt_filter.render(width, height) + +# ------------------------------------------------------------------ init +load_themes_from_disk() diff --git a/src/theme_models.py b/src/theme_models.py index 70fc159c..e0c7b42e 100644 --- a/src/theme_models.py +++ b/src/theme_models.py @@ -9,7 +9,6 @@ from typing import Any VALID_SYNTAX_PALETTES: tuple[str, ...] = ("dark", "light", "mariana", "retro_blue") -@dataclass @dataclass class ThemePalette: window_bg: tuple[int, int, int] = (0, 0, 0) @@ -75,6 +74,24 @@ class ThemePalette: tree_lines: tuple[int, int, int] = (60, 60, 60) unsaved_marker: tuple[int, int, int] = (200, 200, 200) + # Semantic colors + status_success: tuple[int, int, int] = (80, 255, 80) + status_warning: tuple[int, int, int] = (255, 152, 48) + status_error: tuple[int, int, int] = (255, 72, 64) + status_info: tuple[int, int, int] = (0, 255, 255) + bubble_user: tuple[int, int, int] = (30, 45, 75) + bubble_ai: tuple[int, int, int] = (35, 65, 45) + bubble_vendor: tuple[int, int, int] = (65, 55, 30) + bubble_system: tuple[int, int, int] = (25, 25, 25) + slice_manual: tuple[int, int, int] = (255, 165, 0) + slice_auto: tuple[int, int, int] = (0, 255, 0) + slice_selection: tuple[int, int, int] = (100, 100, 255) + + # Diff colors + diff_added: tuple[int, int, int] = (51, 230, 51) + diff_removed: tuple[int, int, int] = (230, 51, 51) + diff_header: tuple[int, int, int] = (77, 178, 255) + @classmethod def from_dict(cls, data: dict[str, Any]) -> ThemePalette: kwargs: dict[str, Any] = {} diff --git a/themes/10x_dark.toml b/themes/10x_dark.toml index ffe64972..8fbd044d 100644 --- a/themes/10x_dark.toml +++ b/themes/10x_dark.toml @@ -3,53 +3,67 @@ syntax_palette = "dark" description = "10x Dark Theme" [colors] -window_bg = [ 34, 32, 28] -child_bg = [ 30, 28, 24] -popup_bg = [ 35, 30, 20] border = [ 60, 55, 50] border_shadow = [ 0, 0, 0] +bubble_ai = [ 35, 65, 45] +bubble_system = [ 25, 25, 25] +bubble_user = [ 30, 45, 75] +bubble_vendor = [ 65, 55, 30] +button = [ 83, 76, 60] +button_active = [115, 90, 70] +button_hovered = [126, 78, 14] +check_mark = [194, 164, 74] +child_bg = [ 30, 28, 24] +diff_added = [ 51, 230, 51] +diff_header = [ 77, 178, 255] +diff_removed = [230, 51, 51] +docking_empty_bg = [ 20, 20, 20] +docking_preview = [126, 78, 14] frame_bg = [ 45, 42, 38] -frame_bg_hovered = [ 60, 56, 50] frame_bg_active = [ 75, 70, 62] -title_bg = [ 40, 35, 25] -title_bg_active = [ 60, 45, 15] -title_bg_collapsed = [ 30, 27, 20] +frame_bg_hovered = [ 60, 56, 50] +header = [ 83, 76, 60] +header_active = [115, 90, 70] +header_hovered = [126, 78, 14] menu_bar_bg = [ 35, 30, 20] +modal_window_dim_bg = [ 10, 10, 10] +nav_cursor = [126, 78, 14] +nav_windowing_dim_bg = [ 20, 20, 20] +nav_windowing_highlight = [194, 164, 74] +popup_bg = [ 35, 30, 20] +resize_grip = [ 60, 55, 44] +resize_grip_active = [194, 164, 74] +resize_grip_hovered = [126, 78, 14] scrollbar_bg = [ 30, 28, 24] scrollbar_grab = [ 80, 78, 72] -scrollbar_grab_hovered = [100, 100, 92] scrollbar_grab_active = [120, 118, 110] -check_mark = [194, 164, 74] +scrollbar_grab_hovered = [100, 100, 92] +separator = [ 70, 65, 55] +separator_active = [194, 164, 74] +separator_hovered = [126, 78, 14] +slice_auto = [ 0, 255, 0] +slice_manual = [255, 165, 0] +slice_selection = [100, 100, 255] slider_grab = [126, 78, 14] slider_grab_active = [194, 140, 30] -button = [ 83, 76, 60] -button_hovered = [126, 78, 14] -button_active = [115, 90, 70] -header = [ 83, 76, 60] -header_hovered = [126, 78, 14] -header_active = [115, 90, 70] -separator = [ 70, 65, 55] -separator_hovered = [126, 78, 14] -separator_active = [194, 164, 74] -resize_grip = [ 60, 55, 44] -resize_grip_hovered = [126, 78, 14] -resize_grip_active = [194, 164, 74] +status_error = [255, 72, 64] +status_info = [ 0, 255, 255] +status_success = [ 80, 255, 80] +status_warning = [255, 152, 48] tab = [ 83, 83, 70] -tab_hovered = [126, 77, 25] -tab_selected = [126, 77, 25] tab_dimmed = [ 60, 58, 50] tab_dimmed_selected = [ 90, 80, 55] -docking_preview = [126, 78, 14] -docking_empty_bg = [ 20, 20, 20] +tab_hovered = [126, 77, 25] +tab_selected = [126, 77, 25] +table_border_light = [ 50, 47, 42] +table_border_strong = [ 70, 65, 55] +table_header_bg = [ 55, 50, 38] +table_row_bg = [ 0, 0, 0] +table_row_bg_alt = [ 40, 38, 34] text = [200, 200, 200] text_disabled = [130, 130, 120] text_selected_bg = [ 59, 86, 142] -table_header_bg = [ 55, 50, 38] -table_border_strong = [ 70, 65, 55] -table_border_light = [ 50, 47, 42] -table_row_bg = [ 0, 0, 0] -table_row_bg_alt = [ 40, 38, 34] -nav_cursor = [126, 78, 14] -nav_windowing_highlight = [194, 164, 74] -nav_windowing_dim_bg = [ 20, 20, 20] -modal_window_dim_bg = [ 10, 10, 10] \ No newline at end of file +title_bg = [ 40, 35, 25] +title_bg_active = [ 60, 45, 15] +title_bg_collapsed = [ 30, 27, 20] +window_bg = [ 34, 32, 28] diff --git a/themes/binks.toml b/themes/binks.toml index 6ee86a69..e1698087 100644 --- a/themes/binks.toml +++ b/themes/binks.toml @@ -3,40 +3,54 @@ syntax_palette = "light" description = "Binks Theme" [colors] -text = [ 0, 0, 0] -text_disabled = [153, 153, 153] -window_bg = [240, 240, 240] -child_bg = [ 0, 0, 0] -popup_bg = [255, 255, 255] -border = [ 0, 0, 0] -border_shadow = [255, 255, 255] -frame_bg = [255, 255, 255] -frame_bg_hovered = [ 66, 150, 250] -frame_bg_active = [ 66, 150, 250] -title_bg = [245, 245, 245] -title_bg_collapsed = [255, 255, 255] -title_bg_active = [209, 209, 209] -menu_bar_bg = [219, 219, 219] -scrollbar_bg = [250, 250, 250] -scrollbar_grab = [176, 176, 176] -scrollbar_grab_hovered = [150, 150, 150] -scrollbar_grab_active = [125, 125, 125] -check_mark = [ 66, 150, 250] -slider_grab = [ 61, 133, 224] -slider_grab_active = [ 66, 150, 250] -button = [ 66, 150, 250] -button_hovered = [ 66, 150, 250] -button_active = [ 15, 135, 250] -header = [ 66, 150, 250] -header_hovered = [ 66, 150, 250] -header_active = [ 66, 150, 250] -separator = [100, 100, 100] -resize_grip = [255, 255, 255] -resize_grip_hovered = [ 66, 150, 250] -resize_grip_active = [ 66, 150, 250] -plot_lines = [ 99, 99, 99] -plot_lines_hovered = [255, 110, 89] -plot_histogram = [230, 178, 0] -plot_histogram_hovered = [255, 153, 0] -text_selected_bg = [ 66, 150, 250] -modal_window_dim_bg = [ 51, 51, 51] \ No newline at end of file +border = [ 0, 0, 0] +border_shadow = [255, 255, 255] +bubble_ai = [220, 255, 220] +bubble_system = [240, 240, 240] +bubble_user = [220, 230, 255] +bubble_vendor = [255, 240, 200] +button = [ 66, 150, 250] +button_active = [ 15, 135, 250] +button_hovered = [ 66, 150, 250] +check_mark = [ 66, 150, 250] +child_bg = [ 0, 0, 0] +diff_added = [ 40, 180, 40] +diff_header = [ 40, 100, 200] +diff_removed = [200, 40, 40] +frame_bg = [255, 255, 255] +frame_bg_active = [ 66, 150, 250] +frame_bg_hovered = [ 66, 150, 250] +header = [ 66, 150, 250] +header_active = [ 66, 150, 250] +header_hovered = [ 66, 150, 250] +menu_bar_bg = [219, 219, 219] +modal_window_dim_bg = [ 51, 51, 51] +plot_histogram = [230, 178, 0] +plot_histogram_hovered = [255, 153, 0] +plot_lines = [ 99, 99, 99] +plot_lines_hovered = [255, 110, 89] +popup_bg = [255, 255, 255] +resize_grip = [255, 255, 255] +resize_grip_active = [ 66, 150, 250] +resize_grip_hovered = [ 66, 150, 250] +scrollbar_bg = [250, 250, 250] +scrollbar_grab = [176, 176, 176] +scrollbar_grab_active = [125, 125, 125] +scrollbar_grab_hovered = [150, 150, 150] +separator = [100, 100, 100] +slice_auto = [ 80, 255, 80] +slice_manual = [255, 200, 0] +slice_selection = [180, 180, 255] +slider_grab = [ 61, 133, 224] +slider_grab_active = [ 66, 150, 250] +status_error = [200, 40, 40] +status_info = [ 40, 100, 200] +status_success = [ 40, 180, 40] +status_warning = [200, 140, 0] +text = [ 0, 0, 0] +text_disabled = [153, 153, 153] +text_selected_bg = [ 66, 150, 250] +title_bg = [245, 245, 245] +title_bg_active = [209, 209, 209] +title_bg_collapsed = [255, 255, 255] +window_bg = [240, 240, 240] diff --git a/themes/gruvbox_dark.toml b/themes/gruvbox_dark.toml index e6a0e549..0ee82035 100644 --- a/themes/gruvbox_dark.toml +++ b/themes/gruvbox_dark.toml @@ -1,38 +1,52 @@ -# Gruvbox Dark — Pavel Pertsev's warm retro palette +name = "gruvbox_dark" syntax_palette = "retro_blue" description = "Gruvbox Dark by Pavel Pertsev (github.com/morhetz/gruvbox)" [colors] -window_bg = [ 40, 40, 40] # bg -child_bg = [ 50, 48, 47] # bg1 -popup_bg = [ 50, 48, 47] -border = [ 60, 56, 54] -frame_bg = [ 50, 48, 47] -frame_bg_hovered = [ 80, 80, 80] -frame_bg_active = [ 90, 90, 90] -title_bg = [ 40, 40, 40] -title_bg_active = [ 80, 80, 80] -menu_bar_bg = [ 40, 40, 40] -scrollbar_bg = [ 40, 40, 40] -scrollbar_grab = [ 80, 80, 80] -scrollbar_grab_hovered = [251, 241, 199] +border = [ 60, 56, 54] +bubble_ai = [ 35, 65, 45] +bubble_system = [ 25, 25, 25] +bubble_user = [ 30, 45, 75] +bubble_vendor = [ 65, 55, 30] +button = [ 60, 56, 54] +button_active = [200, 140, 0] +button_hovered = [180, 120, 40] +check_mark = [184, 187, 38] +child_bg = [ 50, 48, 47] +diff_added = [ 51, 230, 51] +diff_header = [ 77, 178, 255] +diff_removed = [230, 51, 51] +frame_bg = [ 50, 48, 47] +frame_bg_active = [ 90, 90, 90] +frame_bg_hovered = [ 80, 80, 80] +header = [ 60, 56, 54] +header_active = [251, 73, 52] +header_hovered = [180, 120, 40] +menu_bar_bg = [ 40, 40, 40] +popup_bg = [ 50, 48, 47] +scrollbar_bg = [ 40, 40, 40] +scrollbar_grab = [ 80, 80, 80] scrollbar_grab_active = [251, 241, 199] -button = [ 60, 56, 54] -button_hovered = [180, 120, 40] # orange -button_active = [200, 140, 0] # bright orange -header = [ 60, 56, 54] -header_hovered = [180, 120, 40] -header_active = [251, 73, 52] # red -separator = [ 80, 80, 80] -separator_hovered = [180, 120, 40] -separator_active = [251, 241, 199] -tab = [ 60, 56, 54] -tab_hovered = [180, 120, 40] -tab_selected = [ 80, 80, 80] -text = [251, 241, 199] # fg -text_disabled = [146, 131, 116] # comment -text_selected_bg = [180, 120, 40] -check_mark = [184, 187, 38] # green -slider_grab = [184, 187, 38] -slider_grab_active = [184, 187, 38] -table_header_bg = [ 60, 56, 54] \ No newline at end of file +scrollbar_grab_hovered = [251, 241, 199] +separator = [ 80, 80, 80] +separator_active = [251, 241, 199] +separator_hovered = [180, 120, 40] +slice_auto = [ 0, 255, 0] +slice_manual = [255, 165, 0] +slice_selection = [100, 100, 255] +slider_grab = [184, 187, 38] +slider_grab_active = [184, 187, 38] +status_error = [255, 72, 64] +status_info = [ 0, 255, 255] +status_success = [ 80, 255, 80] +status_warning = [255, 152, 48] +tab = [ 60, 56, 54] +tab_hovered = [180, 120, 40] +tab_selected = [ 80, 80, 80] +table_header_bg = [ 60, 56, 54] +text = [251, 241, 199] +text_disabled = [146, 131, 116] +text_selected_bg = [180, 120, 40] +title_bg = [ 40, 40, 40] +title_bg_active = [ 80, 80, 80] +window_bg = [ 40, 40, 40] diff --git a/themes/monokai.toml b/themes/monokai.toml index 6af4441a..7ae5dc1a 100644 --- a/themes/monokai.toml +++ b/themes/monokai.toml @@ -3,51 +3,65 @@ syntax_palette = "dark" description = "Monokai Theme" [colors] -window_bg = [ 39, 40, 34] -child_bg = [ 34, 35, 29] -popup_bg = [ 39, 40, 34] -border = [ 60, 61, 52] -border_shadow = [ 0, 0, 0] -frame_bg = [ 50, 51, 44] -frame_bg_hovered = [ 65, 67, 56] -frame_bg_active = [ 80, 82, 68] -title_bg = [ 39, 40, 34] -title_bg_active = [ 73, 72, 62] -title_bg_collapsed = [ 30, 31, 26] -menu_bar_bg = [ 50, 51, 44] -scrollbar_bg = [ 34, 35, 29] -scrollbar_grab = [ 80, 80, 72] -scrollbar_grab_hovered = [102, 217, 39] -scrollbar_grab_active = [166, 226, 46] -check_mark = [166, 226, 46] -slider_grab = [102, 217, 39] -slider_grab_active = [166, 226, 46] -button = [ 73, 72, 62] -button_hovered = [249, 38, 114] -button_active = [198, 30, 92] -header = [ 73, 72, 62] -header_hovered = [249, 38, 114] -header_active = [198, 30, 92] -separator = [ 60, 61, 52] -separator_hovered = [249, 38, 114] -separator_active = [166, 226, 46] -resize_grip = [ 73, 72, 62] -resize_grip_hovered = [249, 38, 114] -resize_grip_active = [166, 226, 46] -tab = [ 73, 72, 62] -tab_hovered = [249, 38, 114] -tab_selected = [249, 38, 114] -tab_dimmed = [ 50, 51, 44] -tab_dimmed_selected = [ 90, 88, 76] -docking_preview = [249, 38, 114] -docking_empty_bg = [ 20, 20, 18] -text = [248, 248, 242] -text_disabled = [117, 113, 94] -text_selected_bg = [249, 38, 114] -table_header_bg = [ 60, 61, 52] -table_border_strong = [ 73, 72, 62] -table_border_light = [ 55, 56, 48] -table_row_bg = [ 0, 0, 0] -table_row_bg_alt = [ 50, 51, 44] -nav_cursor = [166, 226, 46] -modal_window_dim_bg = [ 10, 10, 8] \ No newline at end of file +border = [ 60, 61, 52] +border_shadow = [ 0, 0, 0] +bubble_ai = [ 35, 65, 45] +bubble_system = [ 25, 25, 25] +bubble_user = [ 30, 45, 75] +bubble_vendor = [ 65, 55, 30] +button = [ 73, 72, 62] +button_active = [198, 30, 92] +button_hovered = [249, 38, 114] +check_mark = [166, 226, 46] +child_bg = [ 34, 35, 29] +diff_added = [ 51, 230, 51] +diff_header = [ 77, 178, 255] +diff_removed = [230, 51, 51] +docking_empty_bg = [ 20, 20, 18] +docking_preview = [249, 38, 114] +frame_bg = [ 50, 51, 44] +frame_bg_active = [ 80, 82, 68] +frame_bg_hovered = [ 65, 67, 56] +header = [ 73, 72, 62] +header_active = [198, 30, 92] +header_hovered = [249, 38, 114] +menu_bar_bg = [ 50, 51, 44] +modal_window_dim_bg = [ 10, 10, 8] +nav_cursor = [166, 226, 46] +popup_bg = [ 39, 40, 34] +resize_grip = [ 73, 72, 62] +resize_grip_active = [166, 226, 46] +resize_grip_hovered = [249, 38, 114] +scrollbar_bg = [ 34, 35, 29] +scrollbar_grab = [ 80, 80, 72] +scrollbar_grab_active = [166, 226, 46] +scrollbar_grab_hovered = [102, 217, 39] +separator = [ 60, 61, 52] +separator_active = [166, 226, 46] +separator_hovered = [249, 38, 114] +slice_auto = [ 0, 255, 0] +slice_manual = [255, 165, 0] +slice_selection = [100, 100, 255] +slider_grab = [102, 217, 39] +slider_grab_active = [166, 226, 46] +status_error = [255, 72, 64] +status_info = [ 0, 255, 255] +status_success = [ 80, 255, 80] +status_warning = [255, 152, 48] +tab = [ 73, 72, 62] +tab_dimmed = [ 50, 51, 44] +tab_dimmed_selected = [ 90, 88, 76] +tab_hovered = [249, 38, 114] +tab_selected = [249, 38, 114] +table_border_light = [ 55, 56, 48] +table_border_strong = [ 73, 72, 62] +table_header_bg = [ 60, 61, 52] +table_row_bg = [ 0, 0, 0] +table_row_bg_alt = [ 50, 51, 44] +text = [248, 248, 242] +text_disabled = [117, 113, 94] +text_selected_bg = [249, 38, 114] +title_bg = [ 39, 40, 34] +title_bg_active = [ 73, 72, 62] +title_bg_collapsed = [ 30, 31, 26] +window_bg = [ 39, 40, 34] diff --git a/themes/moss.toml b/themes/moss.toml index 5d43840a..3c574ffa 100644 --- a/themes/moss.toml +++ b/themes/moss.toml @@ -1,38 +1,52 @@ -# Moss — green-tinted dark theme +name = "moss" syntax_palette = "mariana" description = "Moss — green-tinted dark theme" [colors] -window_bg = [ 40, 47, 49] # green-gray -child_bg = [ 24, 32, 30] -popup_bg = [ 20, 35, 35] -border = [ 60, 80, 90] -frame_bg = [ 50, 70, 80] -frame_bg_hovered = [ 60, 90, 100] -frame_bg_active = [ 70, 100, 110] -title_bg = [ 40, 47, 49] -title_bg_active = [ 42, 77, 50] # mossy green -menu_bar_bg = [ 40, 47, 49] -scrollbar_bg = [ 40, 47, 49] -scrollbar_grab = [ 80, 80, 80] -scrollbar_grab_hovered = [100, 100, 100] +border = [ 60, 80, 90] +bubble_ai = [ 35, 65, 45] +bubble_system = [ 25, 25, 25] +bubble_user = [ 30, 45, 75] +bubble_vendor = [ 65, 55, 30] +button = [ 60, 80, 90] +button_active = [120, 80, 200] +button_hovered = [105, 101, 255] +check_mark = [120, 160, 130] +child_bg = [ 24, 32, 30] +diff_added = [ 51, 230, 51] +diff_header = [ 77, 178, 255] +diff_removed = [230, 51, 51] +frame_bg = [ 50, 70, 80] +frame_bg_active = [ 70, 100, 110] +frame_bg_hovered = [ 60, 90, 100] +header = [ 60, 80, 90] +header_active = [ 42, 77, 50] +header_hovered = [120, 160, 130] +menu_bar_bg = [ 40, 47, 49] +popup_bg = [ 20, 35, 35] +scrollbar_bg = [ 40, 47, 49] +scrollbar_grab = [ 80, 80, 80] scrollbar_grab_active = [120, 120, 120] -button = [ 60, 80, 90] -button_hovered = [105, 101, 255] # blue accent -button_active = [120, 80, 200] -header = [ 60, 80, 90] -header_hovered = [120, 160, 130] # green -header_active = [ 42, 77, 50] # mossy green -separator = [ 60, 80, 90] -separator_hovered = [120, 160, 130] -separator_active = [105, 101, 255] -tab = [ 60, 80, 90] -tab_hovered = [ 80, 100, 110] -tab_selected = [ 42, 77, 50] # mossy green -text = [255, 255, 255] -text_disabled = [208, 208, 208] -text_selected_bg = [105, 101, 255] -check_mark = [120, 160, 130] # green -slider_grab = [120, 160, 130] -slider_grab_active = [120, 160, 130] -table_header_bg = [ 50, 70, 80] \ No newline at end of file +scrollbar_grab_hovered = [100, 100, 100] +separator = [ 60, 80, 90] +separator_active = [105, 101, 255] +separator_hovered = [120, 160, 130] +slice_auto = [ 0, 255, 0] +slice_manual = [255, 165, 0] +slice_selection = [100, 100, 255] +slider_grab = [120, 160, 130] +slider_grab_active = [120, 160, 130] +status_error = [255, 72, 64] +status_info = [ 0, 255, 255] +status_success = [ 80, 255, 80] +status_warning = [255, 152, 48] +tab = [ 60, 80, 90] +tab_hovered = [ 80, 100, 110] +tab_selected = [ 42, 77, 50] +table_header_bg = [ 50, 70, 80] +text = [255, 255, 255] +text_disabled = [208, 208, 208] +text_selected_bg = [105, 101, 255] +title_bg = [ 40, 47, 49] +title_bg_active = [ 42, 77, 50] +window_bg = [ 40, 47, 49] diff --git a/themes/nord_dark.toml b/themes/nord_dark.toml index e19dbe3c..c634a7bd 100644 --- a/themes/nord_dark.toml +++ b/themes/nord_dark.toml @@ -3,51 +3,65 @@ syntax_palette = "dark" description = "Nord Dark Theme" [colors] -window_bg = [ 36, 41, 49] -child_bg = [ 30, 34, 42] -popup_bg = [ 36, 41, 49] -border = [ 59, 66, 82] -border_shadow = [ 0, 0, 0] -frame_bg = [ 46, 52, 64] -frame_bg_hovered = [ 59, 66, 82] -frame_bg_active = [ 67, 76, 94] -title_bg = [ 36, 41, 49] -title_bg_active = [ 59, 66, 82] -title_bg_collapsed = [ 30, 34, 42] -menu_bar_bg = [ 46, 52, 64] -scrollbar_bg = [ 30, 34, 42] -scrollbar_grab = [ 76, 86, 106] -scrollbar_grab_hovered = [ 94, 129, 172] -scrollbar_grab_active = [129, 161, 193] -check_mark = [136, 192, 208] -slider_grab = [ 94, 129, 172] -slider_grab_active = [129, 161, 193] -button = [ 59, 66, 82] -button_hovered = [ 94, 129, 172] -button_active = [129, 161, 193] -header = [ 59, 66, 82] -header_hovered = [ 94, 129, 172] -header_active = [129, 161, 193] -separator = [ 59, 66, 82] -separator_hovered = [ 94, 129, 172] -separator_active = [136, 192, 208] -resize_grip = [ 59, 66, 82] -resize_grip_hovered = [ 94, 129, 172] -resize_grip_active = [136, 192, 208] -tab = [ 46, 52, 64] -tab_hovered = [ 94, 129, 172] -tab_selected = [ 76, 86, 106] -tab_dimmed = [ 36, 41, 49] -tab_dimmed_selected = [ 59, 66, 82] -docking_preview = [ 94, 129, 172] -docking_empty_bg = [ 20, 22, 28] -text = [216, 222, 233] -text_disabled = [116, 128, 150] -text_selected_bg = [ 94, 129, 172] -table_header_bg = [ 59, 66, 82] -table_border_strong = [ 76, 86, 106] -table_border_light = [ 59, 66, 82] -table_row_bg = [ 0, 0, 0] -table_row_bg_alt = [ 46, 52, 64] -nav_cursor = [136, 192, 208] -modal_window_dim_bg = [ 10, 12, 16] \ No newline at end of file +border = [ 59, 66, 82] +border_shadow = [ 0, 0, 0] +bubble_ai = [ 35, 65, 45] +bubble_system = [ 25, 25, 25] +bubble_user = [ 30, 45, 75] +bubble_vendor = [ 65, 55, 30] +button = [ 59, 66, 82] +button_active = [129, 161, 193] +button_hovered = [ 94, 129, 172] +check_mark = [136, 192, 208] +child_bg = [ 30, 34, 42] +diff_added = [ 51, 230, 51] +diff_header = [ 77, 178, 255] +diff_removed = [230, 51, 51] +docking_empty_bg = [ 20, 22, 28] +docking_preview = [ 94, 129, 172] +frame_bg = [ 46, 52, 64] +frame_bg_active = [ 67, 76, 94] +frame_bg_hovered = [ 59, 66, 82] +header = [ 59, 66, 82] +header_active = [129, 161, 193] +header_hovered = [ 94, 129, 172] +menu_bar_bg = [ 46, 52, 64] +modal_window_dim_bg = [ 10, 12, 16] +nav_cursor = [136, 192, 208] +popup_bg = [ 36, 41, 49] +resize_grip = [ 59, 66, 82] +resize_grip_active = [136, 192, 208] +resize_grip_hovered = [ 94, 129, 172] +scrollbar_bg = [ 30, 34, 42] +scrollbar_grab = [ 76, 86, 106] +scrollbar_grab_active = [129, 161, 193] +scrollbar_grab_hovered = [ 94, 129, 172] +separator = [ 59, 66, 82] +separator_active = [136, 192, 208] +separator_hovered = [ 94, 129, 172] +slice_auto = [ 0, 255, 0] +slice_manual = [255, 165, 0] +slice_selection = [100, 100, 255] +slider_grab = [ 94, 129, 172] +slider_grab_active = [129, 161, 193] +status_error = [255, 72, 64] +status_info = [ 0, 255, 255] +status_success = [ 80, 255, 80] +status_warning = [255, 152, 48] +tab = [ 46, 52, 64] +tab_dimmed = [ 36, 41, 49] +tab_dimmed_selected = [ 59, 66, 82] +tab_hovered = [ 94, 129, 172] +tab_selected = [ 76, 86, 106] +table_border_light = [ 59, 66, 82] +table_border_strong = [ 76, 86, 106] +table_header_bg = [ 59, 66, 82] +table_row_bg = [ 0, 0, 0] +table_row_bg_alt = [ 46, 52, 64] +text = [216, 222, 233] +text_disabled = [116, 128, 150] +text_selected_bg = [ 94, 129, 172] +title_bg = [ 36, 41, 49] +title_bg_active = [ 59, 66, 82] +title_bg_collapsed = [ 30, 34, 42] +window_bg = [ 36, 41, 49] diff --git a/themes/solarized_dark.toml b/themes/solarized_dark.toml index dfc4dd14..613ca901 100644 --- a/themes/solarized_dark.toml +++ b/themes/solarized_dark.toml @@ -1,38 +1,52 @@ -# Solarized Dark — Ethan Schoonover's canonical dark palette +name = "solarized_dark" syntax_palette = "dark" description = "Solarized Dark by Ethan Schoonover" [colors] -window_bg = [ 0, 43, 54] # base03 -child_bg = [ 7, 54, 66] # base02 -popup_bg = [ 0, 43, 54] -border = [ 88, 110, 117] # base01 -frame_bg = [ 7, 54, 66] -frame_bg_hovered = [ 88, 110, 117] -frame_bg_active = [101, 123, 131] -title_bg = [ 7, 54, 66] -title_bg_active = [ 88, 110, 117] -menu_bar_bg = [ 0, 43, 54] -scrollbar_bg = [ 7, 54, 66] -scrollbar_grab = [ 88, 110, 117] -scrollbar_grab_hovered = [131, 148, 150] +border = [ 88, 110, 117] +bubble_ai = [ 35, 65, 45] +bubble_system = [ 25, 25, 25] +bubble_user = [ 30, 45, 75] +bubble_vendor = [ 65, 55, 30] +button = [ 7, 54, 66] +button_active = [ 38, 139, 210] +button_hovered = [ 38, 139, 210] +check_mark = [ 38, 139, 210] +child_bg = [ 7, 54, 66] +diff_added = [ 51, 230, 51] +diff_header = [ 77, 178, 255] +diff_removed = [230, 51, 51] +frame_bg = [ 7, 54, 66] +frame_bg_active = [101, 123, 131] +frame_bg_hovered = [ 88, 110, 117] +header = [ 7, 54, 66] +header_active = [ 38, 139, 210] +header_hovered = [ 38, 139, 210] +menu_bar_bg = [ 0, 43, 54] +popup_bg = [ 0, 43, 54] +scrollbar_bg = [ 7, 54, 66] +scrollbar_grab = [ 88, 110, 117] scrollbar_grab_active = [253, 246, 227] -button = [ 7, 54, 66] -button_hovered = [ 38, 139, 210] # blue -button_active = [ 38, 139, 210] -header = [ 7, 54, 66] -header_hovered = [ 38, 139, 210] -header_active = [ 38, 139, 210] -separator = [ 88, 110, 117] -separator_hovered = [ 38, 139, 210] -separator_active = [203, 75, 22] # orange -tab = [ 7, 54, 66] -tab_hovered = [ 38, 139, 210] -tab_selected = [ 88, 110, 117] -text = [147, 161, 161] # base1 -text_disabled = [ 88, 110, 117] # base01 -text_selected_bg = [ 38, 139, 210] -check_mark = [ 38, 139, 210] -slider_grab = [ 38, 139, 210] -slider_grab_active = [ 38, 139, 210] -table_header_bg = [ 7, 54, 66] \ No newline at end of file +scrollbar_grab_hovered = [131, 148, 150] +separator = [ 88, 110, 117] +separator_active = [203, 75, 22] +separator_hovered = [ 38, 139, 210] +slice_auto = [ 0, 255, 0] +slice_manual = [255, 165, 0] +slice_selection = [100, 100, 255] +slider_grab = [ 38, 139, 210] +slider_grab_active = [ 38, 139, 210] +status_error = [255, 72, 64] +status_info = [ 0, 255, 255] +status_success = [ 80, 255, 80] +status_warning = [255, 152, 48] +tab = [ 7, 54, 66] +tab_hovered = [ 38, 139, 210] +tab_selected = [ 88, 110, 117] +table_header_bg = [ 7, 54, 66] +text = [147, 161, 161] +text_disabled = [ 88, 110, 117] +text_selected_bg = [ 38, 139, 210] +title_bg = [ 7, 54, 66] +title_bg_active = [ 88, 110, 117] +window_bg = [ 0, 43, 54] diff --git a/themes/solarized_light.toml b/themes/solarized_light.toml index 3eb82133..fb8af26e 100644 --- a/themes/solarized_light.toml +++ b/themes/solarized_light.toml @@ -1,38 +1,52 @@ -# Solarized Light — Ethan Schoonover's canonical light palette +name = "solarized_light" syntax_palette = "light" description = "Solarized Light by Ethan Schoonover" [colors] -window_bg = [238, 232, 213] # base2 -child_bg = [253, 246, 227] # base3 -popup_bg = [253, 246, 227] -border = [147, 161, 161] # base1 -frame_bg = [253, 246, 227] -frame_bg_hovered = [238, 232, 213] -frame_bg_active = [238, 232, 213] -title_bg = [238, 232, 213] -title_bg_active = [147, 161, 161] -menu_bar_bg = [238, 232, 213] -scrollbar_bg = [238, 232, 213] -scrollbar_grab = [147, 161, 161] -scrollbar_grab_hovered = [131, 148, 150] +border = [147, 161, 161] +bubble_ai = [220, 255, 220] +bubble_system = [240, 240, 240] +bubble_user = [220, 230, 255] +bubble_vendor = [255, 240, 200] +button = [253, 246, 227] +button_active = [ 38, 139, 210] +button_hovered = [ 38, 139, 210] +check_mark = [ 38, 139, 210] +child_bg = [253, 246, 227] +diff_added = [ 40, 180, 40] +diff_header = [ 40, 100, 200] +diff_removed = [200, 40, 40] +frame_bg = [253, 246, 227] +frame_bg_active = [238, 232, 213] +frame_bg_hovered = [238, 232, 213] +header = [253, 246, 227] +header_active = [ 38, 139, 210] +header_hovered = [ 38, 139, 210] +menu_bar_bg = [238, 232, 213] +popup_bg = [253, 246, 227] +scrollbar_bg = [238, 232, 213] +scrollbar_grab = [147, 161, 161] scrollbar_grab_active = [ 7, 54, 66] -button = [253, 246, 227] -button_hovered = [ 38, 139, 210] # blue -button_active = [ 38, 139, 210] -header = [253, 246, 227] -header_hovered = [ 38, 139, 210] -header_active = [ 38, 139, 210] -separator = [147, 161, 161] -separator_hovered = [181, 137, 0] # yellow -separator_active = [203, 75, 22] # orange -tab = [238, 232, 213] -tab_hovered = [ 38, 139, 210] -tab_selected = [147, 161, 161] -text = [ 7, 54, 66] # base02 -text_disabled = [147, 161, 161] # base1 -text_selected_bg = [ 38, 139, 210] -check_mark = [ 38, 139, 210] -slider_grab = [ 38, 139, 210] -slider_grab_active = [ 38, 139, 210] -table_header_bg = [238, 232, 213] \ No newline at end of file +scrollbar_grab_hovered = [131, 148, 150] +separator = [147, 161, 161] +separator_active = [203, 75, 22] +separator_hovered = [181, 137, 0] +slice_auto = [ 80, 255, 80] +slice_manual = [255, 200, 0] +slice_selection = [180, 180, 255] +slider_grab = [ 38, 139, 210] +slider_grab_active = [ 38, 139, 210] +status_error = [200, 40, 40] +status_info = [ 40, 100, 200] +status_success = [ 40, 180, 40] +status_warning = [200, 140, 0] +tab = [238, 232, 213] +tab_hovered = [ 38, 139, 210] +tab_selected = [147, 161, 161] +table_header_bg = [238, 232, 213] +text = [ 7, 54, 66] +text_disabled = [147, 161, 161] +text_selected_bg = [ 38, 139, 210] +title_bg = [238, 232, 213] +title_bg_active = [147, 161, 161] +window_bg = [238, 232, 213]