Private
Public Access
0
0

style(themes): compact TOML formatting and lift semantic colors

This commit is contained in:
2026-06-05 00:02:46 -04:00
parent 06e305aba6
commit 7ea52cbbe8
16 changed files with 915 additions and 516 deletions
+28 -3
View File
@@ -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
+28 -28
View File
@@ -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
+35
View File
@@ -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)
+108
View File
@@ -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"))
+82
View File
@@ -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)
+104 -104
View File
@@ -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()
+101 -81
View File
@@ -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)
_crt_filter.render(width, height)
# ------------------------------------------------------------------ init
load_themes_from_disk()
+18 -1
View File
@@ -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] = {}
+48 -34
View File
@@ -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]
title_bg = [ 40, 35, 25]
title_bg_active = [ 60, 45, 15]
title_bg_collapsed = [ 30, 27, 20]
window_bg = [ 34, 32, 28]
+51 -37
View File
@@ -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]
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]
+47 -33
View File
@@ -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]
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]
+62 -48
View File
@@ -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]
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]
+47 -33
View File
@@ -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]
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]
+62 -48
View File
@@ -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]
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]
+47 -33
View File
@@ -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]
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]
+47 -33
View File
@@ -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]
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]