# theme_2.py """ Theming support for manual_slop GUI — imgui-bundle port. Replaces theme.py (DearPyGui-specific) with imgui-bundle equivalents. Palettes are applied via imgui.get_style().set_color_() calls. Font loading uses hello_imgui.load_font(). Scale uses imgui.get_style().font_scale_main. """ from __future__ import annotations from imgui_bundle import imgui, hello_imgui from src import imgui_scopes as imscope # ------------------------------------------------------------------ palettes # 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. 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.""" return (r / 255.0, g / 255.0, b / 255.0, a / 255.0) # Semantic Colors SUCCESS_GREEN = _c(100, 255, 100) ERROR_RED = _c(255, 100, 100) WARNING_GOLD = _c(255, 220, 100) INFO_BLUE = _c(100, 200, 255) DIM_GRAY = _c(180, 180, 180) DIM_SYSTEM = _c(120, 120, 100) C_OUT = _c(100, 200, 255) C_IN = _c(140, 255, 160) C_REQ = _c(255, 220, 100) C_RES = _c(180, 255, 180) C_TC = _c(255, 180, 80) C_TR = _c(180, 220, 255) C_TRS = _c(200, 180, 255) C_LBL = _c(180, 180, 180) C_VAL = _c(220, 220, 220) C_KEY = _c(140, 200, 255) C_NUM = _c(180, 255, 180) C_TRM = _c(160, 160, 150) # Trimmed/Cruft C_SUB = _c(220, 200, 120) _PALETTES: dict[str, dict[int, tuple]] = { "ImGui Dark": {}, # empty = use imgui dark defaults "10x Dark": { imgui.Col_.window_bg: _c( 34, 32, 28), imgui.Col_.child_bg: _c( 30, 28, 24), imgui.Col_.popup_bg: _c( 35, 30, 20), imgui.Col_.border: _c( 60, 55, 50), imgui.Col_.border_shadow: _c( 0, 0, 0, 0), imgui.Col_.frame_bg: _c( 45, 42, 38), imgui.Col_.frame_bg_hovered: _c( 60, 56, 50), imgui.Col_.frame_bg_active: _c( 75, 70, 62), imgui.Col_.title_bg: _c( 40, 35, 25), imgui.Col_.title_bg_active: _c( 60, 45, 15), imgui.Col_.title_bg_collapsed: _c( 30, 27, 20), imgui.Col_.menu_bar_bg: _c( 35, 30, 20), imgui.Col_.scrollbar_bg: _c( 30, 28, 24), imgui.Col_.scrollbar_grab: _c( 80, 78, 72), imgui.Col_.scrollbar_grab_hovered: _c(100, 100, 92), imgui.Col_.scrollbar_grab_active: _c(120, 118, 110), imgui.Col_.check_mark: _c(194, 164, 74), imgui.Col_.slider_grab: _c(126, 78, 14), imgui.Col_.slider_grab_active: _c(194, 140, 30), imgui.Col_.button: _c( 83, 76, 60), imgui.Col_.button_hovered: _c(126, 78, 14), imgui.Col_.button_active: _c(115, 90, 70), imgui.Col_.header: _c( 83, 76, 60), imgui.Col_.header_hovered: _c(126, 78, 14), imgui.Col_.header_active: _c(115, 90, 70), imgui.Col_.separator: _c( 70, 65, 55), imgui.Col_.separator_hovered: _c(126, 78, 14), imgui.Col_.separator_active: _c(194, 164, 74), imgui.Col_.resize_grip: _c( 60, 55, 44), imgui.Col_.resize_grip_hovered: _c(126, 78, 14), imgui.Col_.resize_grip_active: _c(194, 164, 74), imgui.Col_.tab: _c( 83, 83, 70), imgui.Col_.tab_hovered: _c(126, 77, 25), imgui.Col_.tab_selected: _c(126, 77, 25), imgui.Col_.tab_dimmed: _c( 60, 58, 50), imgui.Col_.tab_dimmed_selected: _c( 90, 80, 55), imgui.Col_.docking_preview: _c(126, 78, 14, 180), imgui.Col_.docking_empty_bg: _c( 20, 20, 20), imgui.Col_.text: _c(200, 200, 200), imgui.Col_.text_disabled: _c(130, 130, 120), imgui.Col_.text_selected_bg: _c( 59, 86, 142, 180), imgui.Col_.table_header_bg: _c( 55, 50, 38), imgui.Col_.table_border_strong: _c( 70, 65, 55), imgui.Col_.table_border_light: _c( 50, 47, 42), imgui.Col_.table_row_bg: _c( 0, 0, 0, 0), imgui.Col_.table_row_bg_alt: _c( 40, 38, 34, 40), imgui.Col_.nav_cursor: _c(126, 78, 14), imgui.Col_.nav_windowing_highlight: _c(194, 164, 74, 180), imgui.Col_.nav_windowing_dim_bg: _c( 20, 20, 20, 80), imgui.Col_.modal_window_dim_bg: _c( 10, 10, 10, 100), }, "Nord Dark": { imgui.Col_.window_bg: _c( 36, 41, 49), imgui.Col_.child_bg: _c( 30, 34, 42), imgui.Col_.popup_bg: _c( 36, 41, 49), imgui.Col_.border: _c( 59, 66, 82), imgui.Col_.border_shadow: _c( 0, 0, 0, 0), imgui.Col_.frame_bg: _c( 46, 52, 64), imgui.Col_.frame_bg_hovered: _c( 59, 66, 82), imgui.Col_.frame_bg_active: _c( 67, 76, 94), imgui.Col_.title_bg: _c( 36, 41, 49), imgui.Col_.title_bg_active: _c( 59, 66, 82), imgui.Col_.title_bg_collapsed: _c( 30, 34, 42), imgui.Col_.menu_bar_bg: _c( 46, 52, 64), imgui.Col_.scrollbar_bg: _c( 30, 34, 42), imgui.Col_.scrollbar_grab: _c( 76, 86, 106), imgui.Col_.scrollbar_grab_hovered: _c( 94, 129, 172), imgui.Col_.scrollbar_grab_active: _c(129, 161, 193), imgui.Col_.check_mark: _c(136, 192, 208), imgui.Col_.slider_grab: _c( 94, 129, 172), imgui.Col_.slider_grab_active: _c(129, 161, 193), imgui.Col_.button: _c( 59, 66, 82), imgui.Col_.button_hovered: _c( 94, 129, 172), imgui.Col_.button_active: _c(129, 161, 193), imgui.Col_.header: _c( 59, 66, 82), imgui.Col_.header_hovered: _c( 94, 129, 172), imgui.Col_.header_active: _c(129, 161, 193), imgui.Col_.separator: _c( 59, 66, 82), imgui.Col_.separator_hovered: _c( 94, 129, 172), imgui.Col_.separator_active: _c(136, 192, 208), imgui.Col_.resize_grip: _c( 59, 66, 82), imgui.Col_.resize_grip_hovered: _c( 94, 129, 172), imgui.Col_.resize_grip_active: _c(136, 192, 208), imgui.Col_.tab: _c( 46, 52, 64), imgui.Col_.tab_hovered: _c( 94, 129, 172), imgui.Col_.tab_selected: _c( 76, 86, 106), imgui.Col_.tab_dimmed: _c( 36, 41, 49), imgui.Col_.tab_dimmed_selected: _c( 59, 66, 82), imgui.Col_.docking_preview: _c( 94, 129, 172, 180), imgui.Col_.docking_empty_bg: _c( 20, 22, 28), imgui.Col_.text: _c(216, 222, 233), imgui.Col_.text_disabled: _c(116, 128, 150), imgui.Col_.text_selected_bg: _c( 94, 129, 172, 180), imgui.Col_.table_header_bg: _c( 59, 66, 82), imgui.Col_.table_border_strong: _c( 76, 86, 106), imgui.Col_.table_border_light: _c( 59, 66, 82), imgui.Col_.table_row_bg: _c( 0, 0, 0, 0), imgui.Col_.table_row_bg_alt: _c( 46, 52, 64, 40), imgui.Col_.nav_cursor: _c(136, 192, 208), imgui.Col_.modal_window_dim_bg: _c( 10, 12, 16, 100), }, "Monokai": { imgui.Col_.window_bg: _c( 39, 40, 34), imgui.Col_.child_bg: _c( 34, 35, 29), imgui.Col_.popup_bg: _c( 39, 40, 34), imgui.Col_.border: _c( 60, 61, 52), imgui.Col_.border_shadow: _c( 0, 0, 0, 0), imgui.Col_.frame_bg: _c( 50, 51, 44), imgui.Col_.frame_bg_hovered: _c( 65, 67, 56), imgui.Col_.frame_bg_active: _c( 80, 82, 68), imgui.Col_.title_bg: _c( 39, 40, 34), imgui.Col_.title_bg_active: _c( 73, 72, 62), imgui.Col_.title_bg_collapsed: _c( 30, 31, 26), imgui.Col_.menu_bar_bg: _c( 50, 51, 44), imgui.Col_.scrollbar_bg: _c( 34, 35, 29), imgui.Col_.scrollbar_grab: _c( 80, 80, 72), imgui.Col_.scrollbar_grab_hovered: _c(102, 217, 39), imgui.Col_.scrollbar_grab_active: _c(166, 226, 46), imgui.Col_.check_mark: _c(166, 226, 46), imgui.Col_.slider_grab: _c(102, 217, 39), imgui.Col_.slider_grab_active: _c(166, 226, 46), imgui.Col_.button: _c( 73, 72, 62), imgui.Col_.button_hovered: _c(249, 38, 114), imgui.Col_.button_active: _c(198, 30, 92), imgui.Col_.header: _c( 73, 72, 62), imgui.Col_.header_hovered: _c(249, 38, 114), imgui.Col_.header_active: _c(198, 30, 92), imgui.Col_.separator: _c( 60, 61, 52), imgui.Col_.separator_hovered: _c(249, 38, 114), imgui.Col_.separator_active: _c(166, 226, 46), imgui.Col_.resize_grip: _c( 73, 72, 62), imgui.Col_.resize_grip_hovered: _c(249, 38, 114), imgui.Col_.resize_grip_active: _c(166, 226, 46), imgui.Col_.tab: _c( 73, 72, 62), imgui.Col_.tab_hovered: _c(249, 38, 114), imgui.Col_.tab_selected: _c(249, 38, 114), imgui.Col_.tab_dimmed: _c( 50, 51, 44), imgui.Col_.tab_dimmed_selected: _c( 90, 88, 76), imgui.Col_.docking_preview: _c(249, 38, 114, 180), imgui.Col_.docking_empty_bg: _c( 20, 20, 18), imgui.Col_.text: _c(248, 248, 242), imgui.Col_.text_disabled: _c(117, 113, 94), imgui.Col_.text_selected_bg: _c(249, 38, 114, 150), imgui.Col_.table_header_bg: _c( 60, 61, 52), imgui.Col_.table_border_strong: _c( 73, 72, 62), imgui.Col_.table_border_light: _c( 55, 56, 48), imgui.Col_.table_row_bg: _c( 0, 0, 0, 0), imgui.Col_.table_row_bg_alt: _c( 50, 51, 44, 40), imgui.Col_.nav_cursor: _c(166, 226, 46), imgui.Col_.modal_window_dim_bg: _c( 10, 10, 8, 100), }, } PALETTE_NAMES: list[str] = list(_PALETTES.keys()) # ------------------------------------------------------------------ state _current_palette_name: str = "10x Dark" _current_font_path: str = "" _current_font_size: float = 16.0 _current_scale: float = 1.0 _transparency: float = 1.0 _child_transparency: float = 1.0 _custom_font: imgui.ImFont = None # type: ignore # ------------------------------------------------------------------ public API def get_palette_names() -> list[str]: return list(_PALETTES.keys()) def get_current_palette() -> str: return _current_palette_name def get_current_font_path() -> str: return _current_font_path def get_current_font_size() -> float: return _current_font_size def get_current_scale() -> float: return _current_scale def get_transparency() -> float: return _transparency def set_transparency(val: float) -> None: global _transparency _transparency = val apply(_current_palette_name) def get_child_transparency() -> float: return _child_transparency def set_child_transparency(val: float) -> None: global _child_transparency _child_transparency = val apply(_current_palette_name) def apply(palette_name: str) -> None: """ Apply a named palette by setting all ImGui style colors. """ global _current_palette_name _current_palette_name = palette_name colours = _PALETTES.get(palette_name, {}) if not colours: # Reset to imgui dark defaults imgui.style_colors_dark() return style = imgui.get_style() # Start from dark defaults so unlisted keys have sensible values imgui.style_colors_dark() for col_enum, rgba in colours.items(): col = imgui.ImVec4(*rgba) # Apply global transparency overrides if col_enum == imgui.Col_.window_bg: col.w *= _transparency if col_enum == imgui.Col_.child_bg: col.w *= _child_transparency style.set_color_(col_enum, col) 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 save_to_config(config: dict) -> None: """Persist theme settings into the config dict.""" config.setdefault("theme", {}) config["theme"]["palette"] = _current_palette_name config["theme"]["font_path"] = _current_font_path config["theme"]["font_size"] = _current_font_size config["theme"]["scale"] = _current_scale config["theme"]["transparency"] = _transparency config["theme"]["child_transparency"] = _child_transparency def load_from_config(config: dict) -> None: """Read theme settings from config.""" global _current_palette_name, _current_font_path, _current_font_size, _current_scale, _transparency, _child_transparency t = config.get("theme", {}) _current_palette_name = t.get("palette", "10x Dark") _current_font_path = t.get("font_path", "") _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)) def apply_current() -> None: """Apply the loaded palette and scale. Call after imgui context exists.""" apply(_current_palette_name) set_scale(_current_scale) 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() # Since custom palettes like '10x Dark' are not in hello_imgui enum, # we always use dark as base and apply our specific colors in apply() tt.theme = hello_imgui.ImGuiTheme_.imgui_colors_dark tt.tweaks.rounding = 6.0 return tt def ai_text_color() -> imgui.ImVec4: return imgui.ImVec4(0.8, 0.9, 0.8, 1.0) 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.""" # Slightly more opaque and distinct tints for role-based structure if role == "User": return imgui.ImVec4(0.12, 0.18, 0.30, 0.6) # Deep Blue elif role == "AI": return imgui.ImVec4(0.14, 0.25, 0.18, 0.6) # Deep Green elif role == "Vendor API": return imgui.ImVec4(0.25, 0.22, 0.12, 0.5) # Earthy Gold return imgui.ImVec4(0.1, 0.1, 0.1, 0.4) # Dim System def is_nerv_active() -> bool: return _current_palette_name == "Nerv" from src.theme_nerv_fx import AlertPulsing, CRTFilter _alert_pulsing = AlertPulsing() _crt_filter = CRTFilter() 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)