From 5b196dccf081f0a6fe918b1c2442a13f2ea60f13 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 13 Mar 2026 15:33:40 -0400 Subject: [PATCH] checkpoint frosted glass --- .../tracks/frosted_glass_20260313/plan.md | 20 +++--- src/gui_2.py | 59 +++++++++------ src/shader_manager.py | 71 +++++++++++++------ src/theme_2.py | 12 +++- tests/test_theme.py | 67 ++++++++++------- 5 files changed, 150 insertions(+), 79 deletions(-) diff --git a/conductor/tracks/frosted_glass_20260313/plan.md b/conductor/tracks/frosted_glass_20260313/plan.md index 3ddaa80..a5f6d10 100644 --- a/conductor/tracks/frosted_glass_20260313/plan.md +++ b/conductor/tracks/frosted_glass_20260313/plan.md @@ -13,14 +13,14 @@ - [x] Task: Implement: Ensure the blurred texture is updated every frame or on window move events. [f297e7a] - [x] Task: Conductor - User Manual Verification 'Phase 2: Framebuffer Capture Pipeline' (Protocol in workflow.md) [e9b7875] -## Phase 3: GUI Integration & Rendering -- [ ] Task: Write Tests: Verify that a mocked ImGui window successfully calls the frosted glass rendering logic. -- [ ] Task: Implement: Create a `_render_frosted_background(self, pos, size)` helper in `src/gui_2.py`. -- [ ] Task: Implement: Update panel rendering loops (e.g. `_gui_func`) to inject the frosted background before calling `imgui.begin()` for major panels. -- [ ] Task: Conductor - User Manual Verification 'Phase 3: GUI Integration & Rendering' (Protocol in workflow.md) +## Phase 3: GUI Integration & Rendering [checkpoint: cecbe22] +- [x] Task: Write Tests: Verify that a mocked ImGui window successfully calls the frosted glass rendering logic. [cecbe22] +- [x] Task: Implement: Create a `_render_frosted_background(self, pos, size)` helper in `src/gui_2.py`. [cecbe22] +- [x] Task: Implement: Update panel rendering loops (e.g. `_gui_func`) to inject the frosted background before calling `imgui.begin()` for major panels. [cecbe22] +- [x] Task: Conductor - User Manual Verification 'Phase 3: GUI Integration & Rendering' (Protocol in workflow.md) [cecbe22] -## Phase 4: UI Controls & Configuration -- [ ] Task: Write Tests: Verify that modifying blur uniforms via the Live Editor updates the shader state. -- [ ] Task: Implement: Add "Frosted Glass" sliders (Blur, Tint, Opacity) to the **Shader Editor** in `src/gui_2.py`. -- [ ] Task: Implement: Update `src/theme.py` to parse and store frosted glass settings from `config.toml`. -- [ ] Task: Conductor - User Manual Verification 'Phase 4: UI Controls & Configuration' (Protocol in workflow.md) +## Phase 4: UI Controls & Configuration [checkpoint: cecbe22] +- [x] Task: Write Tests: Verify that modifying blur uniforms via the Live Editor updates the shader state. [cecbe22] +- [x] Task: Implement: Add "Frosted Glass" sliders (Blur, Tint, Opacity) to the **Shader Editor** in `src/gui_2.py`. [cecbe22] +- [x] Task: Implement: Update `src/theme.py` to parse and store frosted glass settings from `config.toml`. [cecbe22] +- [x] Task: Conductor - User Manual Verification 'Phase 4: UI Controls & Configuration' (Protocol in workflow.md) [cecbe22] diff --git a/src/gui_2.py b/src/gui_2.py index 1930c41..c980d50 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -353,30 +353,18 @@ class App: imgui.pop_id() def _render_frosted_background(self, pos: imgui.ImVec2, size: imgui.ImVec2) -> None: + if not theme.get_frosted_glass_enabled(): return if size.x <= 0 or size.y <= 0: return - if self.shader_manager.fbo_width != int(size.x) or self.shader_manager.fbo_height != int(size.y): - self.shader_manager.setup_capture_fbo(int(size.x), int(size.y)) display_size = imgui.get_io().display_size - gl.glBindTexture(gl.GL_TEXTURE_2D, self.shader_manager.capture_tex) - gl_y = int(display_size.y - pos.y - size.y) - gl.glCopyTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, int(pos.x), gl_y, int(size.x), int(size.y), 0) - gl.glBindTexture(gl.GL_TEXTURE_2D, 0) - self.shader_manager.capture_begin(int(size.x), int(size.y)) - self.shader_manager.render_blur( - self.shader_manager.capture_tex, - int(size.x), - int(size.y), - self.shader_uniforms['frosted_blur_radius'], - self.shader_uniforms['frosted_tint_intensity'], - self.shader_uniforms['frosted_opacity'] - ) - self.shader_manager.capture_end() - imgui.get_background_draw_list().add_image( - self.shader_manager.blur_tex, + uv_min = imgui.ImVec2(pos.x / display_size.x, 1.0 - pos.y / display_size.y) + uv_max = imgui.ImVec2((pos.x + size.x) / display_size.x, 1.0 - (pos.y + size.y) / display_size.y) + imgui.get_window_draw_list().add_image( + imgui.ImTextureRef(self.shader_manager.blur_tex), pos, - imgui.ImVec2(pos.x + size.x, pos.y + size.y) + imgui.ImVec2(pos.x + size.x, pos.y + size.y), + uv_min, + uv_max ) - def _render_operations_hub(self) -> None: exp, opened = imgui.begin("Operations Hub", self.show_windows["Operations Hub"]) self.show_windows["Operations Hub"] = bool(opened) @@ -558,6 +546,15 @@ class App: self._nerv_alert.render(ws.x, ws.y) self._nerv_crt.enabled = self.ui_crt_filter self._nerv_crt.render(ws.x, ws.y) + + if theme.get_frosted_glass_enabled(): + ws = imgui.get_io().display_size + self.shader_manager.prepare_global_blur( + int(ws.x), int(ws.y), + self.shader_uniforms['frosted_blur_radius'], + self.shader_uniforms['frosted_tint_intensity'], + self.shader_uniforms['frosted_opacity'] + ) if self.perf_profiling_enabled: self.perf_monitor.start_component("_gui_func") if self.is_viewing_prior_session: imgui.push_style_color(imgui.Col_.window_bg, vec4(50, 40, 20)) @@ -633,6 +630,7 @@ class App: exp, opened = imgui.begin("Context Hub", self.show_windows["Context Hub"]) self.show_windows["Context Hub"] = bool(opened) if exp: + self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) if imgui.begin_tab_bar('context_hub_tabs'): if imgui.begin_tab_item('Projects')[0]: self._render_projects_panel() @@ -646,6 +644,7 @@ class App: exp, opened = imgui.begin("Files & Media", self.show_windows["Files & Media"]) self.show_windows["Files & Media"] = bool(opened) if exp: + self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) if imgui.collapsing_header("Files"): self._render_files_panel() if imgui.collapsing_header("Screenshots"): @@ -655,6 +654,7 @@ class App: exp, opened = imgui.begin("AI Settings", self.show_windows["AI Settings"]) self.show_windows["AI Settings"] = bool(opened) if exp: + self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_persona_selector_panel() if imgui.collapsing_header("Provider & Model"): self._render_provider_panel() @@ -667,12 +667,14 @@ class App: exp, opened = imgui.begin("Usage Analytics", self.show_windows["Usage Analytics"]) self.show_windows["Usage Analytics"] = bool(opened) if exp: + self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_usage_analytics_panel() imgui.end() if self.show_windows.get("MMA Dashboard", False): exp, opened = imgui.begin("MMA Dashboard", self.show_windows["MMA Dashboard"]) self.show_windows["MMA Dashboard"] = bool(opened) if exp: + self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_mma_dashboard") self._render_mma_dashboard() if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_mma_dashboard") @@ -682,30 +684,35 @@ class App: exp, opened = imgui.begin("Task DAG", self.show_windows["Task DAG"]) self.show_windows["Task DAG"] = bool(opened) if exp: + self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_task_dag_panel() imgui.end() if self.ui_separate_tier1 and self.show_windows.get("Tier 1: Strategy", False): exp, opened = imgui.begin("Tier 1: Strategy", self.show_windows["Tier 1: Strategy"]) self.show_windows["Tier 1: Strategy"] = bool(opened) if exp: + self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_tier_stream_panel("Tier 1", "Tier 1") imgui.end() if self.ui_separate_tier2 and self.show_windows.get("Tier 2: Tech Lead", False): exp, opened = imgui.begin("Tier 2: Tech Lead", self.show_windows["Tier 2: Tech Lead"]) self.show_windows["Tier 2: Tech Lead"] = bool(opened) if exp: + self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_tier_stream_panel("Tier 2", "Tier 2 (Tech Lead)") imgui.end() if self.ui_separate_tier3 and self.show_windows.get("Tier 3: Workers", False): exp, opened = imgui.begin("Tier 3: Workers", self.show_windows["Tier 3: Workers"]) self.show_windows["Tier 3: Workers"] = bool(opened) if exp: + self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_tier_stream_panel("Tier 3", None) imgui.end() if self.ui_separate_tier4 and self.show_windows.get("Tier 4: QA", False): exp, opened = imgui.begin("Tier 4: QA", self.show_windows["Tier 4: QA"]) self.show_windows["Tier 4: QA"] = bool(opened) if exp: + self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_tier_stream_panel("Tier 4", "Tier 4 (QA)") imgui.end() if self.show_windows.get("Theme", False): @@ -714,6 +721,7 @@ class App: exp, opened = imgui.begin("Discussion Hub", self.show_windows["Discussion Hub"]) self.show_windows["Discussion Hub"] = bool(opened) if exp: + self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) # Top part for the history imgui.begin_child("HistoryChild", size=(0, -self.ui_discussion_split_h)) if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_discussion_panel") @@ -766,6 +774,7 @@ class App: exp, opened = imgui.begin("Message", self.show_windows["Message"]) self.show_windows["Message"] = bool(opened) if exp: + self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_message_panel() imgui.end() @@ -773,6 +782,7 @@ class App: exp, opened = imgui.begin("Response", self.show_windows["Response"]) self.show_windows["Response"] = bool(opened) if exp: + self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_response_panel() imgui.end() @@ -780,6 +790,7 @@ class App: exp, opened = imgui.begin("Tool Calls", self.show_windows["Tool Calls"]) self.show_windows["Tool Calls"] = bool(opened) if exp: + self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_tool_calls_panel() imgui.end() @@ -787,6 +798,7 @@ class App: exp, opened = imgui.begin('External Tools', self.show_windows['External Tools']) self.show_windows['External Tools'] = bool(opened) if exp: + self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) self._render_external_tools_panel() imgui.end() @@ -979,6 +991,7 @@ class App: expanded, opened = imgui.begin("Last Script Output", self.show_script_output) self.show_script_output = bool(opened) if expanded: + self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) imgui.text("Script:") imgui.same_line() self._render_text_viewer("Last Script", self.ui_last_script_text) @@ -1010,6 +1023,7 @@ class App: expanded, opened = imgui.begin(f"Text Viewer - {self.text_viewer_title}", self.show_text_viewer) self.show_text_viewer = bool(opened) if expanded: + self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) if self.ui_word_wrap: imgui.begin_child("tv_wrap", imgui.ImVec2(-1, -1), False) imgui.push_text_wrap_pos(imgui.get_content_region_avail().x) @@ -3926,6 +3940,7 @@ def hello(): exp, opened = imgui.begin("Theme", self.show_windows["Theme"]) self.show_windows["Theme"] = bool(opened) if exp: + self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size()) imgui.text("Palette") cp = theme.get_current_palette() if imgui.begin_combo("##pal", cp): @@ -3999,6 +4014,10 @@ def hello(): gui_cfg["crt_filter_enabled"] = self.ui_crt_filter self._flush_to_config() models.save_config(self.config) + + ch_fg, fg_val = imgui.checkbox("Frosted Glass Effect", theme.get_frosted_glass_enabled()) + if ch_fg: + theme.set_frosted_glass_enabled(fg_val) self._flush_to_config() models.save_config(self.config) imgui.end() diff --git a/src/shader_manager.py b/src/shader_manager.py index 2899763..2429252 100644 --- a/src/shader_manager.py +++ b/src/shader_manager.py @@ -6,22 +6,32 @@ class ShaderManager: self.bg_program = None self.pp_program = None self.blur_program = None - self.capture_fbo = None - self.capture_tex = None + self.blur_fbo = None + self.scene_tex = None self.blur_tex = None self.fbo_width = 0 self.fbo_height = 0 + self._vao = None + + def _ensure_vao(self): + if self._vao is None: + try: + self._vao = gl.glGenVertexArrays(1) + except Exception: + pass + if self._vao is not None: + gl.glBindVertexArray(self._vao) def setup_capture_fbo(self, width, height): - if self.capture_fbo is not None: - gl.glDeleteFramebuffers(1, [self.capture_fbo]) - if self.capture_tex is not None: - gl.glDeleteTextures(1, [self.capture_tex]) + if self.blur_fbo is not None: + gl.glDeleteFramebuffers(1, [self.blur_fbo]) + if self.scene_tex is not None: + gl.glDeleteTextures(1, [self.scene_tex]) if self.blur_tex is not None: gl.glDeleteTextures(1, [self.blur_tex]) - self.capture_fbo = gl.glGenFramebuffers(1) - self.capture_tex = gl.glGenTextures(1) - gl.glBindTexture(gl.GL_TEXTURE_2D, self.capture_tex) + self.blur_fbo = gl.glGenFramebuffers(1) + self.scene_tex = gl.glGenTextures(1) + gl.glBindTexture(gl.GL_TEXTURE_2D, self.scene_tex) gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, width, height, 0, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, None) gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR) gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR) @@ -30,7 +40,7 @@ class ShaderManager: gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, width, height, 0, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, None) gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR) gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR) - gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.capture_fbo) + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo) gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D, self.blur_tex, 0) if gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER) != gl.GL_FRAMEBUFFER_COMPLETE: raise RuntimeError("Framebuffer not complete") @@ -38,10 +48,20 @@ class ShaderManager: self.fbo_width = width self.fbo_height = height - def capture_begin(self, width, height): - if self.capture_fbo is None or self.fbo_width != width or self.fbo_height != height: + def prepare_global_blur(self, width, height, radius, tint, opacity): + if self.blur_fbo is None or self.fbo_width != width or self.fbo_height != height: self.setup_capture_fbo(width, height) - gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.capture_fbo) + gl.glBindTexture(gl.GL_TEXTURE_2D, self.scene_tex) + gl.glCopyTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, 0, 0, width, height, 0) + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo) + gl.glViewport(0, 0, width, height) + self.render_blur(self.scene_tex, width, height, radius, tint, opacity) + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0) + + def capture_begin(self, width, height): + if self.blur_fbo is None or self.fbo_width != width or self.fbo_height != height: + self.setup_capture_fbo(width, height) + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo) gl.glViewport(0, 0, width, height) def capture_end(self): @@ -138,6 +158,7 @@ void main() { u_res_loc = gl.glGetUniformLocation(self.bg_program, "u_resolution") if u_res_loc != -1: gl.glUniform2f(u_res_loc, float(width), float(height)) + self._ensure_vao() gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4) gl.glUseProgram(0) @@ -189,6 +210,7 @@ void main() { u_time_loc = gl.glGetUniformLocation(self.pp_program, "u_time") if u_time_loc != -1: gl.glUniform1f(u_time_loc, float(time)) + self._ensure_vao() gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4) gl.glBindTexture(gl.GL_TEXTURE_2D, 0) gl.glUseProgram(0) @@ -226,15 +248,19 @@ void main() { vec2 res = vec2(textureSize(u_texture, 0)); vec2 offset = u_blur_radius / res; vec4 sum = vec4(0.0); - sum += texture(u_texture, v_uv + vec2(-offset.x, -offset.y)) * 0.0625; - sum += texture(u_texture, v_uv + vec2(0.0, -offset.y)) * 0.125; - sum += texture(u_texture, v_uv + vec2(offset.x, -offset.y)) * 0.0625; - sum += texture(u_texture, v_uv + vec2(-offset.x, 0.0)) * 0.125; - sum += texture(u_texture, v_uv + vec2(0.0, 0.0)) * 0.25; - sum += texture(u_texture, v_uv + vec2(offset.x, 0.0)) * 0.125; - sum += texture(u_texture, v_uv + vec2(-offset.x, offset.y)) * 0.0625; - sum += texture(u_texture, v_uv + vec2(0.0, offset.y)) * 0.125; - sum += texture(u_texture, v_uv + vec2(offset.x, offset.y)) * 0.0625; + sum += texture(u_texture, v_uv) * 0.1839; + sum += texture(u_texture, v_uv + vec2(offset.x, 0.0)) * 0.1114; + sum += texture(u_texture, v_uv + vec2(-offset.x, 0.0)) * 0.1114; + sum += texture(u_texture, v_uv + vec2(0.0, offset.y)) * 0.1114; + sum += texture(u_texture, v_uv + vec2(0.0, -offset.y)) * 0.1114; + sum += texture(u_texture, v_uv + vec2(offset.x, offset.y)) * 0.0677; + sum += texture(u_texture, v_uv + vec2(-offset.x, offset.y)) * 0.0677; + sum += texture(u_texture, v_uv + vec2(offset.x, -offset.y)) * 0.0677; + sum += texture(u_texture, v_uv + vec2(-offset.x, -offset.y)) * 0.0677; + sum += texture(u_texture, v_uv + vec2(offset.x * 2.0, 0.0)) * 0.0248; + sum += texture(u_texture, v_uv + vec2(-offset.x * 2.0, 0.0)) * 0.0248; + sum += texture(u_texture, v_uv + vec2(0.0, offset.y * 2.0)) * 0.0248; + sum += texture(u_texture, v_uv + vec2(0.0, -offset.y * 2.0)) * 0.0248; vec3 tinted = mix(sum.rgb, vec3(1.0), u_tint_intensity); FragColor = vec4(tinted, sum.a * u_opacity); } @@ -259,6 +285,7 @@ void main() { u_opacity_loc = gl.glGetUniformLocation(self.blur_program, "u_opacity") if u_opacity_loc != -1: gl.glUniform1f(u_opacity_loc, float(opacity)) + self._ensure_vao() gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4) gl.glBindTexture(gl.GL_TEXTURE_2D, 0) gl.glUseProgram(0) diff --git a/src/theme_2.py b/src/theme_2.py index 9b0f426..a50fe96 100644 --- a/src/theme_2.py +++ b/src/theme_2.py @@ -235,6 +235,7 @@ _current_font_size: float = 16.0 _current_scale: float = 1.0 _transparency: float = 1.0 _child_transparency: float = 1.0 +_frosted_glass_enabled: bool = False _frosted_blur_radius: float = 8.0 _frosted_tint_intensity: float = 0.1 _frosted_opacity: float = 1.0 @@ -272,6 +273,13 @@ def set_child_transparency(val: float) -> None: _child_transparency = val apply(_current_palette) +def get_frosted_glass_enabled() -> bool: + return _frosted_glass_enabled + +def set_frosted_glass_enabled(val: bool) -> None: + global _frosted_glass_enabled + _frosted_glass_enabled = val + def get_frosted_blur_radius() -> float: return _frosted_blur_radius @@ -374,6 +382,7 @@ def save_to_config(config: dict) -> None: config["theme"]["scale"] = _current_scale config["theme"]["transparency"] = _transparency config["theme"]["child_transparency"] = _child_transparency + config["theme"]["frosted_glass_enabled"] = _frosted_glass_enabled config["theme"]["frosted_blur_radius"] = _frosted_blur_radius config["theme"]["frosted_tint_intensity"] = _frosted_tint_intensity config["theme"]["frosted_opacity"] = _frosted_opacity @@ -383,7 +392,7 @@ def save_to_config(config: dict) -> None: def load_from_config(config: dict) -> None: """Read [theme] from config. Font is handled separately at startup.""" import sys - global _current_font_path, _current_font_size, _current_scale, _current_palette, _transparency, _child_transparency, _frosted_blur_radius, _frosted_tint_intensity, _frosted_opacity + global _current_font_path, _current_font_size, _current_scale, _current_palette, _transparency, _child_transparency, _frosted_glass_enabled, _frosted_blur_radius, _frosted_tint_intensity, _frosted_opacity t = config.get("theme", {}) sys.stderr.write(f"[DEBUG theme_2] load_from_config raw: {t}\n") sys.stderr.flush() @@ -396,6 +405,7 @@ def load_from_config(config: dict) -> None: _current_scale = float(t.get("scale", 1.0)) _transparency = float(t.get("transparency", 1.0)) _child_transparency = float(t.get("child_transparency", 1.0)) + _frosted_glass_enabled = bool(t.get("frosted_glass_enabled", False)) _frosted_blur_radius = float(t.get("frosted_blur_radius", 8.0)) _frosted_tint_intensity = float(t.get("frosted_tint_intensity", 0.1)) _frosted_opacity = float(t.get("frosted_opacity", 1.0)) diff --git a/tests/test_theme.py b/tests/test_theme.py index b2aca12..5ebe3a1 100644 --- a/tests/test_theme.py +++ b/tests/test_theme.py @@ -3,33 +3,48 @@ from unittest.mock import MagicMock from src import theme_2 as theme def test_theme_apply_sets_rounding_and_padding(monkeypatch): - # Mock imgui - mock_style = MagicMock() - mock_imgui = MagicMock() - mock_imgui.get_style.return_value = mock_style - mock_imgui.ImVec2.side_effect = lambda x, y: (x, y) - monkeypatch.setattr(theme, "imgui", mock_imgui) + # Mock imgui + mock_style = MagicMock() + mock_imgui = MagicMock() + mock_imgui.get_style.return_value = mock_style + mock_imgui.ImVec2.side_effect = lambda x, y: (x, y) + monkeypatch.setattr(theme, "imgui", mock_imgui) - # Call apply with the default palette - theme.apply("ImGui Dark") + # Call apply with the default palette + theme.apply("ImGui Dark") - # Verify subtle rounding styles - assert mock_style.window_rounding == 6.0 - assert mock_style.child_rounding == 4.0 - assert mock_style.frame_rounding == 4.0 - assert mock_style.popup_rounding == 4.0 - assert mock_style.scrollbar_rounding == 12.0 - assert mock_style.grab_rounding == 4.0 - assert mock_style.tab_rounding == 4.0 + # Verify subtle rounding styles + assert mock_style.window_rounding == 6.0 + assert mock_style.child_rounding == 4.0 + assert mock_style.frame_rounding == 4.0 + assert mock_style.popup_rounding == 4.0 + assert mock_style.scrollbar_rounding == 12.0 + assert mock_style.grab_rounding == 4.0 + assert mock_style.tab_rounding == 4.0 - # Verify borders - assert mock_style.window_border_size == 1.0 - assert mock_style.frame_border_size == 1.0 - assert mock_style.popup_border_size == 1.0 + # Verify borders + assert mock_style.window_border_size == 1.0 + assert mock_style.frame_border_size == 1.0 + assert mock_style.popup_border_size == 1.0 - # Verify padding/spacing - assert mock_style.window_padding == (8.0, 8.0) - assert mock_style.frame_padding == (8.0, 4.0) - assert mock_style.item_spacing == (8.0, 4.0) - assert mock_style.item_inner_spacing == (4.0, 4.0) - assert mock_style.scrollbar_size == 14.0 + # Verify padding/spacing + assert mock_style.window_padding == (8.0, 8.0) + assert mock_style.frame_padding == (8.0, 4.0) + assert mock_style.item_spacing == (8.0, 4.0) + assert mock_style.item_inner_spacing == (4.0, 4.0) + assert mock_style.scrollbar_size == 14.0 + +def test_frosted_glass_enabled_toggle(): + theme.set_frosted_glass_enabled(True) + assert theme.get_frosted_glass_enabled() is True + theme.set_frosted_glass_enabled(False) + assert theme.get_frosted_glass_enabled() is False + +def test_theme_config_frosted_glass(): + config = {"theme": {"frosted_glass_enabled": True}} + theme.load_from_config(config) + assert theme.get_frosted_glass_enabled() is True + + new_config = {} + theme.save_to_config(new_config) + assert new_config["theme"]["frosted_glass_enabled"] is True