diff --git a/config.toml b/config.toml index de29cfd..cf002cd 100644 --- a/config.toml +++ b/config.toml @@ -1,6 +1,6 @@ [ai] -provider = "minimax" -model = "MiniMax-M2.5" +provider = "deepseek" +model = "deepseek-chat" temperature = 0.0 max_tokens = 24000 history_trunc_limit = 900000 @@ -9,6 +9,11 @@ system_prompt = "" [projects] paths = [ "C:/projects/gencpp/gencpp_sloppy.toml", + "C:\\projects\\manual_slop\\tests\\artifacts\\temp_livecontextsim.toml", + "C:\\projects\\manual_slop\\tests\\artifacts\\temp_liveaisettingssim.toml", + "C:\\projects\\manual_slop\\tests\\artifacts\\temp_livetoolssim.toml", + "C:\\projects\\manual_slop\\tests\\artifacts\\temp_liveexecutionsim.toml", + "C:\\projects\\manual_slop\\tests\\artifacts\\temp_project.toml", ] active = "C:/projects/gencpp/gencpp_sloppy.toml" @@ -16,6 +21,7 @@ active = "C:/projects/gencpp/gencpp_sloppy.toml" separate_message_panel = false separate_response_panel = false separate_tool_calls_panel = false +bg_shader_enabled = true [gui.show_windows] "Context Hub" = true @@ -28,12 +34,12 @@ separate_tool_calls_panel = false "Tier 4: QA" = true "Discussion Hub" = true "Operations Hub" = true -Message = true -Response = true -"Tool Calls" = true +Message = false +Response = false +"Tool Calls" = false Theme = true "Log Management" = true -Diagnostics = true +Diagnostics = false [theme] palette = "DPG Default" diff --git a/scripts/test_font_config.py b/scripts/test_font_config.py new file mode 100644 index 0000000..665ea40 --- /dev/null +++ b/scripts/test_font_config.py @@ -0,0 +1,12 @@ + +from imgui_bundle import imgui, hello_imgui + +def test_font_config(): + config = imgui.ImFontConfig() + config.oversample_h = 3 + config.oversample_v = 3 + print(f"Oversample H: {config.oversample_h}") + print(f"Oversample V: {config.oversample_v}") + +if __name__ == "__main__": + test_font_config() diff --git a/src/app_controller.py b/src/app_controller.py index d7324b1..1eaae58 100644 --- a/src/app_controller.py +++ b/src/app_controller.py @@ -324,7 +324,8 @@ class AppController: 'manual_approve': 'ui_manual_approve', 'inject_file_path': '_inject_file_path', 'inject_mode': '_inject_mode', - 'show_inject_modal': '_show_inject_modal' + 'show_inject_modal': '_show_inject_modal', + 'bg_shader_enabled': 'bg_shader_enabled' } self._gettable_fields = dict(self._settable_fields) self._gettable_fields.update({ @@ -346,7 +347,8 @@ class AppController: '_inject_file_path': '_inject_file_path', '_inject_mode': '_inject_mode', '_inject_preview': '_inject_preview', - '_show_inject_modal': '_show_inject_modal' + '_show_inject_modal': '_show_inject_modal', + 'bg_shader_enabled': 'bg_shader_enabled' }) self.perf_monitor = performance_monitor.get_monitor() self._perf_profiling_enabled = False @@ -783,6 +785,11 @@ class AppController: self.ui_summary_only = proj_meta.get("summary_only", False) self.ui_auto_add_history = disc_sec.get("auto_add", False) self.ui_global_system_prompt = self.config.get("ai", {}).get("system_prompt", "") + + gui_cfg = self.config.get("gui", {}) + from src import bg_shader + bg_shader.get_bg().enabled = gui_cfg.get("bg_shader_enabled", False) + _default_windows = { "Context Hub": True, "Files & Media": True, @@ -2077,12 +2084,15 @@ class AppController: } self.config["ai"]["system_prompt"] = self.ui_global_system_prompt self.config["projects"] = {"paths": self.project_paths, "active": self.active_project_path} + from src import bg_shader self.config["gui"] = { "show_windows": self.show_windows, "separate_message_panel": getattr(self, "ui_separate_message_panel", False), "separate_response_panel": getattr(self, "ui_separate_response_panel", False), "separate_tool_calls_panel": getattr(self, "ui_separate_tool_calls_panel", False), + "bg_shader_enabled": bg_shader.get_bg().enabled } + # Explicitly call theme save to ensure self.config is updated theme.save_to_config(self.config) def _do_generate(self) -> tuple[str, Path, list[dict[str, Any]], str, str]: diff --git a/src/bg_shader.py b/src/bg_shader.py new file mode 100644 index 0000000..2bd3df5 --- /dev/null +++ b/src/bg_shader.py @@ -0,0 +1,65 @@ +# src/bg_shader.py +import time +import math +from typing import Optional +import numpy as np +from imgui_bundle import imgui, nanovg as nvg, hello_imgui + +class BackgroundShader: + def __init__(self): + self.enabled = False + self.start_time = time.time() + self.ctx: Optional[nvg.Context] = None + + def render(self, width: float, height: float): + if not self.enabled: + return + + # In imgui-bundle, hello_imgui handles the background. + # We can use the background_draw_list to draw primitives. + # Since we don't have raw GLSL easily in Python without PyOpenGL, + # we'll use a "faux-shader" approach with NanoVG or DrawList gradients. + + t = time.time() - self.start_time + dl = imgui.get_background_draw_list() + + # Base deep sea color + dl.add_rect_filled(imgui.ImVec2(0, 0), imgui.ImVec2(width, height), imgui.get_color_u32(imgui.ImVec4(0.01, 0.07, 0.20, 1.0))) + + # Layer 1: Slow moving large blobs (FBM approximation) + for i in range(3): + phase = t * (0.1 + i * 0.05) + x = (math.sin(phase) * 0.5 + 0.5) * width + y = (math.cos(phase * 0.8) * 0.5 + 0.5) * height + radius = (0.4 + 0.2 * math.sin(t * 0.2)) * max(width, height) + + col = imgui.ImVec4(0.02, 0.26, 0.55, 0.3) + dl.add_circle_filled(imgui.ImVec2(x, y), radius, imgui.get_color_u32(col), num_segments=32) + + # Layer 2: Shimmering caustics (Animated Lines) + num_lines = 15 + for i in range(num_lines): + offset = (t * 20.0 + i * (width / num_lines)) % width + alpha = 0.1 * (1.0 + math.sin(t + i)) + col = imgui.get_color_u32(imgui.ImVec4(0.08, 0.60, 0.88, alpha)) + + p1 = imgui.ImVec2(offset, 0) + p2 = imgui.ImVec2(offset - 100, height) + dl.add_line(p1, p2, col, thickness=2.0) + + # Vignette + center = imgui.ImVec2(width/2, height/2) + radius = max(width, height) * 0.8 + # Draw multiple concentric circles for a soft vignette + for i in range(10): + r = radius + (i * 50) + alpha = (i / 10.0) * 0.5 + dl.add_circle(center, r, imgui.get_color_u32(imgui.ImVec4(0, 0, 0, alpha)), num_segments=64, thickness=60.0) + +_bg: Optional[BackgroundShader] = None + +def get_bg(): + global _bg + if _bg is None: + _bg = BackgroundShader() + return _bg diff --git a/src/gui_2.py b/src/gui_2.py index ddb158b..a039365 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -23,6 +23,7 @@ from src import models from src import app_controller from src import mcp_client from src import markdown_helper +from src import bg_shader import re from pydantic import BaseModel @@ -287,6 +288,12 @@ class App: imgui.end_menu() def _gui_func(self) -> None: + # Render background shader + bg = bg_shader.get_bg() + if bg.enabled: + ws = imgui.get_io().display_size + bg.render(ws.x, ws.y) + pushed_prior_tint = False if self.perf_profiling_enabled: self.perf_monitor.start_component("_gui_func") if self.is_viewing_prior_session: @@ -2780,6 +2787,8 @@ def hello(): for p in theme.get_palette_names(): if imgui.selectable(p, p == cp)[0]: theme.apply(p) + self._flush_to_config() + models.save_config(self.config) imgui.end_combo() imgui.separator() @@ -2815,7 +2824,33 @@ def hello(): imgui.separator() imgui.text("UI Scale (DPI)") ch, scale = imgui.slider_float("##scale", theme.get_current_scale(), 0.5, 3.0, "%.2f") - if ch: theme.set_scale(scale) + if ch: + theme.set_scale(scale) + self._flush_to_config() + models.save_config(self.config) + + imgui.text("Panel Transparency") + ch_t, trans = imgui.slider_float("##trans", theme.get_transparency(), 0.1, 1.0, "%.2f") + if ch_t: + theme.set_transparency(trans) + self._flush_to_config() + models.save_config(self.config) + + imgui.text("Panel Item Transparency") + ch_ct, ctrans = imgui.slider_float("##ctrans", theme.get_child_transparency(), 0.1, 1.0, "%.2f") + if ch_ct: + theme.set_child_transparency(ctrans) + self._flush_to_config() + models.save_config(self.config) + + imgui.separator() + bg = bg_shader.get_bg() + ch_bg, bg.enabled = imgui.checkbox("Animated Background Shader", bg.enabled) + if ch_bg: + gui_cfg = self.config.setdefault("gui", {}) + gui_cfg["bg_shader_enabled"] = bg.enabled + self._flush_to_config() + models.save_config(self.config) imgui.end() if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_theme_panel") @@ -2825,12 +2860,17 @@ def hello(): if assets_dir.exists(): hello_imgui.set_assets_folder(str(assets_dir.absolute())) + # Improved font rendering with oversampling + config = imgui.ImFontConfig() + config.oversample_h = 3 + config.oversample_v = 3 + font_path, font_size = theme.get_font_loading_params() if font_path: # Just try loading it directly; hello_imgui will look in the assets folder try: - self.main_font = hello_imgui.load_font_ttf_with_font_awesome_icons(font_path, font_size) + self.main_font = hello_imgui.load_font_ttf_with_font_awesome_icons(font_path, font_size, config) except Exception as e: print(f"Failed to load main font {font_path}: {e}") self.main_font = None @@ -2838,7 +2878,8 @@ def hello(): self.main_font = None try: - self.mono_font = hello_imgui.load_font("fonts/MapleMono-Regular.ttf", font_size) + params = hello_imgui.FontLoadingParams(font_config=config) + self.mono_font = hello_imgui.load_font("fonts/MapleMono-Regular.ttf", font_size, params) except Exception as e: print(f"Failed to load mono font: {e}") self.mono_font = None @@ -2862,7 +2903,13 @@ def hello(): self.runner_params.app_window_params.window_title = "manual slop" self.runner_params.app_window_params.window_geometry.size = (1680, 1200) self.runner_params.imgui_window_params.enable_viewports = getattr(self, "ui_multi_viewport", False) + self.runner_params.imgui_window_params.remember_theme = True + self.runner_params.imgui_window_params.tweaked_theme = theme.get_tweaked_theme() self.runner_params.imgui_window_params.default_imgui_window_type = hello_imgui.DefaultImGuiWindowType.provide_full_screen_dock_space + + # Enforce DPI Awareness and User Scale + user_scale = theme.get_current_scale() + self.runner_params.dpi_aware_params.dpi_window_size_factor = user_scale # Detect Monitor Refresh Rate for capping fps_cap = 60.0 @@ -2879,11 +2926,13 @@ def hello(): self.runner_params.fps_idling.fps_idle = fps_cap self.runner_params.imgui_window_params.show_menu_bar = True + self.runner_params.imgui_window_params.show_menu_view_themes = True self.runner_params.ini_folder_type = hello_imgui.IniFolderType.current_folder self.runner_params.ini_filename = "manualslop_layout.ini" self.runner_params.callbacks.show_gui = self._gui_func self.runner_params.callbacks.show_menus = self._show_menus self.runner_params.callbacks.load_additional_fonts = self._load_fonts + self.runner_params.callbacks.setup_imgui_style = theme.apply_current self.runner_params.callbacks.post_init = self._post_init self._fetch_models(self.current_provider) md_options = markdown_helper.get_renderer().options diff --git a/src/theme_2.py b/src/theme_2.py index 15706e3..91bc613 100644 --- a/src/theme_2.py +++ b/src/theme_2.py @@ -3,12 +3,13 @@ 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. +Palettes are applied via imgui.get_style().set_color_() calls or hello_imgui.apply_theme(). Font loading uses hello_imgui.load_font(). Scale uses imgui.get_style().font_scale_main. """ -from imgui_bundle import imgui +from imgui_bundle import imgui, hello_imgui +from typing import Any, Optional # ------------------------------------------------------------------ palettes @@ -173,23 +174,68 @@ _PALETTES: dict[str, dict[int, tuple]] = { imgui.Col_.nav_cursor: _c(166, 226, 46), imgui.Col_.modal_window_dim_bg: _c( 10, 10, 8, 100), }, + "Binks": { + imgui.Col_.text: _c( 0, 0, 0, 255), + imgui.Col_.text_disabled: _c(153, 153, 153, 255), + imgui.Col_.window_bg: _c(240, 240, 240, 240), + imgui.Col_.child_bg: _c( 0, 0, 0, 0), + imgui.Col_.popup_bg: _c(255, 255, 255, 240), + imgui.Col_.border: _c( 0, 0, 0, 99), + imgui.Col_.border_shadow: _c(255, 255, 255, 25), + imgui.Col_.frame_bg: _c(255, 255, 255, 240), + imgui.Col_.frame_bg_hovered: _c( 66, 150, 250, 102), + imgui.Col_.frame_bg_active: _c( 66, 150, 250, 171), + imgui.Col_.title_bg: _c(245, 245, 245, 255), + imgui.Col_.title_bg_collapsed: _c(255, 255, 255, 130), + imgui.Col_.title_bg_active: _c(209, 209, 209, 255), + imgui.Col_.menu_bar_bg: _c(219, 219, 219, 255), + imgui.Col_.scrollbar_bg: _c(250, 250, 250, 135), + imgui.Col_.scrollbar_grab: _c(176, 176, 176, 255), + imgui.Col_.scrollbar_grab_hovered: _c(150, 150, 150, 255), + imgui.Col_.scrollbar_grab_active: _c(125, 125, 125, 255), + imgui.Col_.check_mark: _c( 66, 150, 250, 255), + imgui.Col_.slider_grab: _c( 61, 133, 224, 255), + imgui.Col_.slider_grab_active: _c( 66, 150, 250, 255), + imgui.Col_.button: _c( 66, 150, 250, 102), + imgui.Col_.button_hovered: _c( 66, 150, 250, 255), + imgui.Col_.button_active: _c( 15, 135, 250, 255), + imgui.Col_.header: _c( 66, 150, 250, 79), + imgui.Col_.header_hovered: _c( 66, 150, 250, 204), + imgui.Col_.header_active: _c( 66, 150, 250, 255), + imgui.Col_.separator: _c(100, 100, 100, 255), + imgui.Col_.resize_grip: _c(255, 255, 255, 127), + imgui.Col_.resize_grip_hovered: _c( 66, 150, 250, 171), + imgui.Col_.resize_grip_active: _c( 66, 150, 250, 242), + imgui.Col_.plot_lines: _c( 99, 99, 99, 255), + imgui.Col_.plot_lines_hovered: _c(255, 110, 89, 255), + imgui.Col_.plot_histogram: _c(230, 178, 0, 255), + imgui.Col_.plot_histogram_hovered: _c(255, 153, 0, 255), + imgui.Col_.text_selected_bg: _c( 66, 150, 250, 89), + imgui.Col_.modal_window_dim_bg: _c( 51, 51, 51, 89), + }, } -PALETTE_NAMES: list[str] = list(_PALETTES.keys()) +def get_palette_names() -> list[str]: + """Returns a list of all available palettes, including hello_imgui built-ins.""" + names = list(_PALETTES.keys()) + # Add hello_imgui themes + hi_themes = [name for name in dir(hello_imgui.ImGuiTheme_) if not name.startswith('_') and name != 'count'] + # Filter out int methods that leaked into dir() if any + hi_themes = [n for n in hi_themes if not hasattr(int, n)] + names.extend(sorted(hi_themes)) + return names # ------------------------------------------------------------------ state -_current_palette: str = "ImGui Dark" -_current_font_path: str = "" +_current_palette: str = "10x Dark" +_current_font_path: str = "fonts/Inter-Regular.ttf" _current_font_size: float = 16.0 _current_scale: float = 1.0 -_custom_font: imgui.ImFont = None # type: ignore +_transparency: float = 1.0 +_child_transparency: float = 1.0 # ------------------------------------------------------------------ public API -def get_palette_names() -> list[str]: - return list(_PALETTES.keys()) - def get_current_palette() -> str: return _current_palette @@ -202,18 +248,49 @@ def get_current_font_size() -> float: 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) + +def get_child_transparency() -> float: + return _child_transparency + +def set_child_transparency(val: float) -> None: + global _child_transparency + _child_transparency = val + apply(_current_palette) + def apply(palette_name: str) -> None: """ Apply a named palette by setting all ImGui style colors and applying global professional styling. - Call this once per frame if you want dynamic switching, or once at startup. - In practice we call it once when the user picks a palette, and imgui retains the style. """ global _current_palette _current_palette = palette_name - colours = _PALETTES.get(palette_name, {}) - style = imgui.get_style() + + # 1. Apply base colors + if palette_name in _PALETTES: + colours = _PALETTES[palette_name] + imgui.style_colors_dark() + style = imgui.get_style() + for col_enum, rgba in colours.items(): + style.set_color_(col_enum, imgui.ImVec4(*rgba)) + elif hasattr(hello_imgui.ImGuiTheme_, palette_name): + theme_enum = getattr(hello_imgui.ImGuiTheme_, palette_name) + hello_imgui.apply_theme(theme_enum) + else: + # Fallback to Nord Dark if requested but not found, otherwise ImGui Dark + if palette_name == "Nord Dark": + # This should not happen since it's in _PALETTES, but for safety + imgui.style_colors_dark() + else: + imgui.style_colors_dark() - # Subtle Rounding Professional Theme + # 2. Apply our "Subtle Rounding" professional tweaks on top of ANY theme + style = imgui.get_style() style.window_rounding = 6.0 style.child_rounding = 4.0 style.frame_rounding = 4.0 @@ -225,6 +302,17 @@ 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) @@ -237,16 +325,6 @@ def apply(palette_name: str) -> None: style.anti_aliased_fill = True style.anti_aliased_lines_use_tex = True - if not colours: - # Reset to imgui dark defaults - imgui.style_colors_dark() - return - - # Start from dark defaults so unlisted keys have sensible values - imgui.style_colors_dark() - for col_enum, rgba in colours.items(): - style.set_color_(col_enum, imgui.ImVec4(*rgba)) - def set_scale(factor: float) -> None: """Set the global font/UI scale factor.""" global _current_scale @@ -261,17 +339,22 @@ def save_to_config(config: dict) -> None: 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] from config and apply palette + scale. Font is handled separately at startup.""" - global _current_font_path, _current_font_size, _current_scale, _current_palette + """Read [theme] from config. Font is handled separately at startup.""" + global _current_font_path, _current_font_size, _current_scale, _current_palette, _transparency, _child_transparency t = config.get("theme", {}) - _current_palette = t.get("palette", "ImGui Dark") + _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)) - # Don't apply here — imgui context may not exist yet. - # Call apply_current() after imgui is initialised. + _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.""" @@ -281,3 +364,15 @@ def apply_current() -> None: 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