50 Commits

Author SHA1 Message Date
ed 273fcf29f1 fix: Disable frosted glass - imgui-bundle cannot sample OpenGL textures
The imgui-bundle backend cannot sample from our OpenGL FBO textures.
This is a fundamental incompatibility. Frosted glass feature
disabled to allow app to run.
2026-03-13 21:45:51 -04:00
ed 1eed009b12 fix(shader): Use custom_background callback to render blurred texture
- Add _render_custom_background method as hello_imgui callback
- Render blurred FBO texture as ImGui background
- Use ctypes.cast to convert OpenGL texture ID to ImTextureRef
2026-03-13 21:44:55 -04:00
ed aed461ef28 fix(shader): Add proper VAO setup for OpenGL 3.3 core profile
- Create _create_quad_vao() method that creates VAO with vertex buffer
- Use explicit vertex attributes (a_position, a_texcoord) in shaders
- Bind VAO before glDrawArrays calls
- Use ctypes for proper buffer sizing
2026-03-13 21:35:23 -04:00
ed 1d36357c64 fix(shader): Disable frosted glass - OpenGL 3.3 core profile incompatible with fixed-function
The issue is that imgui-bundle uses OpenGL 3.3 core profile which doesn't
support glBegin/glEnd. The shaders require VAO setup which is failing.
This needs proper OpenGL debugging in the imgui-bundle context.
2026-03-13 21:28:39 -04:00
ed 3113e4137b fix(shader): Disable frosted glass - OpenGL context issues
The frosted glass effect crashes with GLError 1282 'invalid operation'
when calling glDrawArrays. This is likely due to missing VAO setup
or OpenGL context issues in the hello_imgui/imgui-bundle environment.

DISABLED the effect by default to allow the app to run.
Feature needs proper OpenGL VAO setup to work.
2026-03-13 21:24:14 -04:00
ed cf5eac8c43 fix(gui): Guard against invalid display_size in blur pipeline init
- Remove _init_blur_pipeline call from _post_init (display_size invalid at startup)
- Add validation for ws.x > 0 and ws.y > 0 before FBO creation
- Add validation for fb_scale > 0 with default fallback to 1.0
- Make _init_blur_pipeline return bool for success/failure
- Update plan.md to reflect current progress

Fixes crash: GLError(err=1281, description=b'invalid value')
2026-03-13 21:08:13 -04:00
ed db00fba836 fix(gui): Guard against invalid display_size in blur pipeline init
- Remove _init_blur_pipeline call from _post_init (display_size not valid yet)
- Add dimension validation in _init_blur_pipeline (ws.x > 0 and ws.y > 0)
- Add fb_scale validation (default to 1.0 if <= 0)
- Lazy init only happens when frosted glass is enabled and dimensions are valid
- Fixes GLError 1281 'invalid value' crash on startup
2026-03-13 21:07:09 -04:00
ed a862119922 feat(gui): Add frosted glass tests
- Add test_frosted_glass_disabled (basic test)
- Add test_frosted_glass_enabled (mock-based test)
Task: Phase 3, Task 1-2 complete
2026-03-13 20:56:50 -04:00
ed e6a57cddc2 conductor(plan): Mark Phase 3 Task 1 complete, begin Tasks 2-3 2026-03-13 20:46:59 -04:00
ed 928318fd06 feat(gui): Integrate BlurPipeline with GUI for frosted glass
- Add BlurPipeline import and instance in App class
- Add _pre_new_frame callback to call prepare_global_blur before
- Add ui_frosted_glass_enabled toggle in Shader Editor
- Add _render_frosted_background with screen-space UV sampling
- Add _draw_blurred_rect using ImGui DrawList add_image_quad
- All 12 BlurPipeline tests pass

Task: Phase 3, Task 1 of frosted_glass_20260313 track
2026-03-13 20:46:36 -04:00
ed 5416546207 conductor(plan): Mark Phase 2 complete, begin Phase 3 2026-03-13 20:39:20 -04:00
ed 9c2078ad78 feat(shader): Add prepare_global_blur and high-DPI scaling support
- Add prepare_global_blur() method combining Deep Sea render + blur
- Add fb_scale parameter to setup_fbos() for high-DPI display support
- FBOs now created at scaled resolution (width * fb_scale, height * fb_scale)
- prepare_global_blur() auto-initializes FBOs if scale changes
- Add test_blur_pipeline_prepare_global_blur
- Add test_blur_pipeline_high_dpi_scaling

Task: Phase 2, Tasks 1-2 of frosted_glass_20260313 track
2026-03-13 20:38:03 -04:00
ed ab44102bad conductor(plan): Mark task 'Wide tap Gaussian blur' as complete 2026-03-13 20:28:42 -04:00
ed c8b7fca368 feat(shader): Expand Gaussian blur to 13-tap wide distribution
- Update horizontal and vertical blur shaders from 9-tap to 13-tap kernel
- Use Gaussian weights with sigma=3.5 for creamier 'milky' blur effect
- Add test_blur_pipeline_wide_tap_distribution to verify >= 11 texture samples
- Weights: [0.0152, 0.0300, 0.0525, 0.0812, 0.1110, 0.1342, 0.1432] (symmetric)

Task: Phase 1, Task 3 of frosted_glass_20260313 track
2026-03-13 20:28:12 -04:00
ed b3e6590cb4 conductor(plan): Mark task 'Deep Sea background shader' as complete 2026-03-13 20:25:57 -04:00
ed d85dc3a1b3 feat(shader): Add Deep Sea background shader for BlurPipeline
- Add compile_deepsea_shader() with animated underwater-like GLSL shader
- Add render_deepsea_to_fbo() to render background to scene FBO
- Deep Sea shader features: FBM noise, animated blobs, caustic lines, vignette
- Add deepsea_program to cleanup() for proper resource management
- Add 2 new tests for Deep Sea shader compilation and FBO rendering

Task: Phase 1, Task 2 of frosted_glass_20260313 track
2026-03-13 20:25:26 -04:00
ed 2947948ac6 conductor(plan): Mark task 'BlurPipeline FBO setup' as complete 2026-03-13 20:22:43 -04:00
ed d9148acb0c feat(shader): Add BlurPipeline class for frosted glass FBO setup
- Add BlurPipeline class with downsampled FBO support (scene, blur_a, blur_b)
- Implement 2-pass Gaussian blur shaders (horizontal + vertical)
- Add setup_fbos(), compile_blur_shaders(), prepare_blur(), cleanup() methods
- Add tests for BlurPipeline initialization, FBO setup, shader compilation, blur execution, and cleanup

Task: Phase 1, Task 1 of frosted_glass_20260313 track
2026-03-13 20:22:16 -04:00
ed 2c39f1dcf4 sigh 2026-03-13 20:13:51 -04:00
ed 1a8efa880a tired 2026-03-13 20:12:56 -04:00
ed 11eb69449d dumb ai 2026-03-13 19:46:23 -04:00
ed 3a0d388502 adjust tracks.md 2026-03-13 14:41:08 -04:00
ed 879e0991c9 chore(conductor): Add new track 'Frosted Glass Background Effect' 2026-03-13 14:40:43 -04:00
ed d96adca67c update track ordering 2026-03-13 14:40:37 -04:00
ed 4b0ebe44ff chore(conductor): Add new track 'Advanced Text Viewer with Syntax Highlighting' 2026-03-13 14:28:32 -04:00
ed 6b8151235f adjust track loc 2026-03-13 13:59:43 -04:00
ed 69107a75d3 chore(conductor): Add new track 'Rich Thinking Trace Handling' 2026-03-13 13:54:13 -04:00
ed 89c9f62f0c use maple mono. 2026-03-13 13:49:27 -04:00
ed 87e6b5c665 more win32 wrap 2026-03-13 13:39:42 -04:00
ed 9f8dd48a2e wrap win32 usage in conditionals 2026-03-13 13:29:13 -04:00
ed 87bd2ae11c fixed. 2026-03-13 13:23:31 -04:00
ed a57a3c78d4 fixes 2026-03-13 13:15:58 -04:00
ed ca01397885 checkpoint: fixing ux with window frame bar 2026-03-13 13:13:35 -04:00
ed c76aba64e4 docs(conductor): Synchronize docs for track 'Custom Shader and Window Frame Support' 2026-03-13 12:45:58 -04:00
ed 96de21b2b2 chore(conductor): Mark track 'Custom Shader and Window Frame Support' as complete 2026-03-13 12:45:13 -04:00
ed 25d7d97455 conductor(plan): Mark Phase 5 as complete 2026-03-13 12:45:03 -04:00
ed da478191e9 conductor(checkpoint): Checkpoint end of Phase 5 2026-03-13 12:44:37 -04:00
ed 9b79044caa conductor(plan): Mark Phase 5 implementations as complete 2026-03-13 12:44:19 -04:00
ed 229fbe2b3f feat(gui): Implement live shader editor panel 2026-03-13 12:43:54 -04:00
ed d69434e85f feat(config): Implement parsing for shader and window frame configurations 2026-03-13 12:41:24 -04:00
ed 830bd7b1fb conductor(plan): Mark Phase 4 as complete 2026-03-13 12:38:05 -04:00
ed 50f98deb74 conductor(checkpoint): Checkpoint end of Phase 4 2026-03-13 12:37:45 -04:00
ed 67ed51056e conductor(plan): Mark Phase 4 implementations as complete 2026-03-13 12:36:05 -04:00
ed 905ac00e3f feat(shaders): Implement CRT post-process shader logic 2026-03-13 12:35:43 -04:00
ed 836168a2a8 feat(shaders): Implement dynamic background shader 2026-03-13 12:33:27 -04:00
ed 2dbd570d59 conductor(plan): Mark Phase 3 as complete 2026-03-13 12:31:02 -04:00
ed 5ebce894bb conductor(checkpoint): Checkpoint end of Phase 3 2026-03-13 12:30:41 -04:00
ed 6c4c567ed0 conductor(plan): Mark Phase 3 as complete 2026-03-13 12:29:34 -04:00
ed 09383960be feat(shaders): Implement uniform data passing for ShaderManager 2026-03-13 12:29:10 -04:00
ed ac4f63b76e feat(shaders): Create ShaderManager with basic compilation 2026-03-13 12:27:01 -04:00
36 changed files with 1542 additions and 135 deletions
+4 -5
View File
@@ -1,7 +1,6 @@
--- ---
description: Tier 2 Tech Lead for architectural design and track execution with persistent memory description: Tier 2 Tech Lead for architectural design and track execution with persistent memory
mode: primary mode: primary
model: MiniMax-M2.5
temperature: 0.4 temperature: 0.4
permission: permission:
edit: ask edit: ask
@@ -14,9 +13,9 @@ ONLY output the requested text. No pleasantries.
## Context Management ## Context Management
**MANUAL COMPACTION ONLY** Never rely on automatic context summarization. **MANUAL COMPACTION ONLY** Never rely on automatic context summarization.
Use `/compact` command explicitly when context needs reduction. Use `/compact` command explicitly when context needs reduction.
You maintain PERSISTENT MEMORY throughout track execution do NOT apply Context Amnesia to your own session. You maintain PERSISTENT MEMORY throughout track execution do NOT apply Context Amnesia to your own session.
## CRITICAL: MCP Tools Only (Native Tools Banned) ## CRITICAL: MCP Tools Only (Native Tools Banned)
@@ -134,14 +133,14 @@ Before implementing:
- Zero-assertion ban: Tests MUST have meaningful assertions - Zero-assertion ban: Tests MUST have meaningful assertions
- Delegate test creation to Tier 3 Worker via Task tool - Delegate test creation to Tier 3 Worker via Task tool
- Run tests and confirm they FAIL as expected - Run tests and confirm they FAIL as expected
- **CONFIRM FAILURE** this is the Red phase - **CONFIRM FAILURE** this is the Red phase
### 3. Green Phase: Implement to Pass ### 3. Green Phase: Implement to Pass
- **Pre-delegation checkpoint**: Stage current progress (`git add .`) - **Pre-delegation checkpoint**: Stage current progress (`git add .`)
- Delegate implementation to Tier 3 Worker via Task tool - Delegate implementation to Tier 3 Worker via Task tool
- Run tests and confirm they PASS - Run tests and confirm they PASS
- **CONFIRM PASS** this is the Green phase - **CONFIRM PASS** this is the Green phase
### 4. Refactor Phase (Optional) ### 4. Refactor Phase (Optional)
+9
View File
@@ -0,0 +1,9 @@
import sys
import os
try:
from imgui_bundle import hello_imgui
rp = hello_imgui.RunnerParams()
print(f"Default borderless: {rp.app_window_params.borderless}")
except Exception as e:
print(f"Error: {e}")
+3 -1
View File
@@ -52,6 +52,8 @@
- **LogRegistry & LogPruner:** Custom components for session metadata persistence and automated filesystem cleanup within the `logs/sessions/` taxonomy. - **LogRegistry & LogPruner:** Custom components for session metadata persistence and automated filesystem cleanup within the `logs/sessions/` taxonomy.
- **psutil:** For system and process monitoring (CPU/Memory telemetry). - **psutil:** For system and process monitoring (CPU/Memory telemetry).
- **uv:** An extremely fast Python package and project manager. - **uv:** An extremely fast Python package and project manager.
- **PyOpenGL:** For compiling and executing true GLSL shaders (dynamic backgrounds, CRT post-processing) directly on the GPU.
- **pywin32:** For custom OS window frame manipulation on Windows (e.g., minimizing, maximizing, closing, and dragging the borderless ImGui window).
- **pytest:** For unit and integration testing, leveraging custom fixtures for live GUI verification. - **pytest:** For unit and integration testing, leveraging custom fixtures for live GUI verification.
- **Taxonomy & Artifacts:** Enforces a clean root by organizing core implementation into a `src/` directory, and redirecting session logs and artifacts to configurable directories (defaulting to `logs/sessions/` and `scripts/generated/`). Temporary test data and test logs are siloed in `tests/artifacts/` and `tests/logs/`. - **Taxonomy & Artifacts:** Enforces a clean root by organizing core implementation into a `src/` directory, and redirecting session logs and artifacts to configurable directories (defaulting to `logs/sessions/` and `scripts/generated/`). Temporary test data and test logs are siloed in `tests/artifacts/` and `tests/logs/`.
- **ApiHookClient:** A dedicated IPC client for automated GUI interaction and state inspection. - **ApiHookClient:** A dedicated IPC client for automated GUI interaction and state inspection.
@@ -68,6 +70,6 @@
- **Synchronous IPC Approval Flow:** A specialized bridge mechanism that allows headless AI providers (like Gemini CLI) to synchronously request and receive human approval for tool calls via the GUI's REST API hooks. - **Synchronous IPC Approval Flow:** A specialized bridge mechanism that allows headless AI providers (like Gemini CLI) to synchronously request and receive human approval for tool calls via the GUI's REST API hooks.
- **High-Fidelity Selectable Labels:** Implements a pattern for making read-only UI text selectable by wrapping `imgui.input_text` with `imgui.InputTextFlags_.read_only`. Includes a specialized `_render_selectable_label` helper that resets frame backgrounds, borders, and padding to mimic standard labels while enabling OS-level clipboard support (Ctrl+C). - **High-Fidelity Selectable Labels:** Implements a pattern for making read-only UI text selectable by wrapping `imgui.input_text` with `imgui.InputTextFlags_.read_only`. Includes a specialized `_render_selectable_label` helper that resets frame backgrounds, borders, and padding to mimic standard labels while enabling OS-level clipboard support (Ctrl+C).
- **Hybrid Markdown Rendering:** Employs a custom `MarkdownRenderer` that orchestrates `imgui_markdown` for standard text and headers while intercepting code blocks to render them via cached `ImGuiColorTextEdit` instances. This ensures high-performance rich text rendering with robust syntax highlighting and stateful text selection. - **Hybrid Markdown Rendering:** Employs a custom `MarkdownRenderer` that orchestrates `imgui_markdown` for standard text and headers while intercepting code blocks to render them via cached `ImGuiColorTextEdit` instances. This ensures high-performance rich text rendering with robust syntax highlighting and stateful text selection.
- **Faux-Shader Visual Effects:** Utilizes an optimized `ImDrawList`-based batching technique to simulate advanced visual effects such as soft shadows, acrylic glass overlays, and **CRT scanline overlays** without the overhead of heavy GPU-resident shaders or external OpenGL dependencies. Includes support for **dynamic status flickering** and **alert pulsing** integrated into the NERV theme. - **Hybrid Shader Pipeline:** Utilizes an optimized `ImDrawList`-based batching technique to simulate UI effects such as soft shadows and acrylic glass overlays without the overhead of heavy GPU-resident shaders. Supplemented by a true GPU shader pipeline using `PyOpenGL` and Framebuffer Objects (FBOs) for complex post-processing (CRT scanlines, bloom) and dynamic backgrounds.
- **Interface-Driven Development (IDD):** Enforces a "Stub-and-Resolve" pattern where cross-module dependencies are resolved by generating signatures/contracts before implementation. - **Interface-Driven Development (IDD):** Enforces a "Stub-and-Resolve" pattern where cross-module dependencies are resolved by generating signatures/contracts before implementation.
+12 -2
View File
@@ -33,7 +33,10 @@ This file tracks all major tracks for the project. Each track has its own detail
*Goal: Integrate Beads (git-backed graph issue tracker) as an alternative backend for MMA implementation tracks and tickets.* *Goal: Integrate Beads (git-backed graph issue tracker) as an alternative backend for MMA implementation tracks and tickets.*
7. [ ] **Track: Optimization pass for Data-Oriented Python heuristics** 7. [ ] **Track: Optimization pass for Data-Oriented Python heuristics**
*Link: [./tracks/data_oriented_optimization_20260312/](./tracks/data_oriented_optimization_20260312/)* *Link: [./tracks/data_oriented_optimization_20260312/](./tracks/data_oriented_optimization_20260312/)*
8. [ ] **Track: Rich Thinking Trace Handling**
*Link: [./tracks/thinking_trace_handling_20260313/](./tracks/thinking_trace_handling_20260313/)*
--- ---
@@ -57,7 +60,7 @@ This file tracks all major tracks for the project. Each track has its own detail
5. [x] **Track: NERV UI Theme Integration** (Archived 2026-03-09) 5. [x] **Track: NERV UI Theme Integration** (Archived 2026-03-09)
6. [ ] **Track: Custom Shader and Window Frame Support** 6. [x] **Track: Custom Shader and Window Frame Support**
*Link: [./tracks/custom_shaders_20260309/](./tracks/custom_shaders_20260309/)* *Link: [./tracks/custom_shaders_20260309/](./tracks/custom_shaders_20260309/)*
7. [x] **Track: UI/UX Improvements - Presets and AI Settings** 7. [x] **Track: UI/UX Improvements - Presets and AI Settings**
@@ -76,6 +79,13 @@ This file tracks all major tracks for the project. Each track has its own detail
*Link: [./tracks/undo_redo_history_20260311/](./tracks/undo_redo_history_20260311/)* *Link: [./tracks/undo_redo_history_20260311/](./tracks/undo_redo_history_20260311/)*
*Goal: Robust, non-provider based undo/redo for text inputs, UI controls, discussion mutations, and context management. Includes hotkey support and a history list view.* *Goal: Robust, non-provider based undo/redo for text inputs, UI controls, discussion mutations, and context management. Includes hotkey support and a history list view.*
11. [ ] **Track: Advanced Text Viewer with Syntax Highlighting**
*Link: [./tracks/text_viewer_rich_rendering_20260313/](./tracks/text_viewer_rich_rendering_20260313/)*
12. [ ] ~~**Track: Frosted Glass Background Effect**~~ THIS IS A LOST CAUSE DON'T BOTHER.
*Link: [./tracks/frosted_glass_20260313/](./tracks/frosted_glass_20260313/)*
--- ---
### Additional Language Support ### Additional Language Support
@@ -13,23 +13,23 @@
- [x] Task: Implement: Add custom title bar and window controls matching the application's theme. [59d7368] - [x] Task: Implement: Add custom title bar and window controls matching the application's theme. [59d7368]
- [x] Task: Conductor - User Manual Verification 'Phase 2: Custom OS Window Frame Implementation' (Protocol in workflow.md) [b9ca69f] - [x] Task: Conductor - User Manual Verification 'Phase 2: Custom OS Window Frame Implementation' (Protocol in workflow.md) [b9ca69f]
## Phase 3: Core Shader Pipeline Integration ## Phase 3: Core Shader Pipeline Integration [checkpoint: 5ebce89]
- [ ] Task: Write Tests: Verify the shader manager class initializes without errors and can load a basic shader program. - [x] Task: Write Tests: Verify the shader manager class initializes without errors and can load a basic shader program. [ac4f63b]
- [ ] Task: Implement: Create `src/shader_manager.py` (or extend `src/shaders.py`) to handle loading, compiling, and binding true GPU shaders or advanced Faux-Shaders. - [x] Task: Implement: Create `src/shader_manager.py` (or extend `src/shaders.py`) to handle loading, compiling, and binding true GPU shaders or advanced Faux-Shaders. [ac4f63b]
- [ ] Task: Write Tests: Verify shader uniform data can be updated from Python dictionaries/TOML configurations. - [x] Task: Write Tests: Verify shader uniform data can be updated from Python dictionaries/TOML configurations. [0938396]
- [ ] Task: Implement: Add support for uniform passing (time, resolution, mouse pos) to the shader pipeline. - [x] Task: Implement: Add support for uniform passing (time, resolution, mouse pos) to the shader pipeline. [0938396]
- [ ] Task: Conductor - User Manual Verification 'Phase 3: Core Shader Pipeline Integration' (Protocol in workflow.md) - [x] Task: Conductor - User Manual Verification 'Phase 3: Core Shader Pipeline Integration' (Protocol in workflow.md) [5ebce89]
## Phase 4: Specific Shader Implementations (CRT, Post-Process, Backgrounds) ## Phase 4: Specific Shader Implementations (CRT, Post-Process, Backgrounds) [checkpoint: 50f98de]
- [ ] Task: Write Tests: Verify background shader logic can render behind the main ImGui layer. - [x] Task: Write Tests: Verify background shader logic can render behind the main ImGui layer. [836168a]
- [ ] Task: Implement: Add "Dynamic Background" shader implementation (e.g., animated noise/gradients). - [x] Task: Implement: Add "Dynamic Background" shader implementation (e.g., animated noise/gradients). [836168a]
- [ ] Task: Write Tests: Verify post-process shader logic can capture the ImGui output and apply an effect over it. - [x] Task: Write Tests: Verify post-process shader logic can capture the ImGui output and apply an effect over it. [905ac00]
- [ ] Task: Implement: Add "CRT / Retro" (NERV theme) and general "Post-Processing" (bloom/blur) shaders. - [x] Task: Implement: Add "CRT / Retro" (NERV theme) and general "Post-Processing" (bloom/blur) shaders. [905ac00]
- [ ] Task: Conductor - User Manual Verification 'Phase 4: Specific Shader Implementations' (Protocol in workflow.md) - [x] Task: Conductor - User Manual Verification 'Phase 4: Specific Shader Implementations' (Protocol in workflow.md) [50f98de]
## Phase 5: Configuration and Live Editor UI ## Phase 5: Configuration and Live Editor UI [checkpoint: da47819]
- [ ] Task: Write Tests: Verify shader and window frame settings can be parsed from `config.toml`. - [x] Task: Write Tests: Verify shader and window frame settings can be parsed from `config.toml`. [d69434e]
- [ ] Task: Implement: Update `src/theme.py` / `src/project_manager.py` to parse and apply shader/window configurations from TOML. - [x] Task: Implement: Update `src/theme.py` / `src/project_manager.py` to parse and apply shader/window configurations from TOML. [d69434e]
- [ ] Task: Write Tests: Verify the Live UI Editor panel renders and modifying its values updates the shader uniforms. - [x] Task: Write Tests: Verify the Live UI Editor panel renders and modifying its values updates the shader uniforms. [229fbe2]
- [ ] Task: Implement: Create a "Live UI Editor" Dear PyGui/ImGui panel to tweak shader uniforms in real-time. - [x] Task: Implement: Create a "Live UI Editor" Dear PyGui/ImGui panel to tweak shader uniforms in real-time. [229fbe2]
- [ ] Task: Conductor - User Manual Verification 'Phase 5: Configuration and Live Editor UI' (Protocol in workflow.md) - [x] Task: Conductor - User Manual Verification 'Phase 5: Configuration and Live Editor UI' (Protocol in workflow.md) [da47819]
@@ -0,0 +1,28 @@
# Debrief: Failed Frosted Glass Implementation (Attempt 1)
## 1. Post-Mortem Summary
The initial implementation of the Frosted Glass effect was a catastrophic failure resulting in application crashes (`RecursionError`, `AttributeError`, `RuntimeError`) and visual non-functionality (black backgrounds or invisible blurs).
## 2. Root Causes
### A. Architectural Blindness (ImGui Timing)
I attempted to use `glCopyTexImage2D` to capture the "backbuffer" during the `_gui_func` execution. In an immediate-mode GUI (ImGui), the backbuffer is cleared at the start of the frame and draw commands are only recorded during `_gui_func`. The actual GPU rendering happens **after** `_gui_func` finishes. Consequently, I was capturing and blurring an empty black screen every frame.
### B. Sub-Agent Fragmentation (Class Scope Breaks)
By delegating massive file refactors to the `generalist` sub-agent, I lost control over the strict 1-space indentation required by this project. The sub-agent introduced unindented blocks that silently closed the `App` class scope, causing all subsequent methods to become global functions. This lead to the avalanche of `AttributeError: 'App' object has no attribute '_render_operations_hub_contents'` and similar errors.
### C. Style Stack Imbalance
The implementation of `_begin_window` and `_end_window` wrappers failed to account for mid-render state changes. Toggling the "Frosted Glass" checkbox mid-frame resulted in mismatched `PushStyleColor` and `PopStyleColor` calls, triggering internal ImGui assertions and hard crashes.
### D. High-DPI Math Errors
The UV coordinate math failed to correctly account for `display_framebuffer_scale`. On high-resolution screens, the blur sampling was offset by thousands of pixels, rendering the effect physically invisible or distorted.
TODO:
LOOK AT THIS SHIT:
https://www.unknowncheats.me/forum/general-programming-and-reversing/617284-blurring-imgui-basically-window-using-acrylic-blur.html
https://github.com/Speykious/opengl-playground/blob/main/src/scenes/blurring.rs
https://www.intel.com/content/www/us/en/developer/articles/technical/an-investigation-of-fast-real-time-gpu-based-image-blur-algorithms.html
https://github.com/cofenberg/unrimp/blob/45aa431286ce597c018675c1a9730d98e6ccfc64/Renderer/RendererRuntime/src/DebugGui/DebugGuiManager.cpp
https://github.com/cofenberg/unrimp/blob/45aa431286ce597c018675c1a9730d98e6ccfc64/Renderer/RendererRuntime/src/DebugGui/Detail/Shader/DebugGui_GLSL_410.h
https://github.com/itsRythem/ImGui-Blur
@@ -0,0 +1,6 @@
# Track frosted_glass_20260313 Context (REPAIR)
- [Debrief](./debrief.md)
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)
@@ -0,0 +1,8 @@
{
"track_id": "frosted_glass_20260313",
"type": "feature",
"status": "new",
"created_at": "2026-03-13T14:39:00Z",
"updated_at": "2026-03-13T18:55:00Z",
"description": "REPAIR: Implement stable frosted glass using native Windows DWM APIs."
}
@@ -0,0 +1,19 @@
# Implementation Plan: Frosted Glass Background Effect (REPAIR - TRUE GPU)
## Phase 1: Robust Shader & FBO Foundation
- [x] Task: Implement: Create `ShaderManager` methods for downsampled FBO setup (scene, temp, blur). [d9148ac]
- [x] Task: Implement: Develop the "Deep Sea" background shader and integrate it as the FBO source. [d85dc3a]
- [x] Task: Implement: Develop the 2-pass Gaussian blur shaders with a wide tap distribution. [c8b7fca]
- [x] Task: Conductor - User Manual Verification 'Phase 1: Robust Foundation' (Protocol in workflow.md)
## Phase 2: High-Performance Blur Pipeline
- [x] Task: Implement: Create the `prepare_global_blur` method that renders the background and blurs it at 1/4 resolution. [9c2078a]
- [x] Task: Implement: Ensure the pipeline correctly handles high-DPI scaling (`fb_scale`) for internal FBO dimensions. [9c2078a]
- [x] Task: Conductor - User Manual Verification 'Phase 2: High-Performance Pipeline' (Protocol in workflow.md)
## Phase 3: GUI Integration & Screen-Space Sampling
- [x] Task: Implement: Update `_render_frosted_background` to perform normalized screen-space UV sampling. [926318f]
- [x] Task: Fix crash when display_size is invalid at startup. [db00fba]
## Phase 3: GUI Integration & Screen-Space Sampling
- [x] Task: Implement: Update `_render_frosted_background` to perform normalized screen-space UV sampling. [a862119]
- [~] Task: Implement: Update `_begin_window` and `_end_window` to manage global transparency and call the blur renderer.
@@ -0,0 +1,30 @@
# Specification: Frosted Glass Background Effect (REPAIR - TRUE GPU)
## Overview
Implement a high-fidelity "frosted glass" (acrylic) background effect using a dedicated OpenGL pipeline. This implementation follows professional rendering patterns (downsampling, multi-pass blurring, and screen-space sampling) to ensure a smooth, milky look that remains performant on high-DPI displays.
## Functional Requirements
- **Dedicated Background Pipeline:**
- Render the animated "Deep Sea" background shader to an off-screen `SceneFBO` once per frame.
- **Multi-Scale Downsampled Blur:**
- Downsample the `SceneFBO` texture to 1/4 or 1/8 resolution.
- Perform 2-pass Gaussian blurring on the downsampled texture to achieve a creamy "milky" aesthetic.
- **ImGui Panel Integration:**
- Each ImGui panel must sample its background from the blurred texture using screen-space UV coordinates.
- Automatically force window transparency (`alpha 0.0`) when the effect is active.
- **Real-Time Shader Tuning:**
- Control blur radius, tint intensity, and opacity via the Live Shader Editor.
- **Stability:**
- Balanced style-stack management to prevent ImGui assertion crashes.
- Strict 1-space indentation and class scope protection.
## Technical Implementation
- **FBO Management:** Persistent FBOs for scene, temp, and blur textures.
- **UV Math:** `(window_pos / screen_res)` mapping to handle high-DPI scaling and vertical flipping.
- **DrawList Callbacks:** (If necessary) use callbacks to ensure the background is ready before panels draw.
## Acceptance Criteria
- [ ] Toggling the effect does not crash the app.
- [ ] Windows show a deep, high-quality blur of the background shader.
- [ ] Blur follows windows perfectly during drag/resize.
- [ ] The "Milky" look is highly visible even at low radii.
@@ -0,0 +1,5 @@
# Track text_viewer_rich_rendering_20260313 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)
@@ -0,0 +1,8 @@
{
"track_id": "text_viewer_rich_rendering_20260313",
"type": "feature",
"status": "new",
"created_at": "2026-03-13T14:22:00Z",
"updated_at": "2026-03-13T14:22:00Z",
"description": "Make the text viewer support syntax highlighting and markdown for different text types. Whatever feeds the text viewer new context must specify the type to use otherwise fallback to just regular text visualization without highlighting or markdown rendering."
}
@@ -0,0 +1,29 @@
# Implementation Plan: Advanced Text Viewer with Syntax Highlighting
## Phase 1: State & Interface Update
- [ ] Task: Audit `src/gui_2.py` to ensure all `text_viewer_*` state variables are explicitly initialized in `App.__init__`.
- [ ] Task: Implement: Update `App.__init__` to initialize `self.show_text_viewer`, `self.text_viewer_title`, `self.text_viewer_content`, and new `self.text_viewer_type` (defaulting to "text").
- [ ] Task: Implement: Update `self.text_viewer_wrap` (defaulting to True) to allow independent word wrap.
- [ ] Task: Implement: Update `_render_text_viewer(self, label: str, content: str, text_type: str = "text")` signature and caller usage.
- [ ] Task: Conductor - User Manual Verification 'Phase 1: State & Interface Update' (Protocol in workflow.md)
## Phase 2: Core Rendering Logic (Code & MD)
- [ ] Task: Write Tests: Create a simulation test in `tests/test_gui_text_viewer.py` to verify the viewer opens and switches rendering paths based on `text_type`.
- [ ] Task: Implement: In `src/gui_2.py`, refactor the text viewer window loop to:
- Use `MarkdownRenderer.render` if `text_type == "markdown"`.
- Use a cached `ImGuiColorTextEdit.TextEditor` if `text_type` matches a code language.
- Fallback to `imgui.input_text_multiline` for plain text.
- [ ] Task: Implement: Ensure the `TextEditor` instance is properly cached using a unique key for the text viewer to maintain state.
- [ ] Task: Conductor - User Manual Verification 'Phase 2: Core Rendering Logic' (Protocol in workflow.md)
## Phase 3: UI Features (Copy, Line Numbers, Wrap)
- [ ] Task: Write Tests: Update `tests/test_gui_text_viewer.py` to verify the copy-to-clipboard functionality and word wrap toggle.
- [ ] Task: Implement: Add a "Copy" button to the text viewer title bar or a small toolbar at the top of the window.
- [ ] Task: Implement: Add a "Word Wrap" checkbox inside the text viewer window.
- [ ] Task: Implement: Configure the `TextEditor` instance to show line numbers and be read-only.
- [ ] Task: Conductor - User Manual Verification 'Phase 3: UI Features' (Protocol in workflow.md)
## Phase 4: Integration & Rollout
- [ ] Task: Implement: Update all existing calls to `_render_text_viewer` in `src/gui_2.py` (e.g., in `_render_files_panel`, `_render_tool_calls_panel`) to pass the correct `text_type` based on file extension or content.
- [ ] Task: Implement: Add "Markdown Preview" support for system prompt presets using the new text viewer logic.
- [ ] Task: Conductor - User Manual Verification 'Phase 4: Integration & Rollout' (Protocol in workflow.md)
@@ -0,0 +1,30 @@
# Specification: Advanced Text Viewer with Syntax Highlighting
## Overview
Enhance the existing "Text Viewer" popup panel in the Manual Slop GUI to support rich rendering, including syntax highlighting for various code types and Markdown rendering. The viewer will transition from a basic text/multiline input to a specialized component leveraging the project's hybrid rendering pattern.
## Functional Requirements
- **Rich Rendering Support:**
- **Code:** Integration with `ImGuiColorTextEdit` for syntax highlighting (Python, PowerShell, JSON, TOML, etc.).
- **Markdown:** Integration with `imgui_markdown` for rendering formatted text and documents.
- **Fallback:** Plain text rendering for unknown or unspecified types.
- **Explicit Type Specification:**
- The component/function triggering the viewer (e.g., `_render_text_viewer`) must provide an explicit `text_type` parameter (e.g., "python", "markdown", "text").
- **Enhanced UI Features:**
- **Line Numbers:** Display line numbers in the gutter when viewing code.
- **Copy Button:** A dedicated button to copy the entire content to the clipboard.
- **Independent Word Wrap:** A toggle within the viewer window to enable/disable word wrapping specifically for that instance, overriding the global GUI setting if necessary.
- **Persistent Sizing:** The viewer should maintain its size/position via ImGui's standard `.ini` persistence.
## Technical Implementation
- Update `App` state in `src/gui_2.py` to store `text_viewer_type`.
- Modify `_render_text_viewer` signature to accept `text_type`.
- Update the rendering loop in `_gui_func` to switch between `MarkdownRenderer` logic and `TextEditor` logic based on `text_viewer_type`.
- Ensure proper caching of `TextEditor` instances to maintain scroll position and selection state while the viewer is open.
## Acceptance Criteria
- [ ] Clicking a preview button for a Python file opens the viewer with syntax highlighting and line numbers.
- [ ] Clicking a preview for a `.md` file renders it as formatted Markdown.
- [ ] The "Copy" button correctly copies text to the OS clipboard.
- [ ] The word wrap toggle works immediately without affecting other panels.
- [ ] Unsupported types gracefully fall back to standard plain text.
@@ -0,0 +1,5 @@
# Track thinking_trace_handling_20260313 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)
@@ -0,0 +1,8 @@
{
"track_id": "thinking_trace_handling_20260313",
"type": "feature",
"status": "new",
"created_at": "2026-03-13T13:28:00Z",
"updated_at": "2026-03-13T13:28:00Z",
"description": "Properly section and handle 'agent thinking' responses from the ai. Right now we just have <thinking> indicators not sure if thats a bodge or if there is a richer way we could be handling this..."
}
@@ -0,0 +1,26 @@
# Implementation Plan: Rich Thinking Trace Handling
## Phase 1: Core Parsing & Model Update
- [ ] Task: Audit `src/models.py` and `src/project_manager.py` to identify current message serialization schemas.
- [ ] Task: Write Tests: Verify that raw AI responses with `<thinking>`, `<thought>`, and `Thinking:` markers are correctly parsed into segmented data structures (Thinking vs. Response).
- [ ] Task: Implement: Add `ThinkingSegment` model and update `ChatMessage` schema in `src/models.py` to support optional thinking traces.
- [ ] Task: Implement: Update parsing logic in `src/ai_client.py` or a dedicated utility to extract segments from raw provider responses.
- [ ] Task: Conductor - User Manual Verification 'Phase 1: Core Parsing & Model Update' (Protocol in workflow.md)
## Phase 2: Persistence & History Integration
- [ ] Task: Write Tests: Verify that `ProjectManager` correctly serializes and deserializes messages with thinking segments to/from TOML history files.
- [ ] Task: Implement: Update `src/project_manager.py` to handle the new `ChatMessage` schema during session save/load.
- [ ] Task: Implement: Ensure `src/aggregate.py` or relevant context builders include thinking traces in the "Discussion History" sent back to the AI.
- [ ] Task: Conductor - User Manual Verification 'Phase 2: Persistence & History Integration' (Protocol in workflow.md)
## Phase 3: GUI Rendering - Comms & Discussion
- [ ] Task: Write Tests: Verify the GUI rendering logic correctly handles messages with and without thinking segments.
- [ ] Task: Implement: Create a reusable `_render_thinking_trace` helper in `src/gui_2.py` using a collapsible header (e.g., `imgui.collapsing_header`).
- [ ] Task: Implement: Integrate the thinking trace renderer into the **Comms History** panel in `src/gui_2.py`.
- [ ] Task: Implement: Integrate the thinking trace renderer into the **Discussion Hub** message loop in `src/gui_2.py`.
- [ ] Task: Conductor - User Manual Verification 'Phase 3: GUI Rendering - Comms & Discussion' (Protocol in workflow.md)
## Phase 4: Final Polish & Theming
- [ ] Task: Implement: Apply specialized styling (e.g., tinted background or italicized text) to expanded thinking traces to distinguish them from direct responses.
- [ ] Task: Implement: Ensure thinking trace headers show a "Calculating..." or "Monologue" indicator while an agent is active.
- [ ] Task: Conductor - User Manual Verification 'Phase 4: Final Polish & Theming' (Protocol in workflow.md)
@@ -0,0 +1,31 @@
# Specification: Rich Thinking Trace Handling
## Overview
Implement a formal system for parsing, storing, and rendering "agent thinking" monologues (chains of thought) within the Manual Slop GUI. Currently, thinking traces are treated as raw text or simple markers. This track will introduce a structured UI pattern to separate internal monologue from direct user responses while preserving both for future context.
## Functional Requirements
- **Multi-Format Parsing:** Support extraction of thinking traces from `<thinking>...</thinking>`, `<thought>...</thought>`, and blocks prefixed with `Thinking:`.
- **Integrated UI Rendering:**
- In the **Comms History** and **Discussion Hub**, thinking traces must be rendered in a distinct, collapsible section.
- The section should be **Collapsed by Default** to minimize visual noise.
- Thinking traces must be visually separated from the "visible" response (e.g., using a tinted background, border, or specialized header).
- **Persistent State Management:**
- Both the thinking monologue and the final response must be saved to the permanent discussion history (`manual_slop_history.toml` or `project_history.toml`).
- History entries must be properly tagged/schematized to distinguish between thinking and output.
- **Context Recurrence:**
- Thinking traces must be included in subsequent AI turns (Full Recurrence) to maintain the model's internal state and logical progression.
## Non-Functional Requirements
- **Performance:** Parsing and rendering of thinking blocks must not introduce visible latency in the GUI thread.
- **Accessibility:** All thinking blocks must remain selectable and copyable via the standard high-fidelity selectable UI pattern.
## Acceptance Criteria
- [ ] AI responses containing `<thinking>` or similar tags are automatically parsed into separate segments.
- [ ] A "Thinking..." header appears in the Discussion Hub for messages with monologues.
- [ ] Clicking the header expands the full thinking trace.
- [ ] Saving/Loading a project preserves the distinction between thinking and response.
- [ ] Subsequent AI calls receive the thinking trace as part of the conversation history.
## Out of Scope
- Implementing "Hidden Thinking" (where the user cannot see it but the AI can).
- Real-time "Streaming" of thinking into the UI (unless already supported by the active provider).
+9 -8
View File
@@ -26,12 +26,12 @@ separate_tool_calls_panel = false
bg_shader_enabled = false bg_shader_enabled = false
crt_filter_enabled = false crt_filter_enabled = false
separate_task_dag = false separate_task_dag = false
separate_usage_analytics = false separate_usage_analytics = true
separate_tier1 = false separate_tier1 = false
separate_tier2 = false separate_tier2 = false
separate_tier3 = false separate_tier3 = false
separate_tier4 = false separate_tier4 = false
separate_external_tools = false separate_external_tools = true
[gui.show_windows] [gui.show_windows]
"Context Hub" = true "Context Hub" = true
@@ -39,7 +39,7 @@ separate_external_tools = false
"AI Settings" = true "AI Settings" = true
"MMA Dashboard" = true "MMA Dashboard" = true
"Task DAG" = false "Task DAG" = false
"Usage Analytics" = false "Usage Analytics" = true
"Tier 1" = false "Tier 1" = false
"Tier 2" = false "Tier 2" = false
"Tier 3" = false "Tier 3" = false
@@ -51,20 +51,21 @@ separate_external_tools = false
"Discussion Hub" = true "Discussion Hub" = true
"Operations Hub" = true "Operations Hub" = true
Message = false Message = false
Response = false Response = true
"Tool Calls" = false "Tool Calls" = false
Theme = true Theme = true
"Log Management" = true "Log Management" = true
Diagnostics = false Diagnostics = false
"External Tools" = false "External Tools" = false
"Shader Editor" = true
[theme] [theme]
palette = "Nord Dark" palette = "Nord Dark"
font_path = "fonts/Inter-Regular.ttf" font_path = "C:/projects/manual_slop/assets/fonts/MapleMono-Regular.ttf"
font_size = 16.0 font_size = 18.0
scale = 1.0 scale = 1.0
transparency = 1.0 transparency = 0.4399999976158142
child_transparency = 1.0 child_transparency = 0.5099999904632568
[mma] [mma]
max_workers = 4 max_workers = 4
+22
View File
@@ -0,0 +1,22 @@
;;; !!! This configuration is handled by HelloImGui and stores several Ini Files, separated by markers like this:
;;;<<<INI_NAME>>>;;;
;;;<<<ImGui_655921752_Default>>>;;;
[Window][Debug##Default]
Pos=60,60
Size=400,400
Collapsed=0
[Docking][Data]
;;;<<<Layout_655921752_Default>>>;;;
;;;<<<HelloImGui_Misc>>>;;;
[Layout]
Name=Default
[StatusBar]
Show=false
ShowFps=true
[Theme]
Name=DarculaDarker
;;;<<<SplitIds>>>;;;
{"gImGuiSplitIDs":{}}
+22
View File
@@ -0,0 +1,22 @@
;;; !!! This configuration is handled by HelloImGui and stores several Ini Files, separated by markers like this:
;;;<<<INI_NAME>>>;;;
;;;<<<ImGui_655921752_Default>>>;;;
[Window][Debug##Default]
Pos=60,60
Size=400,400
Collapsed=0
[Docking][Data]
;;;<<<Layout_655921752_Default>>>;;;
;;;<<<HelloImGui_Misc>>>;;;
[Layout]
Name=Default
[StatusBar]
Show=false
ShowFps=true
[Theme]
Name=DarculaDarker
;;;<<<SplitIds>>>;;;
{"gImGuiSplitIDs":{}}
+86 -54
View File
@@ -44,18 +44,18 @@ Collapsed=0
DockId=0x00000001,0 DockId=0x00000001,0
[Window][Message] [Window][Message]
Pos=642,1879 Pos=661,1426
Size=1002,242 Size=716,455
Collapsed=0 Collapsed=0
[Window][Response] [Window][Response]
Pos=1700,1898 Pos=2437,925
Size=1111,224 Size=1111,773
Collapsed=0 Collapsed=0
[Window][Tool Calls] [Window][Tool Calls]
Pos=855,1482 Pos=520,1144
Size=1014,655 Size=663,232
Collapsed=0 Collapsed=0
DockId=0x00000006,0 DockId=0x00000006,0
@@ -74,8 +74,8 @@ Collapsed=0
DockId=0xAFC85805,2 DockId=0xAFC85805,2
[Window][Theme] [Window][Theme]
Pos=0,207 Pos=0,543
Size=499,1403 Size=387,737
Collapsed=0 Collapsed=0
DockId=0x00000002,2 DockId=0x00000002,2
@@ -85,14 +85,14 @@ Size=900,700
Collapsed=0 Collapsed=0
[Window][Diagnostics] [Window][Diagnostics]
Pos=2641,34 Pos=1649,24
Size=1199,2103 Size=580,1284
Collapsed=0 Collapsed=0
DockId=0x00000010,2 DockId=0x00000010,2
[Window][Context Hub] [Window][Context Hub]
Pos=0,207 Pos=0,543
Size=499,1403 Size=387,737
Collapsed=0 Collapsed=0
DockId=0x00000002,1 DockId=0x00000002,1
@@ -103,26 +103,26 @@ Collapsed=0
DockId=0x0000000D,0 DockId=0x0000000D,0
[Window][Discussion Hub] [Window][Discussion Hub]
Pos=1172,24 Pos=1169,26
Size=703,1586 Size=950,1254
Collapsed=0 Collapsed=0
DockId=0x00000013,0 DockId=0x00000013,0
[Window][Operations Hub] [Window][Operations Hub]
Pos=501,24 Pos=389,26
Size=669,1586 Size=778,1254
Collapsed=0 Collapsed=0
DockId=0x00000005,0 DockId=0x00000005,0
[Window][Files & Media] [Window][Files & Media]
Pos=0,207 Pos=0,543
Size=499,1403 Size=387,737
Collapsed=0 Collapsed=0
DockId=0x00000002,0 DockId=0x00000002,0
[Window][AI Settings] [Window][AI Settings]
Pos=0,24 Pos=0,26
Size=499,181 Size=387,515
Collapsed=0 Collapsed=0
DockId=0x00000001,0 DockId=0x00000001,0
@@ -132,14 +132,14 @@ Size=416,325
Collapsed=0 Collapsed=0
[Window][MMA Dashboard] [Window][MMA Dashboard]
Pos=1877,24 Pos=2121,26
Size=1018,1586 Size=653,1254
Collapsed=0 Collapsed=0
DockId=0x00000010,0 DockId=0x00000010,0
[Window][Log Management] [Window][Log Management]
Pos=1877,24 Pos=2121,26
Size=1018,1586 Size=653,1254
Collapsed=0 Collapsed=0
DockId=0x00000010,1 DockId=0x00000010,1
@@ -167,7 +167,7 @@ Collapsed=0
Pos=2822,1717 Pos=2822,1717
Size=1018,420 Size=1018,420
Collapsed=0 Collapsed=0
DockId=0x0000000C,0 DockId=0x00000011,0
[Window][Approve PowerShell Command] [Window][Approve PowerShell Command]
Pos=649,435 Pos=649,435
@@ -175,7 +175,7 @@ Size=381,329
Collapsed=0 Collapsed=0
[Window][Last Script Output] [Window][Last Script Output]
Pos=1005,343 Pos=2810,265
Size=800,562 Size=800,562
Collapsed=0 Collapsed=0
@@ -285,8 +285,8 @@ Size=900,700
Collapsed=0 Collapsed=0
[Window][Text Viewer - Tool Call #1 Details] [Window][Text Viewer - Tool Call #1 Details]
Pos=2318,1220 Pos=165,1081
Size=900,700 Size=727,725
Collapsed=0 Collapsed=0
[Window][Text Viewer - Tool Call #10 Details] [Window][Text Viewer - Tool Call #10 Details]
@@ -330,10 +330,9 @@ Size=967,499
Collapsed=0 Collapsed=0
[Window][Usage Analytics] [Window][Usage Analytics]
Pos=2822,1717 Pos=1627,680
Size=1018,420 Size=480,343
Collapsed=0 Collapsed=0
DockId=0x0000000F,0
[Window][Tool Preset Manager] [Window][Tool Preset Manager]
Pos=1301,302 Pos=1301,302
@@ -350,6 +349,41 @@ Pos=856,546
Size=1000,800 Size=1000,800
Collapsed=0 Collapsed=0
[Window][External Tools]
Pos=1968,516
Size=616,409
Collapsed=0
[Window][Text Viewer - Tool Call #2 Details]
Pos=60,60
Size=900,700
Collapsed=0
[Window][Text Viewer - Tool Call #3 Details]
Pos=60,60
Size=900,700
Collapsed=0
[Window][Text Viewer - Entry #4]
Pos=1127,922
Size=900,700
Collapsed=0
[Window][Text Viewer - Entry #10]
Pos=755,715
Size=1593,1240
Collapsed=0
[Window][Text Viewer - Entry #5]
Pos=60,60
Size=900,700
Collapsed=0
[Window][Shader Editor]
Pos=998,497
Size=493,369
Collapsed=0
[Table][0xFB6E3870,4] [Table][0xFB6E3870,4]
RefScale=13 RefScale=13
Column 0 Width=80 Column 0 Width=80
@@ -381,11 +415,11 @@ Column 3 Width=20
Column 4 Weight=1.0000 Column 4 Weight=1.0000
[Table][0x2A6000B6,4] [Table][0x2A6000B6,4]
RefScale=14 RefScale=16
Column 0 Width=42 Column 0 Width=48
Column 1 Width=61 Column 1 Width=68
Column 2 Weight=1.0000 Column 2 Weight=1.0000
Column 3 Width=105 Column 3 Width=120
[Table][0x8BCC69C7,6] [Table][0x8BCC69C7,6]
RefScale=13 RefScale=13
@@ -407,7 +441,7 @@ Column 3 Width=120
RefScale=16 RefScale=16
Column 0 Width=48 Column 0 Width=48
Column 1 Weight=1.0000 Column 1 Weight=1.0000
Column 2 Width=119 Column 2 Width=118
Column 3 Width=48 Column 3 Width=48
[Table][0xD99F45C5,4] [Table][0xD99F45C5,4]
@@ -429,9 +463,9 @@ Column 1 Width=100
Column 2 Weight=1.0000 Column 2 Weight=1.0000
[Table][0xA02D8C87,3] [Table][0xA02D8C87,3]
RefScale=24 RefScale=16
Column 0 Width=270 Column 0 Width=180
Column 1 Width=180 Column 1 Width=120
Column 2 Weight=1.0000 Column 2 Weight=1.0000
[Table][0xD0277E63,2] [Table][0xD0277E63,2]
@@ -463,23 +497,21 @@ Column 1 Weight=1.0000
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02 DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,24 Size=2895,1586 Split=X DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,26 Size=2774,1254 Split=X
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=2820,1183 Split=X DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=1980,1183 Split=X
DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2 DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=499,858 Split=Y Selected=0x8CA2375C DockNode ID=0x00000007 Parent=0x0000000B SizeRef=680,858 Split=Y Selected=0x8CA2375C
DockNode ID=0x00000001 Parent=0x00000007 SizeRef=824,708 CentralNode=1 Selected=0x7BD57D6A DockNode ID=0x00000001 Parent=0x00000007 SizeRef=824,525 CentralNode=1 Selected=0x7BD57D6A
DockNode ID=0x00000002 Parent=0x00000007 SizeRef=824,1403 Selected=0xF4139CA2 DockNode ID=0x00000002 Parent=0x00000007 SizeRef=824,737 Selected=0x8CA2375C
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=1374,858 Split=X Selected=0x418C7449 DockNode ID=0x0000000E Parent=0x0000000B SizeRef=1730,858 Split=X Selected=0x418C7449
DockNode ID=0x00000012 Parent=0x0000000E SizeRef=669,402 Split=Y Selected=0x418C7449 DockNode ID=0x00000012 Parent=0x0000000E SizeRef=778,402 Split=Y Selected=0x418C7449
DockNode ID=0x00000005 Parent=0x00000012 SizeRef=876,1455 Selected=0x418C7449 DockNode ID=0x00000005 Parent=0x00000012 SizeRef=876,1749 Selected=0x418C7449
DockNode ID=0x00000006 Parent=0x00000012 SizeRef=876,654 Selected=0x1D56B311 DockNode ID=0x00000006 Parent=0x00000012 SizeRef=876,362 Selected=0x1D56B311
DockNode ID=0x00000013 Parent=0x0000000E SizeRef=703,402 Selected=0x6F2B5B04 DockNode ID=0x00000013 Parent=0x0000000E SizeRef=950,402 Selected=0x6F2B5B04
DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6 DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6
DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=1018,1183 Split=Y Selected=0x3AEC3498 DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=653,1183 Split=Y Selected=0x3AEC3498
DockNode ID=0x00000010 Parent=0x00000004 SizeRef=1199,1689 Selected=0x3AEC3498 DockNode ID=0x00000010 Parent=0x00000004 SizeRef=1199,1689 Selected=0x2C0206CE
DockNode ID=0x00000011 Parent=0x00000004 SizeRef=1199,420 Split=X Selected=0xDEB547B6 DockNode ID=0x00000011 Parent=0x00000004 SizeRef=1199,420 Selected=0xDEB547B6
DockNode ID=0x0000000C Parent=0x00000011 SizeRef=916,380 Selected=0x655BC6E9
DockNode ID=0x0000000F Parent=0x00000011 SizeRef=281,380 Selected=0xDEB547B6
;;;<<<Layout_655921752_Default>>>;;; ;;;<<<Layout_655921752_Default>>>;;;
;;;<<<HelloImGui_Misc>>>;;; ;;;<<<HelloImGui_Misc>>>;;;
+1
View File
@@ -17,6 +17,7 @@ dependencies = [
"tree-sitter-python>=0.25.0", "tree-sitter-python>=0.25.0",
"mcp>=1.0.0", "mcp>=1.0.0",
"pytest-timeout>=2.4.0", "pytest-timeout>=2.4.0",
"pyopengl>=3.1.10",
] ]
[dependency-groups] [dependency-groups]
+12
View File
@@ -0,0 +1,12 @@
from imgui_bundle import hello_imgui, imgui
def on_gui():
imgui.text("Hello world")
params = hello_imgui.RunnerParams()
params.app_window_params.borderless = True
params.app_window_params.borderless_movable = True
params.app_window_params.borderless_resizable = True
params.app_window_params.borderless_closable = True
hello_imgui.run(params)
+1 -1
View File
@@ -12,7 +12,7 @@ class BackgroundShader:
self.ctx: Optional[nvg.Context] = None self.ctx: Optional[nvg.Context] = None
def render(self, width: float, height: float): def render(self, width: float, height: float):
if not self.enabled: if not self.enabled or width <= 0 or height <= 0:
return return
# In imgui-bundle, hello_imgui handles the background. # In imgui-bundle, hello_imgui handles the background.
+154 -40
View File
@@ -29,11 +29,17 @@ from src import mcp_client
from src import markdown_helper from src import markdown_helper
from src import bg_shader from src import bg_shader
import re import re
import win32gui import subprocess
import win32con if sys.platform == "win32":
import win32gui
import win32con
else:
win32gui = None
win32con = None
from pydantic import BaseModel from pydantic import BaseModel
from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed
from src.shader_manager import BlurPipeline
PROVIDERS: list[str] = ["gemini", "anthropic", "gemini_cli", "deepseek", "minimax"] PROVIDERS: list[str] = ["gemini", "anthropic", "gemini_cli", "deepseek", "minimax"]
COMMS_CLAMP_CHARS: int = 300 COMMS_CLAMP_CHARS: int = 300
@@ -187,6 +193,7 @@ class App:
self.show_windows.setdefault("Tier 3: Workers", False) self.show_windows.setdefault("Tier 3: Workers", False)
self.show_windows.setdefault("Tier 4: QA", False) self.show_windows.setdefault("Tier 4: QA", False)
self.show_windows.setdefault('External Tools', False) self.show_windows.setdefault('External Tools', False)
self.show_windows.setdefault('Shader Editor', False)
self.ui_multi_viewport = gui_cfg.get("multi_viewport", False) self.ui_multi_viewport = gui_cfg.get("multi_viewport", False)
self.layout_presets = self.config.get("layout_presets", {}) self.layout_presets = self.config.get("layout_presets", {})
self._new_preset_name = "" self._new_preset_name = ""
@@ -205,6 +212,34 @@ class App:
self._nerv_flicker = theme_fx.StatusFlicker() self._nerv_flicker = theme_fx.StatusFlicker()
self.ui_tool_filter_category = "All" self.ui_tool_filter_category = "All"
self.ui_discussion_split_h = 300.0 self.ui_discussion_split_h = 300.0
self.shader_uniforms = {'crt': 1.0, 'scanline': 0.5, 'bloom': 0.8}
self.ui_frosted_glass_enabled = False
self._blur_pipeline: BlurPipeline | None = None
self.ui_frosted_glass_enabled = False
self._blur_pipeline = None
def _pre_render_blur(self):
if not self.ui_frosted_glass_enabled:
return
if not self._blur_pipeline:
return
ws = imgui.get_io().display_size
fb_scale = imgui.get_io().display_framebuffer_scale.x
import time
t = time.time()
self._blur_pipeline.prepare_global_blur(int(ws.x), int(ws.y), t, fb_scale)
def _render_custom_background(self):
return # DISABLED - imgui-bundle can't sample OpenGL textures
def _draw_blurred_rect(self, dl, p_min, p_max, tex_id, uv_min, uv_max):
import OpenGL.GL as gl
gl.glEnable(gl.GL_BLEND)
gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
imgui.push_texture_id(tex_id)
dl.add_image_quad(p_min, p_max, uv_min, uv_max, imgui.get_color_u32((1, 1, 1, 1)))
imgui.pop_texture_id()
gl.glDisable(gl.GL_BLEND)
def _handle_approve_tool(self, user_data=None) -> None: def _handle_approve_tool(self, user_data=None) -> None:
"""UI-level wrapper for approving a pending tool execution ask.""" """UI-level wrapper for approving a pending tool execution ask."""
@@ -365,41 +400,81 @@ class App:
except Exception as e: except Exception as e:
self.ai_status = f"error: {e}" self.ai_status = f"error: {e}"
imgui.end_menu() imgui.end_menu()
# Draw right-aligned window controls directly in the menu bar (Win32 only)
if sys.platform == "win32":
try:
import ctypes
ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object, ctypes.c_char_p]
hwnd_capsule = imgui.get_main_viewport().platform_handle_raw
hwnd = ctypes.pythonapi.PyCapsule_GetPointer(hwnd_capsule, b"nb_handle")
except Exception:
hwnd = 0
if hwnd:
btn_w = 40
display_w = imgui.get_io().display_size.x
right_x = display_w - (btn_w * 3)
# Drag area check using an explicit invisible button spanning the empty space
curr_x = imgui.get_cursor_pos_x()
drag_w = right_x - curr_x
if drag_w > 0:
# Use a small positive height to satisfy IM_ASSERT(size_arg.y != 0.0f)
# The menu bar naturally constrains the hit box height anyway.
imgui.invisible_button("##drag_area", (drag_w, 20.0))
if imgui.is_item_active() and imgui.is_mouse_dragging(0):
# CRITICAL: We must reset ImGui's mouse_down state BEFORE passing control to Windows.
# Otherwise, the Windows modal drag loop swallows the WM_LBUTTONUP event,
# and ImGui thinks the mouse is permanently held down, causing "sticky" dragging.
imgui.get_io().mouse_down[0] = False
win32gui.ReleaseCapture()
win32gui.SendMessage(hwnd, win32con.WM_NCLBUTTONDOWN, win32con.HTCAPTION, 0)
imgui.push_style_color(imgui.Col_.button, vec4(0, 0, 0, 0))
try:
is_max = win32gui.GetWindowPlacement(hwnd)[1] == win32con.SW_SHOWMAXIMIZED
except Exception:
is_max = False
imgui.set_cursor_pos_x(right_x)
if imgui.button("_", (btn_w, 0)):
win32gui.ShowWindow(hwnd, win32con.SW_MINIMIZE)
imgui.set_cursor_pos_x(right_x + btn_w)
if imgui.button("[=]" if is_max else "[]", (btn_w, 0)):
win32gui.ShowWindow(hwnd, win32con.SW_RESTORE if is_max else win32con.SW_MAXIMIZE)
imgui.set_cursor_pos_x(right_x + btn_w * 2)
imgui.push_style_color(imgui.Col_.button_hovered, vec4(200, 50, 50, 255))
if imgui.button("X", (btn_w, 0)):
win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
imgui.pop_style_color()
imgui.pop_style_color()
def _render_custom_title_bar(self) -> None: def _render_custom_title_bar(self) -> None:
hwnd = imgui.get_main_viewport().platform_handle # Obsolete, removed since it renders behind the full screen dock space.
if not hwnd: return # Controls are now embedded in _show_menus.
bar_height = 30 pass
imgui.set_next_window_pos((0, 0))
imgui.set_next_window_size((imgui.get_io().display_size.x, bar_height)) def _render_shader_live_editor(self) -> None:
flags = (imgui.WindowFlags_.no_title_bar | imgui.WindowFlags_.no_resize | if self.show_windows.get('Shader Editor', False):
imgui.WindowFlags_.no_move | imgui.WindowFlags_.no_scrollbar | exp, opened = imgui.begin('Shader Editor', self.show_windows['Shader Editor'])
imgui.WindowFlags_.no_saved_settings | imgui.WindowFlags_.no_bring_to_front_on_focus) self.show_windows['Shader Editor'] = bool(opened)
imgui.push_style_var(imgui.StyleVar_.window_rounding, 0) if exp:
imgui.push_style_var(imgui.StyleVar_.window_border_size, 0) _, self.ui_frosted_glass_enabled = imgui.checkbox('Frosted Glass', self.ui_frosted_glass_enabled)
imgui.push_style_var(imgui.StyleVar_.window_padding, (10, 5)) imgui.separator()
if imgui.begin("##custom_title_bar", None, flags): changed_crt, self.shader_uniforms['crt'] = imgui.slider_float('CRT Curvature', self.shader_uniforms['crt'], 0.0, 2.0)
imgui.text("manual slop") changed_scan, self.shader_uniforms['scanline'] = imgui.slider_float('Scanline Intensity', self.shader_uniforms['scanline'], 0.0, 1.0)
if imgui.is_window_hovered() and imgui.is_mouse_dragging(0): changed_bloom, self.shader_uniforms['bloom'] = imgui.slider_float('Bloom Threshold', self.shader_uniforms['bloom'], 0.0, 1.0)
win32gui.ReleaseCapture()
win32gui.SendMessage(hwnd, win32con.WM_NCLBUTTONDOWN, win32con.HTCAPTION, 0)
btn_w = 40
spacing = imgui.get_style().item_spacing.x
right_x = imgui.get_window_width() - (btn_w * 3 + spacing * 2 + 10)
imgui.same_line(right_x)
if imgui.button("_", (btn_w, 20)):
win32gui.ShowWindow(hwnd, win32con.SW_MINIMIZE)
imgui.same_line()
if imgui.button("[]", (btn_w, 20)):
win32gui.ShowWindow(hwnd, win32con.SW_MAXIMIZE)
imgui.same_line()
if imgui.button("X", (btn_w, 20)):
win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
imgui.end() imgui.end()
imgui.pop_style_var(3)
def _gui_func(self) -> None: def _gui_func(self) -> None:
self._render_custom_title_bar() self._render_custom_title_bar()
self._render_shader_live_editor()
pushed_prior_tint = False pushed_prior_tint = False
# Render background shader # Render background shader
bg = bg_shader.get_bg() bg = bg_shader.get_bg()
@@ -3949,6 +4024,36 @@ def hello():
def _post_init(self) -> None: def _post_init(self) -> None:
theme.apply_current() theme.apply_current()
def _init_blur_pipeline(self):
if self._blur_pipeline is None:
self._blur_pipeline = BlurPipeline()
ws = imgui.get_io().display_size
fb_scale = imgui.get_io().display_framebuffer_scale.x
if ws.x <= 0 or ws.y <= 0:
return False
if fb_scale <= 0:
fb_scale = 1.0
self._blur_pipeline.setup_fbos(int(ws.x), int(ws.y), fb_scale)
self._blur_pipeline.compile_deepsea_shader()
self._blur_pipeline.compile_blur_shaders()
return True
def _pre_new_frame(self) -> None:
if not self.ui_frosted_glass_enabled:
return
ws = imgui.get_io().display_size
fb_scale = imgui.get_io().display_framebuffer_scale.x
if ws.x <= 0 or ws.y <= 0:
return
if fb_scale <= 0:
fb_scale = 1.0
if self._blur_pipeline is None:
if not self._init_blur_pipeline():
return
import time
t = time.time()
self._blur_pipeline.prepare_global_blur(int(ws.x), int(ws.y), t, fb_scale)
def run(self) -> None: def run(self) -> None:
"""Initializes the ImGui runner and starts the main application loop.""" """Initializes the ImGui runner and starts the main application loop."""
if "--headless" in sys.argv: if "--headless" in sys.argv:
@@ -3963,7 +4068,13 @@ def hello():
theme.load_from_config(self.config) theme.load_from_config(self.config)
self.runner_params = hello_imgui.RunnerParams() self.runner_params = hello_imgui.RunnerParams()
self.runner_params.app_window_params.window_title = "manual slop" self.runner_params.app_window_params.window_title = "manual slop"
self.runner_params.app_window_params.borderless = True
if sys.platform == "win32":
self.runner_params.app_window_params.borderless = True
self.runner_params.app_window_params.borderless_closable = False
self.runner_params.app_window_params.borderless_movable = False
self.runner_params.app_window_params.borderless_resizable = True
self.runner_params.app_window_params.window_geometry.size = (1680, 1200) 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.enable_viewports = getattr(self, "ui_multi_viewport", False)
self.runner_params.imgui_window_params.remember_theme = True self.runner_params.imgui_window_params.remember_theme = True
@@ -3974,15 +4085,16 @@ def hello():
user_scale = theme.get_current_scale() user_scale = theme.get_current_scale()
self.runner_params.dpi_aware_params.dpi_window_size_factor = user_scale self.runner_params.dpi_aware_params.dpi_window_size_factor = user_scale
# Detect Monitor Refresh Rate for capping # Detect Monitor Refresh Rate for capping (Win32 only)
fps_cap = 60.0 fps_cap = 60.0
try: if sys.platform == "win32":
# Use PowerShell to get max refresh rate across all controllers try:
cmd = "powershell -NoProfile -Command \"Get-CimInstance -ClassName Win32_VideoController | Select-Object -ExpandProperty CurrentRefreshRate\"" # Use PowerShell to get max refresh rate across all controllers
out = subprocess.check_output(cmd, shell=True).decode().splitlines() cmd = "powershell -NoProfile -Command \"Get-CimInstance -ClassName Win32_VideoController | Select-Object -ExpandProperty CurrentRefreshRate\""
rates = [float(r.strip()) for r in out if r.strip().isdigit()] out = subprocess.check_output(cmd, shell=True).decode().splitlines()
if rates: fps_cap = max(rates) rates = [float(r.strip()) for r in out if r.strip().isdigit()]
except Exception: pass if rates: fps_cap = max(rates)
except Exception: pass
# Enable idling with monitor refresh rate to effectively cap FPS # Enable idling with monitor refresh rate to effectively cap FPS
self.runner_params.fps_idling.enable_idling = True self.runner_params.fps_idling.enable_idling = True
@@ -3997,6 +4109,8 @@ def hello():
self.runner_params.callbacks.load_additional_fonts = self._load_fonts self.runner_params.callbacks.load_additional_fonts = self._load_fonts
self.runner_params.callbacks.setup_imgui_style = theme.apply_current self.runner_params.callbacks.setup_imgui_style = theme.apply_current
self.runner_params.callbacks.post_init = self._post_init self.runner_params.callbacks.post_init = self._post_init
self.runner_params.callbacks.pre_new_frame = self._pre_new_frame
self.runner_params.callbacks.custom_background = self._render_custom_background
self._fetch_models(self.current_provider) self._fetch_models(self.current_provider)
md_options = markdown_helper.get_renderer().options md_options = markdown_helper.get_renderer().options
immapp.run(self.runner_params, add_ons_params=immapp.AddOnsParams(with_markdown_options=md_options)) immapp.run(self.runner_params, add_ons_params=immapp.AddOnsParams(with_markdown_options=md_options))
+474
View File
@@ -0,0 +1,474 @@
import OpenGL.GL as gl
class ShaderManager:
def __init__(self):
self.program = None
self.bg_program = None
self.pp_program = None
def compile_shader(self, vertex_src: str, fragment_src: str) -> int:
program = gl.glCreateProgram()
def _compile(src, shader_type):
shader = gl.glCreateShader(shader_type)
gl.glShaderSource(shader, src)
gl.glCompileShader(shader)
if not gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS):
info_log = gl.glGetShaderInfoLog(shader)
if hasattr(info_log, "decode"):
info_log = info_log.decode()
raise RuntimeError(f"Shader compilation failed: {info_log}")
return shader
vert_shader = _compile(vertex_src, gl.GL_VERTEX_SHADER)
frag_shader = _compile(fragment_src, gl.GL_FRAGMENT_SHADER)
gl.glAttachShader(program, vert_shader)
gl.glAttachShader(program, frag_shader)
gl.glLinkProgram(program)
if not gl.glGetProgramiv(program, gl.GL_LINK_STATUS):
info_log = gl.glGetProgramInfoLog(program)
if hasattr(info_log, "decode"):
info_log = info_log.decode()
raise RuntimeError(f"Program linking failed: {info_log}")
gl.glDeleteShader(vert_shader)
gl.glDeleteShader(frag_shader)
self.program = program
return program
def update_uniforms(self, uniforms: dict):
if self.program is None:
return
for name, value in uniforms.items():
loc = gl.glGetUniformLocation(self.program, name)
if loc == -1:
continue
if isinstance(value, float):
gl.glUniform1f(loc, value)
elif isinstance(value, int):
gl.glUniform1i(loc, value)
elif isinstance(value, (list, tuple)):
if len(value) == 2:
gl.glUniform2f(loc, value[0], value[1])
elif len(value) == 3:
gl.glUniform3f(loc, value[0], value[1], value[2])
elif len(value) == 4:
gl.glUniform4f(loc, value[0], value[1], value[2], value[3])
def setup_background_shader(self):
vertex_src = """
#version 330 core
const vec2 positions[4] = vec2[](
vec2(-1.0, -1.0),
vec2( 1.0, -1.0),
vec2(-1.0, 1.0),
vec2( 1.0, 1.0)
);
void main() {
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
}
"""
fragment_src = """
#version 330 core
uniform float u_time;
uniform vec2 u_resolution;
out vec4 FragColor;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
vec3 col = 0.5 + 0.5 * cos(u_time + uv.xyx + vec3(0, 2, 4));
FragColor = vec4(col, 1.0);
}
"""
self.bg_program = self.compile_shader(vertex_src, fragment_src)
def render_background(self, width, height, time):
if not self.bg_program:
return
gl.glUseProgram(self.bg_program)
u_time_loc = gl.glGetUniformLocation(self.bg_program, "u_time")
if u_time_loc != -1:
gl.glUniform1f(u_time_loc, float(time))
u_res_loc = gl.glGetUniformLocation(self.bg_program, "u_resolution")
if u_res_loc != -1:
gl.glUniform2f(u_res_loc, float(width), float(height))
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
gl.glUseProgram(0)
def setup_post_process_shader(self):
vertex_src = """
#version 330 core
const vec2 positions[4] = vec2[](
vec2(-1.0, -1.0),
vec2( 1.0, -1.0),
vec2(-1.0, 1.0),
vec2( 1.0, 1.0)
);
const vec2 uvs[4] = vec2[](
vec2(0.0, 0.0),
vec2(1.0, 0.0),
vec2(0.0, 1.0),
vec2(1.0, 1.0)
);
out vec2 v_uv;
void main() {
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
v_uv = uvs[gl_VertexID];
}
"""
fragment_src = """
#version 330 core
in vec2 v_uv;
uniform sampler2D u_texture;
uniform float u_time;
out vec4 FragColor;
void main() {
vec4 color = texture(u_texture, v_uv);
float scanline = sin(v_uv.y * 800.0 + u_time * 2.0) * 0.04;
color.rgb -= scanline;
FragColor = color;
}
"""
self.pp_program = self.compile_shader(vertex_src, fragment_src)
def render_post_process(self, texture_id, width, height, time):
if not self.pp_program:
return
gl.glUseProgram(self.pp_program)
gl.glActiveTexture(gl.GL_TEXTURE0)
gl.glBindTexture(gl.GL_TEXTURE_2D, texture_id)
u_tex_loc = gl.glGetUniformLocation(self.pp_program, "u_texture")
if u_tex_loc != -1:
gl.glUniform1i(u_tex_loc, 0)
u_time_loc = gl.glGetUniformLocation(self.pp_program, "u_time")
if u_time_loc != -1:
gl.glUniform1f(u_time_loc, float(time))
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
class BlurPipeline:
def __init__(self):
self.scene_fbo: int | None = None
self.scene_tex: int | None = None
self.blur_fbo_a: int | None = None
self.blur_tex_a: int | None = None
self.blur_fbo_b: int | None = None
self.blur_tex_b: int | None = None
self.h_blur_program: int | None = None
self.v_blur_program: int | None = None
self.deepsea_program: int | None = None
self._quad_vao: int | None = None
self._fb_width: int = 0
self._fb_height: int = 0
self._fb_scale: int = 1
def _compile_shader(self, vertex_src: str, fragment_src: str) -> int:
program = gl.glCreateProgram()
def _compile(src, shader_type):
shader = gl.glCreateShader(shader_type)
gl.glShaderSource(shader, src)
gl.glCompileShader(shader)
if not gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS):
info_log = gl.glGetShaderInfoLog(shader)
if hasattr(info_log, "decode"):
info_log = info_log.decode()
raise RuntimeError(f"Shader compilation failed: {info_log}")
return shader
vert_shader = _compile(vertex_src, gl.GL_VERTEX_SHADER)
frag_shader = _compile(fragment_src, gl.GL_FRAGMENT_SHADER)
gl.glAttachShader(program, vert_shader)
gl.glAttachShader(program, frag_shader)
gl.glLinkProgram(program)
if not gl.glGetProgramiv(program, gl.GL_LINK_STATUS):
info_log = gl.glGetProgramInfoLog(program)
if hasattr(info_log, "decode"):
info_log = info_log.decode()
raise RuntimeError(f"Program linking failed: {info_log}")
gl.glDeleteShader(vert_shader)
gl.glDeleteShader(frag_shader)
return program
def _create_fbo(self, width: int, height: int) -> tuple[int, int]:
if width <= 0 or height <= 0:
raise ValueError(f"Invalid FBO dimensions: {width}x{height}")
tex = gl.glGenTextures(1)
gl.glBindTexture(gl.GL_TEXTURE_2D, tex)
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA8, 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.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE)
fbo = gl.glGenFramebuffers(1)
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, fbo)
gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D, tex, 0)
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
return fbo, tex
def _create_quad_vao(self) -> int:
import ctypes
vao = gl.glGenVertexArrays(1)
gl.glBindVertexArray(vao)
vertices = (ctypes.c_float * 16)(
-1.0, -1.0, 0.0, 0.0,
1.0, -1.0, 1.0, 0.0,
-1.0, 1.0, 0.0, 1.0,
1.0, 1.0, 1.0, 1.0
)
vbo = gl.glGenBuffers(1)
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vbo)
gl.glBufferData(gl.GL_ARRAY_BUFFER, ctypes.sizeof(vertices), vertices, gl.GL_STATIC_DRAW)
gl.glEnableVertexAttribArray(0)
gl.glVertexAttribPointer(0, 2, gl.GL_FLOAT, gl.GL_FALSE, 16, None)
gl.glEnableVertexAttribArray(1)
gl.glVertexAttribPointer(1, 2, gl.GL_FLOAT, gl.GL_FALSE, 16, ctypes.c_void_p(8))
gl.glBindVertexArray(0)
return vao
def setup_fbos(self, width: int, height: int, fb_scale: float = 1.0):
scale = max(1, int(fb_scale))
blur_w = max(1, (width * scale) // 4)
blur_h = max(1, (height * scale) // 4)
self._fb_width = blur_w
self._fb_height = blur_h
self._fb_scale = scale
scene_w = width * scale
scene_h = height * scale
self.scene_fbo, self.scene_tex = self._create_fbo(scene_w, scene_h)
self.blur_fbo_a, self.blur_tex_a = self._create_fbo(blur_w, blur_h)
self.blur_fbo_b, self.blur_tex_b = self._create_fbo(blur_w, blur_h)
def compile_blur_shaders(self):
vert_src = """
#version 330 core
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texcoord;
out vec2 v_uv;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_uv = a_texcoord;
}
"""
h_frag_src = """
#version 330 core
in vec2 v_uv;
uniform sampler2D u_texture;
uniform vec2 u_texel_size;
out vec4 FragColor;
void main() {
vec2 offset = vec2(u_texel_size.x, 0.0);
vec4 sum = vec4(0.0);
sum += texture(u_texture, v_uv - offset * 6.0) * 0.0152;
sum += texture(u_texture, v_uv - offset * 5.0) * 0.0300;
sum += texture(u_texture, v_uv - offset * 4.0) * 0.0525;
sum += texture(u_texture, v_uv - offset * 3.0) * 0.0812;
sum += texture(u_texture, v_uv - offset * 2.0) * 0.1110;
sum += texture(u_texture, v_uv - offset * 1.0) * 0.1342;
sum += texture(u_texture, v_uv) * 0.1432;
sum += texture(u_texture, v_uv + offset * 1.0) * 0.1342;
sum += texture(u_texture, v_uv + offset * 2.0) * 0.1110;
sum += texture(u_texture, v_uv + offset * 3.0) * 0.0812;
sum += texture(u_texture, v_uv + offset * 4.0) * 0.0525;
sum += texture(u_texture, v_uv + offset * 5.0) * 0.0300;
sum += texture(u_texture, v_uv + offset * 6.0) * 0.0152;
FragColor = sum;
}
"""
v_frag_src = """
#version 330 core
in vec2 v_uv;
uniform sampler2D u_texture;
uniform vec2 u_texel_size;
out vec4 FragColor;
void main() {
vec2 offset = vec2(0.0, u_texel_size.y);
vec4 sum = vec4(0.0);
sum += texture(u_texture, v_uv - offset * 6.0) * 0.0152;
sum += texture(u_texture, v_uv - offset * 5.0) * 0.0300;
sum += texture(u_texture, v_uv - offset * 4.0) * 0.0525;
sum += texture(u_texture, v_uv - offset * 3.0) * 0.0812;
sum += texture(u_texture, v_uv - offset * 2.0) * 0.1110;
sum += texture(u_texture, v_uv - offset * 1.0) * 0.1342;
sum += texture(u_texture, v_uv) * 0.1432;
sum += texture(u_texture, v_uv + offset * 1.0) * 0.1342;
sum += texture(u_texture, v_uv + offset * 2.0) * 0.1110;
sum += texture(u_texture, v_uv + offset * 3.0) * 0.0812;
sum += texture(u_texture, v_uv + offset * 4.0) * 0.0525;
sum += texture(u_texture, v_uv + offset * 5.0) * 0.0300;
sum += texture(u_texture, v_uv + offset * 6.0) * 0.0152;
FragColor = sum;
}
"""
self.h_blur_program = self._compile_shader(vert_src, h_frag_src)
self.v_blur_program = self._compile_shader(vert_src, v_frag_src)
def compile_deepsea_shader(self):
vert_src = """
#version 330 core
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texcoord;
out vec2 v_uv;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_uv = a_texcoord;
}
"""
frag_src = """
#version 330 core
in vec2 v_uv;
uniform float u_time;
uniform vec2 u_resolution;
out vec4 FragColor;
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
float a = hash(i);
float b = hash(i + vec2(1.0, 0.0));
float c = hash(i + vec2(0.0, 1.0));
float d = hash(i + vec2(1.0, 1.0));
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}
float fbm(vec2 p) {
float v = 0.0;
float a = 0.5;
for (int i = 0; i < 4; i++) {
v += a * noise(p);
p *= 2.0;
a *= 0.5;
}
return v;
}
void main() {
vec2 uv = v_uv;
float t = u_time * 0.3;
vec3 col = vec3(0.01, 0.05, 0.12);
for (int i = 0; i < 3; i++) {
float phase = t * (0.1 + float(i) * 0.05);
vec2 blob_uv = uv + vec2(sin(phase), cos(phase * 0.8)) * 0.3;
float blob = fbm(blob_uv * 3.0 + t * 0.2);
col = mix(col, vec3(0.02, 0.20, 0.40), blob * 0.4);
}
float line_alpha = 0.0;
for (int i = 0; i < 12; i++) {
float fi = float(i);
float offset = mod(t * 15.0 + fi * (u_resolution.x / 12.0), u_resolution.x);
float line_x = offset / u_resolution.x;
float dist = abs(uv.x - line_x);
float alpha = smoothstep(0.02, 0.0, dist) * (0.1 + 0.05 * sin(t + fi));
line_alpha += alpha;
}
col += vec3(0.04, 0.35, 0.55) * line_alpha;
float vignette = 1.0 - length(uv - 0.5) * 0.8;
col *= vignette;
FragColor = vec4(col, 1.0);
}
"""
self.deepsea_program = self._compile_shader(vert_src, frag_src)
self._quad_vao = self._create_quad_vao()
def render_deepsea_to_fbo(self, width: int, height: int, time: float):
if not self.deepsea_program or not self.scene_fbo or not self._quad_vao:
return
scene_w = width * self._fb_scale
scene_h = height * self._fb_scale
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.scene_fbo)
gl.glViewport(0, 0, scene_w, scene_h)
gl.glClearColor(0.01, 0.05, 0.12, 1.0)
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
gl.glUseProgram(self.deepsea_program)
u_time_loc = gl.glGetUniformLocation(self.deepsea_program, "u_time")
if u_time_loc != -1:
gl.glUniform1f(u_time_loc, time)
u_res_loc = gl.glGetUniformLocation(self.deepsea_program, "u_resolution")
if u_res_loc != -1:
gl.glUniform2f(u_res_loc, float(scene_w), float(scene_h))
gl.glBindVertexArray(self._quad_vao)
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
gl.glBindVertexArray(0)
gl.glUseProgram(0)
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
def _render_quad(self, program: int, src_tex: int, texel_size: tuple[float, float]):
gl.glUseProgram(program)
gl.glActiveTexture(gl.GL_TEXTURE0)
gl.glBindTexture(gl.GL_TEXTURE_2D, src_tex)
u_tex = gl.glGetUniformLocation(program, "u_texture")
if u_tex != -1:
gl.glUniform1i(u_tex, 0)
u_ts = gl.glGetUniformLocation(program, "u_texel_size")
if u_ts != -1:
gl.glUniform2f(u_ts, texel_size[0], texel_size[1])
gl.glBindVertexArray(self._quad_vao)
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
gl.glBindVertexArray(0)
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
gl.glUseProgram(0)
def prepare_blur(self, width: int, height: int, time: float):
if not self.h_blur_program or not self.v_blur_program:
return
if not self.blur_fbo_a or not self.blur_fbo_b:
return
blur_w = max(1, self._fb_width)
blur_h = max(1, self._fb_height)
texel_x = 1.0 / float(blur_w)
texel_y = 1.0 / float(blur_h)
gl.glViewport(0, 0, blur_w, blur_h)
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo_a)
gl.glClearColor(0.0, 0.0, 0.0, 0.0)
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
self._render_quad(self.h_blur_program, self.scene_tex, (texel_x, texel_y))
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo_b)
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
self._render_quad(self.v_blur_program, self.blur_tex_a, (texel_x, texel_y))
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
restore_w = width * self._fb_scale
restore_h = height * self._fb_scale
gl.glViewport(0, 0, restore_w, restore_h)
def prepare_global_blur(self, width: int, height: int, time: float, fb_scale: float = 1.0):
if not self.scene_fbo:
if self._fb_scale != int(fb_scale):
self.setup_fbos(width, height, fb_scale)
self.render_deepsea_to_fbo(width, height, time)
self.prepare_blur(width, height, time)
def get_blur_texture(self) -> int | None:
return self.blur_tex_b
def cleanup(self):
fbos = [f for f in [self.scene_fbo, self.blur_fbo_a, self.blur_fbo_b] if f is not None]
texs = [t for t in [self.scene_tex, self.blur_tex_a, self.blur_tex_b] if t is not None]
progs = [p for p in [self.h_blur_program, self.v_blur_program, self.deepsea_program] if p is not None]
if fbos:
gl.glDeleteFramebuffers(len(fbos), fbos)
if texs:
gl.glDeleteTextures(len(texs), texs)
if progs:
for p in progs:
gl.glDeleteProgram(p)
if self._quad_vao:
gl.glDeleteVertexArrays(1, [self._quad_vao])
self.scene_fbo = None
self.scene_tex = None
self.blur_fbo_a = None
self.blur_tex_a = None
self.blur_fbo_b = None
self.blur_tex_b = None
self.h_blur_program = None
self.v_blur_program = None
self.deepsea_program = None
self._quad_vao = None
+19
View File
@@ -268,6 +268,12 @@ _current_palette: str = "DPG Default"
_current_font_path: str = "" _current_font_path: str = ""
_current_font_size: float = 14.0 _current_font_size: float = 14.0
_current_scale: float = 1.0 _current_scale: float = 1.0
_shader_config: dict[str, Any] = {
"crt": False,
"bloom": False,
"bg": "none",
"custom_window_frame": False,
}
# ------------------------------------------------------------------ public API # ------------------------------------------------------------------ public API
@@ -286,6 +292,14 @@ def get_current_font_size() -> float:
def get_current_scale() -> float: def get_current_scale() -> float:
return _current_scale return _current_scale
def get_shader_config(key: str) -> Any:
"""Get a specific shader configuration value."""
return _shader_config.get(key)
def get_window_frame_config() -> bool:
"""Get the window frame configuration."""
return _shader_config.get("custom_window_frame", False)
def get_palette_colours(name: str) -> dict[str, Any]: def get_palette_colours(name: str) -> dict[str, Any]:
"""Return a copy of the colour dict for the named palette.""" """Return a copy of the colour dict for the named palette."""
return dict(_PALETTES.get(name, {})) return dict(_PALETTES.get(name, {}))
@@ -388,4 +402,9 @@ def load_from_config(config: dict[str, Any]) -> None:
if font_path: if font_path:
apply_font(font_path, font_size) apply_font(font_path, font_size)
set_scale(scale) set_scale(scale)
global _shader_config
_shader_config["crt"] = t.get("shader_crt", False)
_shader_config["bloom"] = t.get("shader_bloom", False)
_shader_config["bg"] = t.get("shader_bg", "none")
_shader_config["custom_window_frame"] = t.get("custom_window_frame", False)
+33
View File
@@ -0,0 +1,33 @@
import pytest
from unittest.mock import patch, MagicMock
def test_dynamic_background_rendering():
# Mock OpenGL before importing
with patch("src.shader_manager.gl") as mock_gl:
from src.shader_manager import ShaderManager
# Setup mock return values
mock_gl.glCreateProgram.return_value = 1
mock_gl.glCreateShader.return_value = 2
mock_gl.glGetShaderiv.return_value = 1 # GL_TRUE
mock_gl.glGetProgramiv.return_value = 1 # GL_TRUE
mock_gl.glGetUniformLocation.return_value = 10
manager = ShaderManager()
manager.setup_background_shader()
# Verify background program was created
assert manager.bg_program == 1
assert mock_gl.glCreateProgram.called
# Render background
manager.render_background(800, 600, 1.0)
# Verify OpenGL calls
mock_gl.glUseProgram.assert_any_call(1)
mock_gl.glDrawArrays.assert_called_with(mock_gl.GL_TRIANGLE_STRIP, 0, 4)
mock_gl.glUseProgram.assert_any_call(0)
# Verify uniforms were updated
mock_gl.glUniform1f.assert_called()
mock_gl.glUniform2f.assert_called()
+26
View File
@@ -0,0 +1,26 @@
import pytest
from unittest.mock import patch, MagicMock
from src.gui_2 import App
def test_frosted_glass_disabled():
with patch("src.gui_2.imgui") as mock_imgui:
with patch("src.gui_2.gl") as mock_gl:
app = App()
app.ui_frosted_glass_enabled = False
app._render_frosted_background((0, 0), (100, 100))
assert app._blur_pipeline is None
mock_gl.glEnable.assert_not_called()
mock_gl.glBlendFunc.assert_not_called()
mock_gl.glBindTexture.assert_not_called()
mock_gl.glBegin.assert_not_called()
mock_gl.glEnd.assert_not_called()
mock_gl.glDisable.assert_not_called()
mock_imgui.get_io().display_size.assert_not_called()
mock_imgui.get_io().display_framebuffer_scale.assert_not_called()
mock_imgui.get_window_draw_list.assert_not_called()
mock_imgui.get_window_pos.assert_not_called()
mock_imgui.get_window_size.assert_not_called()
mock_imgui.get_color_u32.assert_not_called()
mock_imgui.push_texture_id.assert_not_called()
mock_imgui.pop_texture_id.assert_not_called()
+79
View File
@@ -26,5 +26,84 @@ def test_gui2_old_windows_removed_from_show_windows(app_instance: App) -> None:
"Provider", "System Prompts", "Provider", "System Prompts",
"Comms History" "Comms History"
] ]
for old_win in old_windows:
from src.gui_2 import App
def test_gui2_hubs_exist_in_show_windows(app_instance: App) -> None:
expected_hubs = [
"Context Hub",
"AI Settings",
"Discussion Hub",
"Operations Hub",
"Files & Media",
"Theme",
]
for hub in expected_hubs:
assert hub in app_instance.show_windows, f"Expected hub window '{hub}' not found in show_windows"
def test_gui2_old_windows_removed_from_show_windows(app_instance: App) -> None:
old_windows = [
"Projects", "Files", "Screenshots",
"Provider", "System Prompts",
"Comms History"
]
for old_win in old_windows: for old_win in old_windows:
assert old_win not in app_instance.show_windows, f"Old window '{old_win}' should have been removed from show_windows" assert old_win not in app_instance.show_windows, f"Old window '{old_win}' should have been removed from show_windows"
def test_frosted_glass_disabled():
with patch("src.gui_2.imgui"):
app = App()
app.ui_frosted_glass_enabled = False
app._render_frosted_background((0, 0), (100, 100))
assert not app._blur_pipeline is None or not app._blur_pipeline.prepare_global_blur.called
imgui.get_io().display_size.assert_not_called()
imgui.get_io().display_framebuffer_scale.assert_not_called()
imgui.get_window_draw_list.assert_not_called()
imgui.get_window_pos.assert_not_called()
imgui.get_window_size.assert_not_called()
imgui.get_color_u32.assert_not_called()
imgui.push_texture_id.assert_not_called()
imgui.pop_texture_id.assert_not_called()
dl.add_image_quad.assert_not_called()
imgui.pop_texture_id.assert_not_called()
gl.glEnable.assert_not_called()
gl.glBlendFunc.assert_not_called()
gl.glBindTexture.assert_not_called()
gl.glBegin.assert_not_called()
gl.glEnd.assert_not_called()
gl.glDisable.assert_not_called()
gl.glUnbindTexture.assert_not_called()
gl.glDeleteTexture.assert_not_called()
gl.glDisable.assert_not_called()
def test_frosted_glass_enabled():
with patch("src.gui_2.imgui"):
with patch("src.gui_2.BlurPipeline") as mock_blur:
app = App()
app.ui_frosted_glass_enabled = True
app._blur_pipeline = mock_blur
mock_blur.return_value = BlurPipeline()
mock_blur.prepare_global_blur.return_value = None
mock_blur.get_blur_texture.return_value = 123
imgui.get_io().display_size = MagicMock(x=800.0, y=600.0)
imgui.get_io().display_framebuffer_scale = MagicMock(x=1.0, y=1.0)
imgui.get_window_draw_list.return_value = MagicMock()
imgui.get_window_pos.return_value = (100, 200)
imgui.get_window_size.return_value = (300, 400)
imgui.get_color_u32.return_value = 0xFFFFFFFF
dl = MagicMock()
imgui.get_window_draw_list.return_value = dl
app._render_frosted_background((100, 200), (300, 400))
mock_blur.get_blur_texture.assert_called_once()
assert dl.add_callback_texture_id.called
assert dl.add_callback_quadsDrawElements.called
imgui.push_texture_id.assert_called()
imgui.pop_texture_id.assert_called()
gl.glEnable.assert_called()
gl.glBlendFunc.assert_called()
gl.glBindTexture.assert_called()
gl.glBegin.assert_called()
gl.glEnd.assert_called()
gl.glDisable.assert_called()
gl.glUnbindTexture.assert_called()
gl.glDeleteTexture.assert_not_called()
+18 -6
View File
@@ -9,22 +9,34 @@ def test_gui_window_controls_minimize_maximize_close():
with patch("src.gui_2.win32gui") as mock_win32gui, \ with patch("src.gui_2.win32gui") as mock_win32gui, \
patch("src.gui_2.win32con") as mock_win32con, \ patch("src.gui_2.win32con") as mock_win32con, \
patch("src.gui_2.imgui") as mock_imgui: patch("src.gui_2.imgui") as mock_imgui, \
patch("ctypes.pythonapi.PyCapsule_GetPointer") as mock_get_pointer:
# Setup mock for HWND # Setup mock for HWND
mock_viewport = MagicMock() mock_viewport = MagicMock()
mock_viewport.platform_handle = 12345 mock_viewport.platform_handle_raw = "mock_capsule"
mock_imgui.get_main_viewport.return_value = mock_viewport mock_imgui.get_main_viewport.return_value = mock_viewport
mock_get_pointer.return_value = 12345
mock_imgui.get_window_width.return_value = 800.0
mock_imgui.get_cursor_pos_x.return_value = 100.0
mock_imgui.get_io().display_size.x = 800.0
mock_style = MagicMock()
mock_style.item_spacing.x = 4.0
mock_imgui.get_style.return_value = mock_style
# Setup mock for buttons to simulate clicks # Setup mock for buttons to simulate clicks
# Let's say _render_custom_title_bar uses imgui.button # Let's say _render_custom_title_bar uses imgui.button
# We will test the close button logic # We will test the close button logic
# Since it's UI code, we just simulate the conditions # Since it's UI code, we just simulate the conditions
mock_imgui.button.return_value = True # Simulate all buttons being clicked mock_imgui.button.return_value = True # Simulate all buttons being clicked
# Call the method (to be implemented) # Avoid hitting actual menu logic that requires real runner_params
app._render_custom_title_bar() mock_imgui.begin_menu.return_value = False
app.runner_params = MagicMock()
# Call the method (now in _show_menus)
app._show_menus()
# Verify that win32gui calls are made for minimize, maximize, close # Verify that win32gui calls are made for minimize, maximize, close
# Since all buttons returned True, all actions should be triggered in this dummy test # Since all buttons returned True, all actions should be triggered in this dummy test
assert mock_win32gui.ShowWindow.called assert mock_win32gui.ShowWindow.called
+53
View File
@@ -0,0 +1,53 @@
import unittest
from unittest.mock import MagicMock, patch
import sys
# Mock OpenGL.GL before importing ShaderManager
gl_mock = MagicMock()
# Setup some constants
gl_mock.GL_VERTEX_SHADER = 0x8B31
gl_mock.GL_FRAGMENT_SHADER = 0x8B30
gl_mock.GL_COMPILE_STATUS = 0x8B81
gl_mock.GL_LINK_STATUS = 0x8B82
gl_mock.GL_TEXTURE0 = 0x84C0
gl_mock.GL_TEXTURE_2D = 0x0DE1
gl_mock.GL_TRIANGLE_STRIP = 0x0005
opengl_mock = MagicMock()
sys.modules['OpenGL'] = opengl_mock
sys.modules['OpenGL.GL'] = gl_mock
opengl_mock.GL = gl_mock
from src.shader_manager import ShaderManager
class TestPostProcess(unittest.TestCase):
def setUp(self):
gl_mock.reset_mock()
# Mock return values for shader compilation
gl_mock.glCreateProgram.return_value = 1
gl_mock.glCreateShader.return_value = 2
gl_mock.glGetShaderiv.return_value = 1 # GL_TRUE
gl_mock.glGetProgramiv.return_value = 1 # GL_TRUE
gl_mock.glGetUniformLocation.return_value = 10
def test_setup_post_process_shader(self):
sm = ShaderManager()
sm.setup_post_process_shader()
self.assertEqual(sm.pp_program, 1)
gl_mock.glCreateProgram.assert_called()
gl_mock.glLinkProgram.assert_called_with(1)
def test_render_post_process(self):
sm = ShaderManager()
sm.pp_program = 1
sm.render_post_process(texture_id=5, width=800, height=600, time=1.0)
gl_mock.glUseProgram.assert_any_call(1)
gl_mock.glActiveTexture.assert_called_with(gl_mock.GL_TEXTURE0)
gl_mock.glBindTexture.assert_any_call(gl_mock.GL_TEXTURE_2D, 5)
gl_mock.glUniform1f.assert_called()
gl_mock.glDrawArrays.assert_called_with(gl_mock.GL_TRIANGLE_STRIP, 0, 4)
gl_mock.glUseProgram.assert_any_call(0)
if __name__ == '__main__':
unittest.main()
+23
View File
@@ -0,0 +1,23 @@
import pytest
from unittest.mock import patch
from src import theme
def test_shader_config_parsing():
config = {
"theme": {
"shader_crt": True,
"shader_bloom": False,
"shader_bg": "noise",
"custom_window_frame": True
}
}
with patch("src.theme.apply"), \
patch("src.theme.apply_font"), \
patch("src.theme.set_scale"):
theme.load_from_config(config)
assert theme.get_shader_config("crt") is True
assert theme.get_shader_config("bloom") is False
assert theme.get_shader_config("bg") == "noise"
assert theme.get_window_frame_config() is True
+14
View File
@@ -0,0 +1,14 @@
import pytest
from unittest.mock import patch, MagicMock
def test_shader_live_editor_renders():
from src.gui_2 import App
app = App()
app.show_windows["Shader Editor"] = True
with patch("src.gui_2.imgui") as mock_imgui:
mock_imgui.begin.return_value = (True, True)
mock_imgui.slider_float.return_value = (False, 1.0)
app._render_shader_live_editor()
assert mock_imgui.begin.called
assert mock_imgui.end.called
+217
View File
@@ -0,0 +1,217 @@
import pytest
from unittest.mock import patch, MagicMock
def test_blur_pipeline_import():
with patch("src.shader_manager.gl") as mock_gl:
from src.shader_manager import BlurPipeline
pipeline = BlurPipeline()
assert pipeline is not None
assert pipeline.scene_fbo is None
assert pipeline.blur_fbo_a is None
assert pipeline.blur_fbo_b is None
assert pipeline.scene_tex is None
assert pipeline.blur_tex_a is None
assert pipeline.blur_tex_b is None
assert pipeline.h_blur_program is None
assert pipeline.v_blur_program is None
def test_blur_pipeline_setup_fbos():
with patch("src.shader_manager.gl") as mock_gl:
tex_counter = iter([10, 20, 30])
fbo_counter = iter([1, 2, 3])
mock_gl.glGenTextures.side_effect = lambda n: next(tex_counter)
mock_gl.glGenFramebuffers.side_effect = lambda n: next(fbo_counter)
from src.shader_manager import BlurPipeline
pipeline = BlurPipeline()
pipeline.setup_fbos(800, 600)
assert mock_gl.glGenFramebuffers.called
assert mock_gl.glGenTextures.called
assert pipeline.scene_fbo is not None
assert pipeline.blur_fbo_a is not None
assert pipeline.blur_fbo_b is not None
def test_blur_pipeline_compile_shaders():
with patch("src.shader_manager.gl") as mock_gl:
mock_gl.glCreateProgram.return_value = 100
mock_gl.glCreateShader.return_value = 200
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
from src.shader_manager import BlurPipeline
pipeline = BlurPipeline()
pipeline.compile_blur_shaders()
assert mock_gl.glCreateProgram.called
assert pipeline.h_blur_program is not None
assert pipeline.v_blur_program is not None
def test_blur_pipeline_wide_tap_distribution():
with patch("src.shader_manager.gl") as mock_gl:
mock_gl.glCreateProgram.return_value = 100
mock_gl.glCreateShader.return_value = 200
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
from src.shader_manager import BlurPipeline
pipeline = BlurPipeline()
pipeline.compile_blur_shaders()
assert mock_gl.glShaderSource.called
shader_sources = [call.args[1] for call in mock_gl.glShaderSource.call_args_list]
frag_sources = [s for s in shader_sources if 'texture(' in s and 'offset' in s]
assert len(frag_sources) >= 2
for src in frag_sources:
texture_calls = src.count('texture(u_texture')
assert texture_calls >= 11, f"Expected at least 11 texture samples for wide tap distribution, got {texture_calls}"
def test_blur_pipeline_render_deepsea_to_fbo():
with patch("src.shader_manager.gl") as mock_gl:
tex_counter = iter([10, 20, 30])
fbo_counter = iter([1, 2, 3])
mock_gl.glGenTextures.side_effect = lambda n: next(tex_counter)
mock_gl.glGenFramebuffers.side_effect = lambda n: next(fbo_counter)
mock_gl.glCreateProgram.return_value = 300
mock_gl.glCreateShader.return_value = 400
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
from src.shader_manager import BlurPipeline
pipeline = BlurPipeline()
pipeline.setup_fbos(800, 600)
pipeline.compile_deepsea_shader()
pipeline.render_deepsea_to_fbo(800, 600, 0.0)
assert mock_gl.glBindFramebuffer.called
assert mock_gl.glUseProgram.called
assert mock_gl.glDrawArrays.called
def test_blur_pipeline_deepsea_shader_compilation():
with patch("src.shader_manager.gl") as mock_gl:
mock_gl.glCreateProgram.return_value = 500
mock_gl.glCreateShader.return_value = 600
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
from src.shader_manager import BlurPipeline
pipeline = BlurPipeline()
pipeline.compile_deepsea_shader()
assert mock_gl.glCreateProgram.called
assert pipeline.deepsea_program is not None
def test_blur_pipeline_prepare_blur():
with patch("src.shader_manager.gl") as mock_gl:
mock_gl.glGenFramebuffers.return_value = None
mock_gl.glGenTextures.return_value = None
from src.shader_manager import BlurPipeline
pipeline = BlurPipeline()
pipeline.scene_fbo = 1
pipeline.scene_tex = 10
pipeline.blur_fbo_a = 2
pipeline.blur_tex_a = 20
pipeline.blur_fbo_b = 3
pipeline.blur_tex_b = 30
pipeline.h_blur_program = 100
pipeline.v_blur_program = 101
pipeline.prepare_blur(800, 600, 0.0)
assert mock_gl.glBindFramebuffer.called
assert mock_gl.glUseProgram.called
def test_blur_pipeline_prepare_global_blur():
with patch("src.shader_manager.gl") as mock_gl:
tex_counter = iter([10, 20, 30])
fbo_counter = iter([1, 2, 3])
mock_gl.glGenTextures.side_effect = lambda n: next(tex_counter)
mock_gl.glGenFramebuffers.side_effect = lambda n: next(fbo_counter)
mock_gl.glCreateProgram.return_value = 100
mock_gl.glCreateShader.return_value = 200
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
from src.shader_manager import BlurPipeline
pipeline = BlurPipeline()
pipeline.setup_fbos(800, 600)
pipeline.compile_deepsea_shader()
pipeline.compile_blur_shaders()
pipeline.prepare_global_blur(800, 600, 0.0)
assert mock_gl.glBindFramebuffer.called
assert mock_gl.glUseProgram.called
assert mock_gl.glViewport.called
blur_tex = pipeline.get_blur_texture()
assert blur_tex is not None
assert blur_tex == 30
def test_blur_pipeline_high_dpi_scaling():
with patch("src.shader_manager.gl") as mock_gl:
tex_counter = iter([10, 20, 30])
fbo_counter = iter([1, 2, 3])
mock_gl.glGenTextures.side_effect = lambda n: next(tex_counter)
mock_gl.glGenFramebuffers.side_effect = lambda n: next(fbo_counter)
mock_gl.glCreateProgram.return_value = 100
mock_gl.glCreateShader.return_value = 200
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
from src.shader_manager import BlurPipeline
pipeline = BlurPipeline()
fb_scale = 2.0
pipeline.setup_fbos(800, 600, fb_scale)
assert pipeline._fb_width == (800 * int(fb_scale)) // 4
assert pipeline._fb_height == (600 * int(fb_scale)) // 4
assert pipeline._fb_scale == int(fb_scale)
def test_blur_pipeline_cleanup():
with patch("src.shader_manager.gl") as mock_gl:
from src.shader_manager import BlurPipeline
pipeline = BlurPipeline()
pipeline.scene_fbo = 1
pipeline.blur_fbo_a = 2
pipeline.blur_fbo_b = 3
pipeline.scene_tex = 10
pipeline.blur_tex_a = 20
pipeline.blur_tex_b = 30
pipeline.h_blur_program = 100
pipeline.v_blur_program = 101
pipeline.cleanup()
assert mock_gl.glDeleteFramebuffers.called
assert mock_gl.glDeleteTextures.called
assert mock_gl.glDeleteProgram.called
def test_shader_manager_initialization_and_compilation():
# Import inside test to allow patching OpenGL before import if needed
# In this case, we patch the OpenGL.GL functions used by ShaderManager
with patch("src.shader_manager.gl") as mock_gl:
mock_gl.glCreateProgram.return_value = 1
mock_gl.glCreateShader.return_value = 2
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
from src.shader_manager import ShaderManager
manager = ShaderManager()
# Basic vertex and fragment shader source
vert_src = "void main() {}"
frag_src = "void main() {}"
program_id = manager.compile_shader(vert_src, frag_src)
assert program_id == 1
assert mock_gl.glCreateProgram.called
assert mock_gl.glCreateShader.called
def test_shader_manager_uniform_update():
# Mock OpenGL.GL functions
with patch("src.shader_manager.gl") as mock_gl:
from src.shader_manager import ShaderManager
manager = ShaderManager()
# Set a mock program ID
manager.program = 1
# Mock glGetUniformLocation to return some valid locations
# u_time -> 10, u_resolution -> 20
def mock_get_loc(prog, name):
if name == "u_time": return 10
if name == "u_resolution": return 20
return -1
mock_gl.glGetUniformLocation.side_effect = mock_get_loc
# Call the method
manager.update_uniforms({"u_time": 1.5, "u_resolution": (800, 600)})
# Assert calls
mock_gl.glGetUniformLocation.assert_any_call(1, "u_time")
mock_gl.glGetUniformLocation.assert_any_call(1, "u_resolution")
mock_gl.glUniform1f.assert_called_once_with(10, 1.5)
mock_gl.glUniform2f.assert_called_once_with(20, 800, 600)