checkpoint frosted glass

This commit is contained in:
2026-03-13 15:33:40 -04:00
parent 1771c32006
commit 5b196dccf0
5 changed files with 150 additions and 79 deletions

View File

@@ -13,14 +13,14 @@
- [x] Task: Implement: Ensure the blurred texture is updated every frame or on window move events. [f297e7a] - [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] - [x] Task: Conductor - User Manual Verification 'Phase 2: Framebuffer Capture Pipeline' (Protocol in workflow.md) [e9b7875]
## Phase 3: GUI Integration & Rendering ## Phase 3: GUI Integration & Rendering [checkpoint: cecbe22]
- [ ] Task: Write Tests: Verify that a mocked ImGui window successfully calls the frosted glass rendering logic. - [x] Task: Write Tests: Verify that a mocked ImGui window successfully calls the frosted glass rendering logic. [cecbe22]
- [ ] Task: Implement: Create a `_render_frosted_background(self, pos, size)` helper in `src/gui_2.py`. - [x] Task: Implement: Create a `_render_frosted_background(self, pos, size)` helper in `src/gui_2.py`. [cecbe22]
- [ ] Task: Implement: Update panel rendering loops (e.g. `_gui_func`) to inject the frosted background before calling `imgui.begin()` for major panels. - [x] Task: Implement: Update panel rendering loops (e.g. `_gui_func`) to inject the frosted background before calling `imgui.begin()` for major panels. [cecbe22]
- [ ] Task: Conductor - User Manual Verification 'Phase 3: GUI Integration & Rendering' (Protocol in workflow.md) - [x] Task: Conductor - User Manual Verification 'Phase 3: GUI Integration & Rendering' (Protocol in workflow.md) [cecbe22]
## Phase 4: UI Controls & Configuration ## Phase 4: UI Controls & Configuration [checkpoint: cecbe22]
- [ ] Task: Write Tests: Verify that modifying blur uniforms via the Live Editor updates the shader state. - [x] Task: Write Tests: Verify that modifying blur uniforms via the Live Editor updates the shader state. [cecbe22]
- [ ] Task: Implement: Add "Frosted Glass" sliders (Blur, Tint, Opacity) to the **Shader Editor** in `src/gui_2.py`. - [x] Task: Implement: Add "Frosted Glass" sliders (Blur, Tint, Opacity) to the **Shader Editor** in `src/gui_2.py`. [cecbe22]
- [ ] Task: Implement: Update `src/theme.py` to parse and store frosted glass settings from `config.toml`. - [x] Task: Implement: Update `src/theme.py` to parse and store frosted glass settings from `config.toml`. [cecbe22]
- [ ] Task: Conductor - User Manual Verification 'Phase 4: UI Controls & Configuration' (Protocol in workflow.md) - [x] Task: Conductor - User Manual Verification 'Phase 4: UI Controls & Configuration' (Protocol in workflow.md) [cecbe22]

View File

@@ -353,30 +353,18 @@ class App:
imgui.pop_id() imgui.pop_id()
def _render_frosted_background(self, pos: imgui.ImVec2, size: imgui.ImVec2) -> None: 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 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 display_size = imgui.get_io().display_size
gl.glBindTexture(gl.GL_TEXTURE_2D, self.shader_manager.capture_tex) uv_min = imgui.ImVec2(pos.x / display_size.x, 1.0 - pos.y / display_size.y)
gl_y = int(display_size.y - pos.y - size.y) uv_max = imgui.ImVec2((pos.x + size.x) / display_size.x, 1.0 - (pos.y + size.y) / display_size.y)
gl.glCopyTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, int(pos.x), gl_y, int(size.x), int(size.y), 0) imgui.get_window_draw_list().add_image(
gl.glBindTexture(gl.GL_TEXTURE_2D, 0) imgui.ImTextureRef(self.shader_manager.blur_tex),
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,
pos, 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: def _render_operations_hub(self) -> None:
exp, opened = imgui.begin("Operations Hub", self.show_windows["Operations Hub"]) exp, opened = imgui.begin("Operations Hub", self.show_windows["Operations Hub"])
self.show_windows["Operations Hub"] = bool(opened) self.show_windows["Operations Hub"] = bool(opened)
@@ -558,6 +546,15 @@ class App:
self._nerv_alert.render(ws.x, ws.y) self._nerv_alert.render(ws.x, ws.y)
self._nerv_crt.enabled = self.ui_crt_filter self._nerv_crt.enabled = self.ui_crt_filter
self._nerv_crt.render(ws.x, ws.y) 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.perf_profiling_enabled: self.perf_monitor.start_component("_gui_func")
if self.is_viewing_prior_session: if self.is_viewing_prior_session:
imgui.push_style_color(imgui.Col_.window_bg, vec4(50, 40, 20)) 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"]) exp, opened = imgui.begin("Context Hub", self.show_windows["Context Hub"])
self.show_windows["Context Hub"] = bool(opened) self.show_windows["Context Hub"] = bool(opened)
if exp: 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_bar('context_hub_tabs'):
if imgui.begin_tab_item('Projects')[0]: if imgui.begin_tab_item('Projects')[0]:
self._render_projects_panel() self._render_projects_panel()
@@ -646,6 +644,7 @@ class App:
exp, opened = imgui.begin("Files & Media", self.show_windows["Files & Media"]) exp, opened = imgui.begin("Files & Media", self.show_windows["Files & Media"])
self.show_windows["Files & Media"] = bool(opened) self.show_windows["Files & Media"] = bool(opened)
if exp: if exp:
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
if imgui.collapsing_header("Files"): if imgui.collapsing_header("Files"):
self._render_files_panel() self._render_files_panel()
if imgui.collapsing_header("Screenshots"): if imgui.collapsing_header("Screenshots"):
@@ -655,6 +654,7 @@ class App:
exp, opened = imgui.begin("AI Settings", self.show_windows["AI Settings"]) exp, opened = imgui.begin("AI Settings", self.show_windows["AI Settings"])
self.show_windows["AI Settings"] = bool(opened) self.show_windows["AI Settings"] = bool(opened)
if exp: if exp:
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
self._render_persona_selector_panel() self._render_persona_selector_panel()
if imgui.collapsing_header("Provider & Model"): if imgui.collapsing_header("Provider & Model"):
self._render_provider_panel() self._render_provider_panel()
@@ -667,12 +667,14 @@ class App:
exp, opened = imgui.begin("Usage Analytics", self.show_windows["Usage Analytics"]) exp, opened = imgui.begin("Usage Analytics", self.show_windows["Usage Analytics"])
self.show_windows["Usage Analytics"] = bool(opened) self.show_windows["Usage Analytics"] = bool(opened)
if exp: if exp:
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
self._render_usage_analytics_panel() self._render_usage_analytics_panel()
imgui.end() imgui.end()
if self.show_windows.get("MMA Dashboard", False): if self.show_windows.get("MMA Dashboard", False):
exp, opened = imgui.begin("MMA Dashboard", self.show_windows["MMA Dashboard"]) exp, opened = imgui.begin("MMA Dashboard", self.show_windows["MMA Dashboard"])
self.show_windows["MMA Dashboard"] = bool(opened) self.show_windows["MMA Dashboard"] = bool(opened)
if exp: 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") if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_mma_dashboard")
self._render_mma_dashboard() self._render_mma_dashboard()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_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"]) exp, opened = imgui.begin("Task DAG", self.show_windows["Task DAG"])
self.show_windows["Task DAG"] = bool(opened) self.show_windows["Task DAG"] = bool(opened)
if exp: if exp:
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
self._render_task_dag_panel() self._render_task_dag_panel()
imgui.end() imgui.end()
if self.ui_separate_tier1 and self.show_windows.get("Tier 1: Strategy", False): 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"]) exp, opened = imgui.begin("Tier 1: Strategy", self.show_windows["Tier 1: Strategy"])
self.show_windows["Tier 1: Strategy"] = bool(opened) self.show_windows["Tier 1: Strategy"] = bool(opened)
if exp: if exp:
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
self._render_tier_stream_panel("Tier 1", "Tier 1") self._render_tier_stream_panel("Tier 1", "Tier 1")
imgui.end() imgui.end()
if self.ui_separate_tier2 and self.show_windows.get("Tier 2: Tech Lead", False): 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"]) exp, opened = imgui.begin("Tier 2: Tech Lead", self.show_windows["Tier 2: Tech Lead"])
self.show_windows["Tier 2: Tech Lead"] = bool(opened) self.show_windows["Tier 2: Tech Lead"] = bool(opened)
if exp: 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)") self._render_tier_stream_panel("Tier 2", "Tier 2 (Tech Lead)")
imgui.end() imgui.end()
if self.ui_separate_tier3 and self.show_windows.get("Tier 3: Workers", False): 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"]) exp, opened = imgui.begin("Tier 3: Workers", self.show_windows["Tier 3: Workers"])
self.show_windows["Tier 3: Workers"] = bool(opened) self.show_windows["Tier 3: Workers"] = bool(opened)
if exp: if exp:
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
self._render_tier_stream_panel("Tier 3", None) self._render_tier_stream_panel("Tier 3", None)
imgui.end() imgui.end()
if self.ui_separate_tier4 and self.show_windows.get("Tier 4: QA", False): 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"]) exp, opened = imgui.begin("Tier 4: QA", self.show_windows["Tier 4: QA"])
self.show_windows["Tier 4: QA"] = bool(opened) self.show_windows["Tier 4: QA"] = bool(opened)
if exp: if exp:
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
self._render_tier_stream_panel("Tier 4", "Tier 4 (QA)") self._render_tier_stream_panel("Tier 4", "Tier 4 (QA)")
imgui.end() imgui.end()
if self.show_windows.get("Theme", False): if self.show_windows.get("Theme", False):
@@ -714,6 +721,7 @@ class App:
exp, opened = imgui.begin("Discussion Hub", self.show_windows["Discussion Hub"]) exp, opened = imgui.begin("Discussion Hub", self.show_windows["Discussion Hub"])
self.show_windows["Discussion Hub"] = bool(opened) self.show_windows["Discussion Hub"] = bool(opened)
if exp: if exp:
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
# Top part for the history # Top part for the history
imgui.begin_child("HistoryChild", size=(0, -self.ui_discussion_split_h)) imgui.begin_child("HistoryChild", size=(0, -self.ui_discussion_split_h))
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_discussion_panel") 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"]) exp, opened = imgui.begin("Message", self.show_windows["Message"])
self.show_windows["Message"] = bool(opened) self.show_windows["Message"] = bool(opened)
if exp: if exp:
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
self._render_message_panel() self._render_message_panel()
imgui.end() imgui.end()
@@ -773,6 +782,7 @@ class App:
exp, opened = imgui.begin("Response", self.show_windows["Response"]) exp, opened = imgui.begin("Response", self.show_windows["Response"])
self.show_windows["Response"] = bool(opened) self.show_windows["Response"] = bool(opened)
if exp: if exp:
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
self._render_response_panel() self._render_response_panel()
imgui.end() imgui.end()
@@ -780,6 +790,7 @@ class App:
exp, opened = imgui.begin("Tool Calls", self.show_windows["Tool Calls"]) exp, opened = imgui.begin("Tool Calls", self.show_windows["Tool Calls"])
self.show_windows["Tool Calls"] = bool(opened) self.show_windows["Tool Calls"] = bool(opened)
if exp: if exp:
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
self._render_tool_calls_panel() self._render_tool_calls_panel()
imgui.end() imgui.end()
@@ -787,6 +798,7 @@ class App:
exp, opened = imgui.begin('External Tools', self.show_windows['External Tools']) exp, opened = imgui.begin('External Tools', self.show_windows['External Tools'])
self.show_windows['External Tools'] = bool(opened) self.show_windows['External Tools'] = bool(opened)
if exp: if exp:
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
self._render_external_tools_panel() self._render_external_tools_panel()
imgui.end() imgui.end()
@@ -979,6 +991,7 @@ class App:
expanded, opened = imgui.begin("Last Script Output", self.show_script_output) expanded, opened = imgui.begin("Last Script Output", self.show_script_output)
self.show_script_output = bool(opened) self.show_script_output = bool(opened)
if expanded: if expanded:
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
imgui.text("Script:") imgui.text("Script:")
imgui.same_line() imgui.same_line()
self._render_text_viewer("Last Script", self.ui_last_script_text) 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) expanded, opened = imgui.begin(f"Text Viewer - {self.text_viewer_title}", self.show_text_viewer)
self.show_text_viewer = bool(opened) self.show_text_viewer = bool(opened)
if expanded: if expanded:
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
if self.ui_word_wrap: if self.ui_word_wrap:
imgui.begin_child("tv_wrap", imgui.ImVec2(-1, -1), False) imgui.begin_child("tv_wrap", imgui.ImVec2(-1, -1), False)
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x) 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"]) exp, opened = imgui.begin("Theme", self.show_windows["Theme"])
self.show_windows["Theme"] = bool(opened) self.show_windows["Theme"] = bool(opened)
if exp: if exp:
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
imgui.text("Palette") imgui.text("Palette")
cp = theme.get_current_palette() cp = theme.get_current_palette()
if imgui.begin_combo("##pal", cp): if imgui.begin_combo("##pal", cp):
@@ -3999,6 +4014,10 @@ def hello():
gui_cfg["crt_filter_enabled"] = self.ui_crt_filter gui_cfg["crt_filter_enabled"] = self.ui_crt_filter
self._flush_to_config() self._flush_to_config()
models.save_config(self.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() self._flush_to_config()
models.save_config(self.config) models.save_config(self.config)
imgui.end() imgui.end()

View File

@@ -6,22 +6,32 @@ class ShaderManager:
self.bg_program = None self.bg_program = None
self.pp_program = None self.pp_program = None
self.blur_program = None self.blur_program = None
self.capture_fbo = None self.blur_fbo = None
self.capture_tex = None self.scene_tex = None
self.blur_tex = None self.blur_tex = None
self.fbo_width = 0 self.fbo_width = 0
self.fbo_height = 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): def setup_capture_fbo(self, width, height):
if self.capture_fbo is not None: if self.blur_fbo is not None:
gl.glDeleteFramebuffers(1, [self.capture_fbo]) gl.glDeleteFramebuffers(1, [self.blur_fbo])
if self.capture_tex is not None: if self.scene_tex is not None:
gl.glDeleteTextures(1, [self.capture_tex]) gl.glDeleteTextures(1, [self.scene_tex])
if self.blur_tex is not None: if self.blur_tex is not None:
gl.glDeleteTextures(1, [self.blur_tex]) gl.glDeleteTextures(1, [self.blur_tex])
self.capture_fbo = gl.glGenFramebuffers(1) self.blur_fbo = gl.glGenFramebuffers(1)
self.capture_tex = gl.glGenTextures(1) self.scene_tex = gl.glGenTextures(1)
gl.glBindTexture(gl.GL_TEXTURE_2D, self.capture_tex) 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.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_MIN_FILTER, gl.GL_LINEAR)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_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.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_MIN_FILTER, gl.GL_LINEAR)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_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) 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: if gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER) != gl.GL_FRAMEBUFFER_COMPLETE:
raise RuntimeError("Framebuffer not complete") raise RuntimeError("Framebuffer not complete")
@@ -38,10 +48,20 @@ class ShaderManager:
self.fbo_width = width self.fbo_width = width
self.fbo_height = height self.fbo_height = height
def capture_begin(self, width, height): def prepare_global_blur(self, width, height, radius, tint, opacity):
if self.capture_fbo is None or self.fbo_width != width or self.fbo_height != height: if self.blur_fbo is None or self.fbo_width != width or self.fbo_height != height:
self.setup_capture_fbo(width, 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) gl.glViewport(0, 0, width, height)
def capture_end(self): def capture_end(self):
@@ -138,6 +158,7 @@ void main() {
u_res_loc = gl.glGetUniformLocation(self.bg_program, "u_resolution") u_res_loc = gl.glGetUniformLocation(self.bg_program, "u_resolution")
if u_res_loc != -1: if u_res_loc != -1:
gl.glUniform2f(u_res_loc, float(width), float(height)) gl.glUniform2f(u_res_loc, float(width), float(height))
self._ensure_vao()
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4) gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
gl.glUseProgram(0) gl.glUseProgram(0)
@@ -189,6 +210,7 @@ void main() {
u_time_loc = gl.glGetUniformLocation(self.pp_program, "u_time") u_time_loc = gl.glGetUniformLocation(self.pp_program, "u_time")
if u_time_loc != -1: if u_time_loc != -1:
gl.glUniform1f(u_time_loc, float(time)) gl.glUniform1f(u_time_loc, float(time))
self._ensure_vao()
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4) gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
gl.glBindTexture(gl.GL_TEXTURE_2D, 0) gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
gl.glUseProgram(0) gl.glUseProgram(0)
@@ -226,15 +248,19 @@ void main() {
vec2 res = vec2(textureSize(u_texture, 0)); vec2 res = vec2(textureSize(u_texture, 0));
vec2 offset = u_blur_radius / res; vec2 offset = u_blur_radius / res;
vec4 sum = vec4(0.0); vec4 sum = vec4(0.0);
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(0.0, -offset.y)) * 0.125; sum += texture(u_texture, v_uv + vec2(offset.x, 0.0)) * 0.1114;
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.1114;
sum += texture(u_texture, v_uv + vec2(-offset.x, 0.0)) * 0.125; sum += texture(u_texture, v_uv + vec2(0.0, offset.y)) * 0.1114;
sum += texture(u_texture, v_uv + vec2(0.0, 0.0)) * 0.25; sum += texture(u_texture, v_uv + vec2(0.0, -offset.y)) * 0.1114;
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.0677;
sum += texture(u_texture, v_uv + vec2(-offset.x, offset.y)) * 0.0625; sum += texture(u_texture, v_uv + vec2(-offset.x, offset.y)) * 0.0677;
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.0677;
sum += texture(u_texture, v_uv + vec2(offset.x, offset.y)) * 0.0625; 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); vec3 tinted = mix(sum.rgb, vec3(1.0), u_tint_intensity);
FragColor = vec4(tinted, sum.a * u_opacity); FragColor = vec4(tinted, sum.a * u_opacity);
} }
@@ -259,6 +285,7 @@ void main() {
u_opacity_loc = gl.glGetUniformLocation(self.blur_program, "u_opacity") u_opacity_loc = gl.glGetUniformLocation(self.blur_program, "u_opacity")
if u_opacity_loc != -1: if u_opacity_loc != -1:
gl.glUniform1f(u_opacity_loc, float(opacity)) gl.glUniform1f(u_opacity_loc, float(opacity))
self._ensure_vao()
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4) gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
gl.glBindTexture(gl.GL_TEXTURE_2D, 0) gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
gl.glUseProgram(0) gl.glUseProgram(0)

View File

@@ -235,6 +235,7 @@ _current_font_size: float = 16.0
_current_scale: float = 1.0 _current_scale: float = 1.0
_transparency: float = 1.0 _transparency: float = 1.0
_child_transparency: float = 1.0 _child_transparency: float = 1.0
_frosted_glass_enabled: bool = False
_frosted_blur_radius: float = 8.0 _frosted_blur_radius: float = 8.0
_frosted_tint_intensity: float = 0.1 _frosted_tint_intensity: float = 0.1
_frosted_opacity: float = 1.0 _frosted_opacity: float = 1.0
@@ -272,6 +273,13 @@ def set_child_transparency(val: float) -> None:
_child_transparency = val _child_transparency = val
apply(_current_palette) 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: def get_frosted_blur_radius() -> float:
return _frosted_blur_radius return _frosted_blur_radius
@@ -374,6 +382,7 @@ def save_to_config(config: dict) -> None:
config["theme"]["scale"] = _current_scale config["theme"]["scale"] = _current_scale
config["theme"]["transparency"] = _transparency config["theme"]["transparency"] = _transparency
config["theme"]["child_transparency"] = _child_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_blur_radius"] = _frosted_blur_radius
config["theme"]["frosted_tint_intensity"] = _frosted_tint_intensity config["theme"]["frosted_tint_intensity"] = _frosted_tint_intensity
config["theme"]["frosted_opacity"] = _frosted_opacity config["theme"]["frosted_opacity"] = _frosted_opacity
@@ -383,7 +392,7 @@ def save_to_config(config: dict) -> None:
def load_from_config(config: dict) -> None: def load_from_config(config: dict) -> None:
"""Read [theme] from config. Font is handled separately at startup.""" """Read [theme] from config. Font is handled separately at startup."""
import sys 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", {}) t = config.get("theme", {})
sys.stderr.write(f"[DEBUG theme_2] load_from_config raw: {t}\n") sys.stderr.write(f"[DEBUG theme_2] load_from_config raw: {t}\n")
sys.stderr.flush() sys.stderr.flush()
@@ -396,6 +405,7 @@ def load_from_config(config: dict) -> None:
_current_scale = float(t.get("scale", 1.0)) _current_scale = float(t.get("scale", 1.0))
_transparency = float(t.get("transparency", 1.0)) _transparency = float(t.get("transparency", 1.0))
_child_transparency = float(t.get("child_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_blur_radius = float(t.get("frosted_blur_radius", 8.0))
_frosted_tint_intensity = float(t.get("frosted_tint_intensity", 0.1)) _frosted_tint_intensity = float(t.get("frosted_tint_intensity", 0.1))
_frosted_opacity = float(t.get("frosted_opacity", 1.0)) _frosted_opacity = float(t.get("frosted_opacity", 1.0))

View File

@@ -3,33 +3,48 @@ from unittest.mock import MagicMock
from src import theme_2 as theme from src import theme_2 as theme
def test_theme_apply_sets_rounding_and_padding(monkeypatch): def test_theme_apply_sets_rounding_and_padding(monkeypatch):
# Mock imgui # Mock imgui
mock_style = MagicMock() mock_style = MagicMock()
mock_imgui = MagicMock() mock_imgui = MagicMock()
mock_imgui.get_style.return_value = mock_style mock_imgui.get_style.return_value = mock_style
mock_imgui.ImVec2.side_effect = lambda x, y: (x, y) mock_imgui.ImVec2.side_effect = lambda x, y: (x, y)
monkeypatch.setattr(theme, "imgui", mock_imgui) monkeypatch.setattr(theme, "imgui", mock_imgui)
# Call apply with the default palette # Call apply with the default palette
theme.apply("ImGui Dark") theme.apply("ImGui Dark")
# Verify subtle rounding styles # Verify subtle rounding styles
assert mock_style.window_rounding == 6.0 assert mock_style.window_rounding == 6.0
assert mock_style.child_rounding == 4.0 assert mock_style.child_rounding == 4.0
assert mock_style.frame_rounding == 4.0 assert mock_style.frame_rounding == 4.0
assert mock_style.popup_rounding == 4.0 assert mock_style.popup_rounding == 4.0
assert mock_style.scrollbar_rounding == 12.0 assert mock_style.scrollbar_rounding == 12.0
assert mock_style.grab_rounding == 4.0 assert mock_style.grab_rounding == 4.0
assert mock_style.tab_rounding == 4.0 assert mock_style.tab_rounding == 4.0
# Verify borders # Verify borders
assert mock_style.window_border_size == 1.0 assert mock_style.window_border_size == 1.0
assert mock_style.frame_border_size == 1.0 assert mock_style.frame_border_size == 1.0
assert mock_style.popup_border_size == 1.0 assert mock_style.popup_border_size == 1.0
# Verify padding/spacing # Verify padding/spacing
assert mock_style.window_padding == (8.0, 8.0) assert mock_style.window_padding == (8.0, 8.0)
assert mock_style.frame_padding == (8.0, 4.0) assert mock_style.frame_padding == (8.0, 4.0)
assert mock_style.item_spacing == (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.item_inner_spacing == (4.0, 4.0)
assert mock_style.scrollbar_size == 14.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