58 Commits

Author SHA1 Message Date
ed 70bfcc61c2 update tracks 2026-03-13 18:32:52 -04:00
ed 976a3b63e0 checkpoint: ai went retarted 2026-03-13 18:31:02 -04:00
ed 0e0b185290 checkpoint: progressing on frosted glass panels 2026-03-13 16:09:52 -04:00
ed 5b196dccf0 checkpoint frosted glass 2026-03-13 15:33:40 -04:00
ed 1771c32006 docs(conductor): Synchronize docs for track 'Frosted Glass Background Effect' 2026-03-13 15:07:47 -04:00
ed 6a39f440d9 chore(conductor): Mark track 'Frosted Glass Background Effect' as complete 2026-03-13 15:05:26 -04:00
ed cecbe2245a feat(gui): Implement frosted glass UI controls and config persistence 2026-03-13 15:04:49 -04:00
ed ad9ffb68f5 conductor(plan): Mark Phase 2 as complete 2026-03-13 14:50:02 -04:00
ed e9b7875de5 conductor(checkpoint): Checkpoint end of Phase 2 2026-03-13 14:49:38 -04:00
ed 3d5c768598 conductor(plan): Mark Phase 2 capture pipeline as complete 2026-03-13 14:49:06 -04:00
ed f297e7a3bd feat(shaders): Implement FBO capture lifecycle in ShaderManager 2026-03-13 14:48:43 -04:00
ed 4a705a8060 conductor(plan): Mark Phase 1 as complete 2026-03-13 14:46:26 -04:00
ed 55f3bd87e2 conductor(checkpoint): Checkpoint end of Phase 1 2026-03-13 14:46:02 -04:00
ed e56d4af097 conductor(plan): Mark Phase 1 shader implementation as complete 2026-03-13 14:45:40 -04:00
ed 1328bc159b feat(shaders): Implement FrostedGlassShader compilation in ShaderManager 2026-03-13 14:45:16 -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
ed 356d5f3618 conductor(plan): Mark Phase 2 as complete 2026-03-13 12:24:03 -04:00
ed b9ca69fbae conductor(checkpoint): Checkpoint end of Phase 2 2026-03-13 12:23:40 -04:00
ed 3f4ae21708 conductor(plan): Mark Phase 2 implementation as complete 2026-03-13 12:20:59 -04:00
ed 59d7368bd7 feat(gui): Implement custom title bar and window controls 2026-03-13 12:20:37 -04:00
ed 02fca1f8ba test(gui): Verify borderless window mode is configured 2026-03-13 12:05:49 -04:00
ed 841e54aa47 conductor(plan): Mark Phase 1 as complete 2026-03-13 11:58:43 -04:00
ed 815ee55981 conductor(checkpoint): Checkpoint end of Phase 1 2026-03-13 11:58:16 -04:00
ed 4e5ec31876 conductor(plan): Mark Phase 1 investigation tasks as complete 2026-03-13 11:57:46 -04:00
ed 5f4da366f1 docs(architecture): Add custom shaders and window frame architecture document 2026-03-13 11:57:22 -04:00
ed 82722999a8 ai put it in the wrong spot 2026-03-12 21:47:57 -04:00
ed ad93a294fb chore(conductor): Add new track 'Optimization pass for Data-Oriented Python heuristics' 2026-03-12 21:47:16 -04:00
ed b677228a96 get prior session history properly working. 2026-03-12 21:38:19 -04:00
ed f2c5ae43d7 add resize splitter to dicussion hub message/response section 2026-03-12 21:14:41 -04:00
ed cf5ee6c0f1 make sure you can't send another rquest prompt when one is still being processed 2026-03-12 21:04:14 -04:00
45 changed files with 1859 additions and 245 deletions
+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}")
+1 -1
View File
@@ -66,7 +66,7 @@ For deep implementation details when planning or implementing tracks, consult `d
- **Clean Project Root:** Enforces a "Cruft-Free Root" policy by organizing core implementation into a `src/` directory and redirecting all temporary test data, configurations, and AI-generated artifacts to `tests/artifacts/`.
- **Performance Diagnostics:** Comprehensive, conditional per-component profiling across the entire application. Features a dedicated **Diagnostics Panel** providing real-time telemetry for FPS, Frame Time, CPU usage, and **Detailed Component Timings** for all GUI panels and background threads, including automated threshold-based latency alerts.
- **Automated UX Verification:** A robust IPC mechanism via API hooks and a modular simulation suite allows for human-like simulation walkthroughs and automated regression testing of the full GUI lifecycle across multiple specialized scenarios.
- **Professional UI Theme & Typography:** Implements a high-fidelity visual system featuring **Inter** and **Maple Mono** fonts for optimal readability. Employs a cohesive "Subtle Rounding" aesthetic across all standard widgets, supported by custom **soft shadow shaders** for modals and popups to provide depth and professional polish. Includes a selectable **NERV UI theme** featuring a "Black Void" palette, zero-rounding geometry, and CRT-style visual effects (scanlines, status flickering).
- **Professional UI Theme & Typography:** Implements a high-fidelity visual system featuring **Inter** and **Maple Mono** fonts for optimal readability. Employs a cohesive \"Subtle Rounding\" aesthetic across all standard widgets, supported by custom **soft shadow shaders** for modals and popups, and a high-fidelity **frosted glass (acrylic) background effect** for panels to provide depth and professional polish. Includes a selectable **NERV UI theme** featuring a \"Black Void\" palette, zero-rounding geometry, and CRT-style visual effects (scanlines, status flickering).
- **Rich Text & Syntax Highlighting:** Provides advanced rendering for messages, logs, and tool outputs using a hybrid Markdown system. Supports GitHub-Flavored Markdown (GFM) via `imgui_markdown` and integrates `ImGuiColorTextEdit` for high-performance syntax highlighting of code blocks (Python, JSON, C++, etc.). Includes automated language detection and clickable URL support.
- **Multi-Viewport & Layout Management:** Full support for ImGui Multi-Viewport, allowing users to detach panels into standalone OS windows for complex multi-monitor workflows. Includes a comprehensive **Layout Presets system**, enabling developers to save, name, and instantly restore custom window arrangements, including their Multi-Viewport state.
- **Headless Backend Service & Hook API:** Optional headless mode allowing the core AI and tool execution logic to run as a decoupled service. Features a comprehensive Hook API and WebSocket event streaming for remote orchestration, deep state inspection, and manual worker lifecycle management.
+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.
- **psutil:** For system and process monitoring (CPU/Memory telemetry).
- **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.
- **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.
@@ -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.
- **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.
- **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 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), dynamic backgrounds, and high-fidelity **frosted glass (acrylic) blurring** of the GUI panels via multi-pass Gaussian/Kawase filtering.
- **Interface-Driven Development (IDD):** Enforces a "Stub-and-Resolve" pattern where cross-module dependencies are resolved by generating signatures/contracts before implementation.
+13 -1
View File
@@ -32,6 +32,12 @@ This file tracks all major tracks for the project. Each track has its own detail
*Link: [./tracks/beads_mode_20260309/](./tracks/beads_mode_20260309/)*
*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**
*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/)*
---
### GUI Overhauls & Visualizations
@@ -54,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)
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/)*
7. [x] **Track: UI/UX Improvements - Presets and AI Settings**
@@ -73,6 +79,12 @@ 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/)*
*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. [x] **Track: Frosted Glass Background Effect**
*Link: [./tracks/frosted_glass_20260313/](./tracks/frosted_glass_20260313/)*
---
### Additional Language Support
@@ -1,35 +1,35 @@
# Implementation Plan: Custom Shader and Window Frame Support
## Phase 1: Investigation & Architecture Prototyping
- [ ] Task: Investigate `imgui-bundle` and Dear PyGui capabilities for injecting raw custom shaders (OpenGL/D3D11) vs extending ImDrawList batching.
- [ ] Task: Investigate Python ecosystem capabilities for overloading OS window frames (e.g., `pywin32` for DWM vs ImGui borderless mode).
- [ ] Task: Draft architectural design document (`docs/guide_shaders_and_window.md`) detailing the chosen shader injection method and window frame overloading strategy.
- [ ] Task: Conductor - User Manual Verification 'Phase 1: Investigation & Architecture Prototyping' (Protocol in workflow.md)
## Phase 1: Investigation & Architecture Prototyping [checkpoint: 815ee55]
- [x] Task: Investigate imgui-bundle and Dear PyGui capabilities for injecting raw custom shaders (OpenGL/D3D11) vs extending ImDrawList batching. [5f4da36]
- [x] Task: Investigate Python ecosystem capabilities for overloading OS window frames (e.g., `pywin32` for DWM vs ImGui borderless mode). [5f4da36]
- [x] Task: Draft architectural design document (`docs/guide_shaders_and_window.md`) detailing the chosen shader injection method and window frame overloading strategy. [5f4da36]
- [x] Task: Conductor - User Manual Verification 'Phase 1: Investigation & Architecture Prototyping' (Protocol in workflow.md) [815ee55]
## Phase 2: Custom OS Window Frame Implementation
- [ ] Task: Write Tests: Verify the application window launches with the custom frame/borderless mode active.
- [ ] Task: Implement: Integrate custom window framing logic into the main GUI loop (`src/gui_2.py` / Dear PyGui setup).
- [ ] Task: Write Tests: Verify standard window controls (minimize, maximize, close, drag) function correctly with the new frame.
- [ ] Task: Implement: Add custom title bar and window controls matching the application's theme.
- [ ] Task: Conductor - User Manual Verification 'Phase 2: Custom OS Window Frame Implementation' (Protocol in workflow.md)
## Phase 2: Custom OS Window Frame Implementation [checkpoint: b9ca69f]
- [x] Task: Write Tests: Verify the application window launches with the custom frame/borderless mode active. [02fca1f]
- [x] Task: Implement: Integrate custom window framing logic into the main GUI loop (`src/gui_2.py` / Dear PyGui setup). [59d7368]
- [x] Task: Write Tests: Verify standard window controls (minimize, maximize, close, drag) function correctly with the new frame. [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]
## Phase 3: Core Shader Pipeline Integration
- [ ] Task: Write Tests: Verify the shader manager class initializes without errors and can load a basic shader program.
- [ ] 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.
- [ ] Task: Write Tests: Verify shader uniform data can be updated from Python dictionaries/TOML configurations.
- [ ] Task: Implement: Add support for uniform passing (time, resolution, mouse pos) to the shader pipeline.
- [ ] Task: Conductor - User Manual Verification 'Phase 3: Core Shader Pipeline Integration' (Protocol in workflow.md)
## Phase 3: Core Shader Pipeline Integration [checkpoint: 5ebce89]
- [x] Task: Write Tests: Verify the shader manager class initializes without errors and can load a basic shader program. [ac4f63b]
- [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]
- [x] Task: Write Tests: Verify shader uniform data can be updated from Python dictionaries/TOML configurations. [0938396]
- [x] Task: Implement: Add support for uniform passing (time, resolution, mouse pos) to the shader pipeline. [0938396]
- [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)
- [ ] Task: Write Tests: Verify background shader logic can render behind the main ImGui layer.
- [ ] Task: Implement: Add "Dynamic Background" shader implementation (e.g., animated noise/gradients).
- [ ] Task: Write Tests: Verify post-process shader logic can capture the ImGui output and apply an effect over it.
- [ ] Task: Implement: Add "CRT / Retro" (NERV theme) and general "Post-Processing" (bloom/blur) shaders.
- [ ] Task: Conductor - User Manual Verification 'Phase 4: Specific Shader Implementations' (Protocol in workflow.md)
## Phase 4: Specific Shader Implementations (CRT, Post-Process, Backgrounds) [checkpoint: 50f98de]
- [x] Task: Write Tests: Verify background shader logic can render behind the main ImGui layer. [836168a]
- [x] Task: Implement: Add "Dynamic Background" shader implementation (e.g., animated noise/gradients). [836168a]
- [x] Task: Write Tests: Verify post-process shader logic can capture the ImGui output and apply an effect over it. [905ac00]
- [x] Task: Implement: Add "CRT / Retro" (NERV theme) and general "Post-Processing" (bloom/blur) shaders. [905ac00]
- [x] Task: Conductor - User Manual Verification 'Phase 4: Specific Shader Implementations' (Protocol in workflow.md) [50f98de]
## Phase 5: Configuration and Live Editor UI
- [ ] Task: Write Tests: Verify shader and window frame settings can be parsed from `config.toml`.
- [ ] Task: Implement: Update `src/theme.py` / `src/project_manager.py` to parse and apply shader/window configurations from TOML.
- [ ] Task: Write Tests: Verify the Live UI Editor panel renders and modifying its values updates the shader uniforms.
- [ ] Task: Implement: Create a "Live UI Editor" Dear PyGui/ImGui panel to tweak shader uniforms in real-time.
- [ ] Task: Conductor - User Manual Verification 'Phase 5: Configuration and Live Editor UI' (Protocol in workflow.md)
## Phase 5: Configuration and Live Editor UI [checkpoint: da47819]
- [x] Task: Write Tests: Verify shader and window frame settings can be parsed from `config.toml`. [d69434e]
- [x] Task: Implement: Update `src/theme.py` / `src/project_manager.py` to parse and apply shader/window configurations from TOML. [d69434e]
- [x] Task: Write Tests: Verify the Live UI Editor panel renders and modifying its values updates the shader uniforms. [229fbe2]
- [x] Task: Implement: Create a "Live UI Editor" Dear PyGui/ImGui panel to tweak shader uniforms in real-time. [229fbe2]
- [x] Task: Conductor - User Manual Verification 'Phase 5: Configuration and Live Editor UI' (Protocol in workflow.md) [da47819]
@@ -0,0 +1,5 @@
# Track data_oriented_optimization_20260312 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)
@@ -0,0 +1,8 @@
{
"track_id": "data_oriented_optimization_20260312",
"type": "chore",
"status": "new",
"created_at": "2026-03-12T00:00:00Z",
"updated_at": "2026-03-12T00:00:00Z",
"description": "Optimization pass. I want to update the product guidlines to take into account with data-oriented appraoch the more performant way to semantically define procedrual code in python so executes almost entirely heavy operations optimally. I know there is a philosophy of 'the less python does the better' which is problably why the imgui lib is so performant because all python really does is define the ui's DAG via an imgui interface procedurally along with what state the dag may modify within its constraints of interactions the user may do. This problably can be reflected in the way the rest of the codebase is done. I want to go over the ./src and ./simulation to make sure this insight and related herustics are properly enfroced. Worst case I want to identify what code I should consider lower down to C maybe and making python bindings to if there is a significant bottleneck identified via profiling and testing that cannot be resolved otherwise."
}
@@ -0,0 +1,27 @@
# Implementation Plan: Data-Oriented Python Optimization Pass
## Phase 1: Guidelines and Instrumentation
- [ ] Task: Update `conductor/product-guidelines.md` with Data-Oriented Python heuristics and the "less Python does the better" philosophy.
- [ ] Task: Review existing profiling instrumentation in `src/performance_monitor.py` or diagnostic hooks.
- [ ] Task: Expand profiling instrumentation to capture more detailed execution times for non-GUI data structures/processes if necessary.
- [ ] Task: Conductor - User Manual Verification 'Phase 1: Guidelines and Instrumentation' (Protocol in workflow.md)
## Phase 2: Audit and Profiling (`src/` and `simulation/`)
- [ ] Task: Run profiling scenarios (especially utilizing simulations) to generate baseline metrics.
- [ ] Task: Audit `src/` (e.g., `dag_engine.py`, `multi_agent_conductor.py`, `aggregate.py`) against the new guidelines, cross-referencing with profiling data to identify bottlenecks.
- [ ] Task: Audit `simulation/` files against the new guidelines to ensure the test harness is performant and non-blocking.
- [ ] Task: Compile a list of identified bottleneck targets to refactor.
- [ ] Task: Conductor - User Manual Verification 'Phase 2: Audit and Profiling (`src/` and `simulation/`)' (Protocol in workflow.md)
## Phase 3: Targeted Optimization and Refactoring
- [ ] Task: Write/update tests for the first identified bottleneck to establish a performance or structural baseline (Red Phase).
- [ ] Task: Refactor the first identified bottleneck to align with data-oriented guidelines (Green Phase).
- [ ] Task: Write/update tests for remaining identified bottlenecks.
- [ ] Task: Refactor remaining identified bottlenecks.
- [ ] Task: Conductor - User Manual Verification 'Phase 3: Targeted Optimization and Refactoring' (Protocol in workflow.md)
## Phase 4: Final Evaluation and Documentation
- [ ] Task: Re-run all profiling scenarios to compare against the baseline metrics.
- [ ] Task: Analyze remaining bottlenecks that did not reach performance thresholds and document them as candidates for C/C++ bindings (Last Resort).
- [ ] Task: Generate a final summary report of the optimizations applied and the C extension evaluation.
- [ ] Task: Conductor - User Manual Verification 'Phase 4: Final Evaluation and Documentation' (Protocol in workflow.md)
@@ -0,0 +1,35 @@
# Specification: Data-Oriented Python Optimization Pass
## Overview
Perform an optimization pass and audit across the codebase (`./src` and `./simulation`), aligning the implementation with the Data-Oriented Design philosophy and the "less Python does the better" heuristic. Update the `product-guidelines.md` to formally document this approach for procedural Python code.
## Functional Requirements
1. **Update Product Guidelines:**
- Formalize the heuristic that Python should act primarily as a procedural semantic definer (similar to how ImGui defines a UI DAG), delegating heavy lifting.
- Enforce data-oriented guidelines for Python code structure, focusing on minimizing Python JIT overhead.
2. **Codebase Audit (`./src` and `./simulation`):**
- Review global `src/` files and simulation logic against the new guidelines.
- Identify bottlenecks that violate these heuristics (e.g., heavy procedural state manipulation in Python).
3. **Profiling & Instrumentation Expansion:**
- Expand existing profiling instrumentation (e.g., `performance_monitor.py` or diagnostic hooks) if currently insufficient for identifying real structural bottlenecks.
4. **Optimization Execution:**
- Refactor identified bottlenecks to align with the new data-oriented Python heuristics.
- Re-evaluate performance post-refactor.
5. **C Extension Evaluation (Last Resort):**
- If Python optimizations fail to meet performance thresholds, specifically identify and document routines that must be lowered to C/C++ with Python bindings. Only proceed with bindings if absolutely necessary.
## Non-Functional Requirements
- Maintain existing test coverage and strict type-hinting requirements.
- Ensure 1-space indentation and ultra-compact style rules are not violated during refactoring.
- Ensure the main GUI rendering thread is never blocked.
## Acceptance Criteria
- `product-guidelines.md` is updated with data-oriented procedural Python guidelines.
- `src/` and `simulation/` undergo a documented profiling audit.
- Identified bottlenecks are refactored to reduce Python overhead.
- No regressions in automated simulation or unit tests.
- A final report is provided detailing optimizations made and any candidates for future C extension porting.
## Out of Scope
- Actually implementing C/C++ bindings in this track (this track only identifies/evaluates them as a last resort; if needed, they get a separate track).
- Major UI visual theme changes.
@@ -0,0 +1,5 @@
# Track frosted_glass_20260313 Context
- [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-13T14:39:00Z",
"description": "Add 'frosted glass' bg for transparency on panels and popups. This blurring effect will allow drop downs and other elements of these panels to not get hard to discern from background text or elements behind the panel."
}
@@ -0,0 +1,26 @@
# Implementation Plan: Frosted Glass Background Effect
## Phase 1: Shader Development & Integration [checkpoint: 55f3bd8]
- [x] Task: Audit `src/shader_manager.py` to identify existing background/post-process integration points. [1328bc1]
- [x] Task: Write Tests: Verify `ShaderManager` can compile and bind a multi-pass blur shader. [1328bc1]
- [x] Task: Implement: Add `FrostedGlassShader` (GLSL) to `src/shader_manager.py`. [1328bc1]
- [x] Task: Implement: Integrate the blur shader into the `ShaderManager` lifecycle. [1328bc1]
- [x] Task: Conductor - User Manual Verification 'Phase 1: Shader Development & Integration' (Protocol in workflow.md) [55f3bd8]
## Phase 2: Framebuffer Capture Pipeline [checkpoint: e9b7875]
- [x] Task: Write Tests: Verify the FBO capture mechanism correctly samples the back buffer and stores it in a texture. [f297e7a]
- [x] Task: Implement: Update `src/shader_manager.py` or `src/gui_2.py` to handle "pre-rendering" of the background into a texture for blurring. [f297e7a]
- [x] Task: Implement: Ensure the blurred texture is updated every frame or on window move events. [f297e7a]
- [x] Task: Conductor - User Manual Verification 'Phase 2: Framebuffer Capture Pipeline' (Protocol in workflow.md) [e9b7875]
## Phase 3: GUI Integration & Rendering [checkpoint: cecbe22]
- [x] Task: Write Tests: Verify that a mocked ImGui window successfully calls the frosted glass rendering logic. [cecbe22]
- [x] Task: Implement: Create a `_render_frosted_background(self, pos, size)` helper in `src/gui_2.py`. [cecbe22]
- [x] Task: Implement: Update panel rendering loops (e.g. `_gui_func`) to inject the frosted background before calling `imgui.begin()` for major panels. [cecbe22]
- [x] Task: Conductor - User Manual Verification 'Phase 3: GUI Integration & Rendering' (Protocol in workflow.md) [cecbe22]
## Phase 4: UI Controls & Configuration [checkpoint: cecbe22]
- [x] Task: Write Tests: Verify that modifying blur uniforms via the Live Editor updates the shader state. [cecbe22]
- [x] Task: Implement: Add "Frosted Glass" sliders (Blur, Tint, Opacity) to the **Shader Editor** in `src/gui_2.py`. [cecbe22]
- [x] Task: Implement: Update `src/theme.py` to parse and store frosted glass settings from `config.toml`. [cecbe22]
- [x] Task: Conductor - User Manual Verification 'Phase 4: UI Controls & Configuration' (Protocol in workflow.md) [cecbe22]
@@ -0,0 +1,34 @@
# Specification: Frosted Glass Background Effect
## Overview
Implement a high-fidelity "frosted glass" (acrylic) background effect for all GUI panels and popups within the Manual Slop interface. This effect will use a GPU-resident shader to blur the content behind active windows, improving readability and visual depth while preventing background text from clashing with foreground UI elements.
## Functional Requirements
- **GPU-Accelerated Blur:**
- Implement a GLSL fragment shader (e.g., Gaussian or Kawase blur) within the existing `ShaderManager` pipeline.
- The shader must sample the current frame buffer background and render a blurred version behind the active window's background.
- **Global Integration:**
- The effect must automatically apply to all standard ImGui panels and popups.
- Integrate with `imgui.begin()` and `imgui.begin_popup()` (or via a reusable wrapper helper).
- **Real-Time Tuning:**
- Add controls to the **Live Shader Editor** to adjust the following parameters:
- **Blur Radius:** Control the intensity of the Gaussian blur.
- **Tint Intensity:** Control the strength of the "frost" overlay color.
- **Base Opacity:** Control the overall transparency of the frosted layer.
- **Persistence:**
- Save frosted glass parameters to `config.toml` under the `theme` or `shader` section.
## Technical Implementation
- **Shader Pipeline:** Use `PyOpenGL` to manage a dedicated background texture/FBO for sampling.
- **Coordinate Mapping:** Ensure the blur shader correctly maps screen coordinates to the region behind the current ImGui window.
- **State Integration:** Store tuning parameters in `App.shader_uniforms` and ensure they are updated every frame.
## Acceptance Criteria
- [ ] Panels and popups have a distinct, blurred background that clearly separates them from the content behind them.
- [ ] Changing the "Blur Radius" slider in the Shader Editor immediately updates the visual frostiness.
- [ ] The effect remains stable during window dragging and resizing.
- [ ] No significant performance degradation (maintaining target FPS).
## Out of Scope
- Implementing different blur types (e.g., motion blur, radial blur).
- Per-panel unique blur settings (initially global only).
@@ -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).
+13 -8
View File
@@ -26,12 +26,12 @@ separate_tool_calls_panel = false
bg_shader_enabled = false
crt_filter_enabled = false
separate_task_dag = false
separate_usage_analytics = false
separate_usage_analytics = true
separate_tier1 = false
separate_tier2 = false
separate_tier3 = false
separate_tier4 = false
separate_external_tools = false
separate_external_tools = true
[gui.show_windows]
"Context Hub" = true
@@ -39,7 +39,7 @@ separate_external_tools = false
"AI Settings" = true
"MMA Dashboard" = true
"Task DAG" = false
"Usage Analytics" = false
"Usage Analytics" = true
"Tier 1" = false
"Tier 2" = false
"Tier 3" = false
@@ -51,20 +51,25 @@ separate_external_tools = false
"Discussion Hub" = true
"Operations Hub" = true
Message = false
Response = false
"Tool Calls" = false
Response = true
"Tool Calls" = true
Theme = true
"Log Management" = true
Diagnostics = false
"External Tools" = false
"Shader Editor" = true
[theme]
palette = "Nord Dark"
font_path = "fonts/Inter-Regular.ttf"
font_path = "C:/projects/manual_slop/assets/fonts/MapleMono-Regular.ttf"
font_size = 16.0
scale = 1.0
transparency = 1.0
child_transparency = 1.0
transparency = 0.699999988079071
child_transparency = 0.6899999976158142
frosted_blur_radius = 29.68400001525879
frosted_tint_intensity = 0.5659999847412109
frosted_opacity = 0.5389999747276306
frosted_glass_enabled = true
[mma]
max_workers = 4
+33
View File
@@ -0,0 +1,33 @@
# Custom Shaders and Window Frame Architecture
## 1. Shader Injection Strategy
### Evaluation
* **Dear PyGui (Legacy):** Does not natively support raw GLSL/HLSL shader injection into the UI layer. It relies heavily on fixed-function vertex/fragment shaders compiled into the C++ core. Faux-shaders via DrawList are the only viable path without modifying the DPG source.
* **imgui-bundle (Current):** `imgui-bundle` utilizes `hello_imgui` as its application runner, which provides robust lifecycle callbacks (e.g., `callbacks.custom_background`, `callbacks.post_init`). Because `hello_imgui` exposes the underlying OpenGL context, we can use `PyOpenGL` alongside it to execute raw GLSL shaders.
### Chosen Approach: Hybrid Faux-Shader & PyOpenGL FBO
Given the Python environment, we will adopt a hybrid approach:
1. **Faux-Shaders (ImDrawList Batching):** Continue using `imgui.ImDrawList` primitives for simple effects like soft shadows, glows, and basic gradients (as seen in `src/shaders.py`). This is highly performant for UI elements and requires no external dependencies.
2. **True GPU Shaders (PyOpenGL + FBO):** For complex post-processing (CRT curvature, bloom, dynamic noise backgrounds), we will integrate `PyOpenGL`.
* We will compile GLSL shaders during `post_init`.
* We will render the effect into a Framebuffer Object (FBO).
* We will display the resulting texture ID using `imgui.image()` or inject it into the `custom_background` callback.
*Note: This approach introduces `PyOpenGL` as a dependency, which is standard for advanced Python graphics.*
## 2. Custom Window Frame Strategy
### Evaluation
* **Native DWM Overloading (PyWin32):** It is possible to use `pywin32` to subclass the application window, intercept `WM_NCHITTEST`, and return `HTCAPTION` for a custom ImGui-drawn title bar region. This preserves Windows snap layouts and native drop shadows. However, it is strictly Windows-only and can conflict with GLFW/SDL2 event loops used by `hello_imgui`.
* **Borderless Window Mode (ImGui/GLFW):** `hello_imgui` allows configuring the main window as borderless/undecorated (`runner_params.app_window_params.borderless = True`). We must then manually draw the title bar, minimize/maximize/close buttons, and handle window dragging by updating the OS window position based on ImGui mouse drag deltas.
### Chosen Approach: Pure ImGui Borderless Implementation
To ensure cross-platform compatibility and avoid brittle Win32 hook collisions with `hello_imgui`, we will use the **Borderless Window Mode** approach.
1. **Initialization:** Configure `hello_imgui.RunnerParams` to disable OS window decorations.
2. **Title Bar Rendering:** Dedicate the top ~30 pixels of the ImGui workspace to a custom title bar that matches the current theme (e.g., NERV or standard).
3. **Window Controls:** Implement custom ImGui buttons for `_`, `[]`, and `X`, which will call native window management functions exposed by `hello_imgui` or `glfw`.
4. **Drag Handling:** Detect `imgui.is_mouse_dragging()` on the title bar region and dynamically adjust the application window position.
## 3. Integration with Event Metrics
Both the shader uniforms (time, resolution) and window control events will be hooked into the existing `dag_engine` and `events` systems to ensure minimal performance overhead and centralized configuration via `config.toml`.
+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":{}}
+103 -69
View File
@@ -12,7 +12,7 @@ ViewportPos=43,95
ViewportId=0x78C57832
Size=897,649
Collapsed=0
DockId=0x00000001,0
DockId=0x00000005,0
[Window][Files]
ViewportPos=3125,170
@@ -33,7 +33,7 @@ DockId=0x0000000A,0
Pos=0,17
Size=1680,730
Collapsed=0
DockId=0x00000001,0
DockId=0x00000005,0
[Window][Provider]
ViewportPos=43,95
@@ -41,23 +41,22 @@ ViewportId=0x78C57832
Pos=0,651
Size=897,468
Collapsed=0
DockId=0x00000001,0
DockId=0x00000005,0
[Window][Message]
Pos=642,1879
Size=1002,242
Pos=661,1321
Size=716,455
Collapsed=0
[Window][Response]
Pos=1700,1898
Size=1111,224
Pos=2437,925
Size=1111,773
Collapsed=0
[Window][Tool Calls]
Pos=855,1482
Size=1014,655
Pos=1039,464
Size=587,510
Collapsed=0
DockId=0x00000006,0
[Window][Comms History]
ViewportPos=43,95
@@ -74,10 +73,10 @@ Collapsed=0
DockId=0xAFC85805,2
[Window][Theme]
Pos=0,207
Size=499,1403
Pos=2671,24
Size=1169,2136
Collapsed=0
DockId=0x00000002,2
DockId=0x00000002,1
[Window][Text Viewer - Entry #7]
Pos=379,324
@@ -85,16 +84,16 @@ Size=900,700
Collapsed=0
[Window][Diagnostics]
Pos=2641,34
Size=1199,2103
Pos=1649,24
Size=580,1284
Collapsed=0
DockId=0x00000010,2
DockId=0x00000004,2
[Window][Context Hub]
Pos=0,207
Size=499,1403
Pos=0,1719
Size=999,441
Collapsed=0
DockId=0x00000002,1
DockId=0x00000006,0
[Window][AI Settings Hub]
Pos=406,17
@@ -103,28 +102,28 @@ Collapsed=0
DockId=0x0000000D,0
[Window][Discussion Hub]
Pos=1172,24
Size=703,1586
Pos=1762,24
Size=907,2136
Collapsed=0
DockId=0x00000013,0
DockId=0x00000011,0
[Window][Operations Hub]
Pos=501,24
Size=669,1586
Pos=1001,24
Size=759,2136
Collapsed=0
DockId=0x00000005,0
DockId=0x00000010,0
[Window][Files & Media]
Pos=0,207
Size=499,1403
Pos=0,1719
Size=999,441
Collapsed=0
DockId=0x00000002,0
DockId=0x00000006,1
[Window][AI Settings]
Pos=0,24
Size=499,181
Size=999,1693
Collapsed=0
DockId=0x00000001,0
DockId=0x00000005,0
[Window][Approve Tool Execution]
Pos=3,524
@@ -132,16 +131,16 @@ Size=416,325
Collapsed=0
[Window][MMA Dashboard]
Pos=1877,24
Size=1018,1586
Pos=2671,24
Size=1169,2136
Collapsed=0
DockId=0x00000010,0
DockId=0x00000002,0
[Window][Log Management]
Pos=1877,24
Size=1018,1586
Pos=1931,24
Size=629,1416
Collapsed=0
DockId=0x00000010,1
DockId=0x00000002,1
[Window][Track Proposal]
Pos=709,326
@@ -167,7 +166,7 @@ Collapsed=0
Pos=2822,1717
Size=1018,420
Collapsed=0
DockId=0x0000000C,0
DockId=0x00000004,0
[Window][Approve PowerShell Command]
Pos=649,435
@@ -175,7 +174,7 @@ Size=381,329
Collapsed=0
[Window][Last Script Output]
Pos=1005,343
Pos=2810,265
Size=800,562
Collapsed=0
@@ -210,7 +209,7 @@ Size=3840,32
Collapsed=0
[Window][Text Viewer - message]
Pos=568,1226
Pos=562,588
Size=900,700
Collapsed=0
@@ -220,7 +219,7 @@ Size=900,700
Collapsed=0
[Window][Text Viewer - text]
Pos=60,60
Pos=555,644
Size=900,700
Collapsed=0
@@ -240,7 +239,7 @@ Size=900,700
Collapsed=0
[Window][Text Viewer - tool_calls]
Pos=60,60
Pos=589,490
Size=900,700
Collapsed=0
@@ -285,8 +284,8 @@ Size=900,700
Collapsed=0
[Window][Text Viewer - Tool Call #1 Details]
Pos=2318,1220
Size=900,700
Pos=165,1081
Size=727,725
Collapsed=0
[Window][Text Viewer - Tool Call #10 Details]
@@ -330,10 +329,9 @@ Size=967,499
Collapsed=0
[Window][Usage Analytics]
Pos=2822,1717
Size=1018,420
Pos=1702,689
Size=566,438
Collapsed=0
DockId=0x0000000F,0
[Window][Tool Preset Manager]
Pos=1301,302
@@ -350,6 +348,46 @@ Pos=856,546
Size=1000,800
Collapsed=0
[Window][External Tools]
Pos=531,376
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=828,397
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=753,637
Size=493,487
Collapsed=0
[Window][Text Viewer - list_directory]
Pos=60,60
Size=900,700
Collapsed=0
[Table][0xFB6E3870,4]
RefScale=13
Column 0 Width=80
@@ -381,11 +419,11 @@ Column 3 Width=20
Column 4 Weight=1.0000
[Table][0x2A6000B6,4]
RefScale=14
Column 0 Width=42
Column 1 Width=61
RefScale=16
Column 0 Width=48
Column 1 Width=68
Column 2 Weight=1.0000
Column 3 Width=105
Column 3 Width=120
[Table][0x8BCC69C7,6]
RefScale=13
@@ -407,7 +445,7 @@ Column 3 Width=120
RefScale=16
Column 0 Width=48
Column 1 Weight=1.0000
Column 2 Width=119
Column 2 Width=117
Column 3 Width=48
[Table][0xD99F45C5,4]
@@ -429,9 +467,9 @@ Column 1 Width=100
Column 2 Weight=1.0000
[Table][0xA02D8C87,3]
RefScale=24
Column 0 Width=270
Column 1 Width=180
RefScale=16
Column 0 Width=180
Column 1 Width=120
Column 2 Weight=1.0000
[Table][0xD0277E63,2]
@@ -463,23 +501,19 @@ Column 1 Weight=1.0000
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,24 Size=2895,1586 Split=X
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=2820,1183 Split=X
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,24 Size=3840,2136 Split=X
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=1617,1183 Split=X
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=0x00000001 Parent=0x00000007 SizeRef=824,708 CentralNode=1 Selected=0x7BD57D6A
DockNode ID=0x00000002 Parent=0x00000007 SizeRef=824,1403 Selected=0xF4139CA2
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=1374,858 Split=X Selected=0x418C7449
DockNode ID=0x00000012 Parent=0x0000000E SizeRef=669,402 Split=Y Selected=0x418C7449
DockNode ID=0x00000005 Parent=0x00000012 SizeRef=876,1455 Selected=0x418C7449
DockNode ID=0x00000006 Parent=0x00000012 SizeRef=876,654 Selected=0x1D56B311
DockNode ID=0x00000013 Parent=0x0000000E SizeRef=703,402 Selected=0x6F2B5B04
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=999,858 Split=Y Selected=0x7BD57D6A
DockNode ID=0x00000005 Parent=0x00000007 SizeRef=639,904 CentralNode=1 Selected=0x7BD57D6A
DockNode ID=0x00000006 Parent=0x00000007 SizeRef=639,441 Selected=0x1DCB2623
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=2839,858 Split=X Selected=0x418C7449
DockNode ID=0x00000001 Parent=0x0000000E SizeRef=1668,1288 Split=X Selected=0x6F2B5B04
DockNode ID=0x00000010 Parent=0x00000001 SizeRef=759,1416 Selected=0x418C7449
DockNode ID=0x00000011 Parent=0x00000001 SizeRef=907,1416 Selected=0x6F2B5B04
DockNode ID=0x00000002 Parent=0x0000000E SizeRef=1169,1288 Selected=0x8CA2375C
DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6
DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=1018,1183 Split=Y Selected=0x3AEC3498
DockNode ID=0x00000010 Parent=0x00000004 SizeRef=1199,1689 Selected=0x3AEC3498
DockNode ID=0x00000011 Parent=0x00000004 SizeRef=1199,420 Split=X Selected=0xDEB547B6
DockNode ID=0x0000000C Parent=0x00000011 SizeRef=916,380 Selected=0x655BC6E9
DockNode ID=0x0000000F Parent=0x00000011 SizeRef=281,380 Selected=0xDEB547B6
DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=511,1183 Selected=0x3AEC3498
;;;<<<Layout_655921752_Default>>>;;;
;;;<<<HelloImGui_Misc>>>;;;
+1
View File
@@ -17,6 +17,7 @@ dependencies = [
"tree-sitter-python>=0.25.0",
"mcp>=1.0.0",
"pytest-timeout>=2.4.0",
"pyopengl>=3.1.10",
]
[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)
+109 -2
View File
@@ -286,7 +286,9 @@ class AppController:
self._gemini_cache_text: str = ""
self._last_stable_md: str = ''
self._token_stats: Dict[str, Any] = {}
self._token_stats_dirty: bool = False
self._comms_log_dirty: bool = True
self._tool_log_dirty: bool = True
self._token_stats_dirty: bool = True
self.ui_disc_truncate_pairs: int = 2
self.ui_auto_scroll_comms: bool = True
self.ui_auto_scroll_tool_calls: bool = True
@@ -294,10 +296,14 @@ class AppController:
self._track_discussion_active: bool = False
self._tier_stream_last_len: Dict[str, int] = {}
self.is_viewing_prior_session: bool = False
self._current_session_usage = None
self._current_mma_tier_usage = None
self.prior_session_entries: List[Dict[str, Any]] = []
self.prior_tool_calls: List[Dict[str, Any]] = []
self.prior_disc_entries: List[Dict[str, Any]] = []
self.prior_mma_dashboard_state: Dict[str, Any] = {}
self.prior_mma_dashboard_state = {}
self._current_token_history = None
self._current_session_start_time = None
self.test_hooks_enabled: bool = ("--enable-test-hooks" in sys.argv) or (os.environ.get("SLOP_TEST_HOOKS") == "1")
self.ui_manual_approve: bool = False
# Injection state
@@ -531,6 +537,11 @@ class AppController:
"payload": status
})
def _trigger_gui_refresh(self):
with self._pending_gui_tasks_lock:
self._pending_gui_tasks.append({'action': 'set_comms_dirty'})
self._pending_gui_tasks.append({'action': 'set_tool_log_dirty'})
def _process_pending_gui_tasks(self) -> None:
# Periodic telemetry broadcast
now = time.time()
@@ -557,6 +568,10 @@ class AppController:
# ...
if action == "refresh_api_metrics":
self._refresh_api_metrics(task.get("payload", {}), md_content=self.last_md or None)
elif action == 'set_comms_dirty':
self._comms_log_dirty = True
elif action == 'set_tool_log_dirty':
self._tool_log_dirty = True
elif action == "set_ai_status":
self.ai_status = task.get("payload", "")
sys.stderr.write(f"[DEBUG] Updated ai_status via task to: {self.ai_status}\n")
@@ -986,6 +1001,12 @@ class AppController:
if not path:
return
if not self.is_viewing_prior_session:
self._current_session_usage = copy.deepcopy(self.session_usage)
self._current_mma_tier_usage = copy.deepcopy(self.mma_tier_usage)
self._current_token_history = copy.deepcopy(self._token_history)
self._current_session_start_time = self._session_start_time
log_path = Path(path)
if log_path.is_dir():
log_file = log_path / "comms.log"
@@ -1020,6 +1041,15 @@ class AppController:
entries = []
disc_entries = []
paired_tools = {}
final_tool_calls = []
new_token_history = []
new_usage = {'input_tokens': 0, 'output_tokens': 0, 'cache_read_input_tokens': 0, 'cache_creation_input_tokens': 0, 'total_tokens': 0, 'last_latency': 0.0, 'percentage': 0.0}
new_mma_usage = copy.deepcopy(self.mma_tier_usage)
for t in new_mma_usage:
new_mma_usage[t]['input'] = 0
new_mma_usage[t]['output'] = 0
try:
with open(log_file, "r", encoding="utf-8") as f:
for line in f:
@@ -1031,6 +1061,47 @@ class AppController:
kind = entry.get("kind", entry.get("type", ""))
payload = entry.get("payload", {})
ts = entry.get("ts", "")
if kind == 'tool_call':
tid = payload.get('id') or payload.get('call_id')
script = payload.get('script') or json.dumps(payload.get('args', {}), indent=1)
script = _resolve_log_ref(script, session_dir)
entry_obj = {
'source_tier': entry.get('source_tier', 'main'),
'script': script,
'result': '', # Waiting for result
'ts': ts
}
if tid:
paired_tools[tid] = entry_obj
final_tool_calls.append(entry_obj)
elif kind == 'tool_result':
tid = payload.get('id') or payload.get('call_id')
output = payload.get('output', payload.get('content', ''))
output = _resolve_log_ref(output, session_dir)
if tid and tid in paired_tools:
paired_tools[tid]['result'] = output
else:
# Fallback: if no ID, try matching last entry in final_tool_calls that has no result
for old_call in reversed(final_tool_calls):
if not old_call['result']:
old_call['result'] = output
break
if kind == 'response' and 'usage' in payload:
u = payload['usage']
for k in ['input_tokens', 'output_tokens', 'cache_read_input_tokens', 'cache_creation_input_tokens', 'total_tokens']:
if k in new_usage: new_usage[k] += u.get(k, 0) or 0
tier = entry.get('source_tier', 'main')
if tier in new_mma_usage:
new_mma_usage[tier]['input'] += u.get('input_tokens', 0) or 0
new_mma_usage[tier]['output'] += u.get('output_tokens', 0) or 0
new_token_history.append({
'time': ts,
'input': u.get('input_tokens', 0) or 0,
'output': u.get('output_tokens', 0) or 0,
'model': entry.get('model', 'unknown')
})
if kind == "history_add":
content = payload.get("content", payload.get("text", payload.get("message", "")))
@@ -1088,11 +1159,47 @@ class AppController:
self._set_status(f"log load error: {e}")
return
self.session_usage = new_usage
self.mma_tier_usage = new_mma_usage
self._token_history = new_token_history
if new_token_history:
try:
import datetime
first_ts = new_token_history[0]['time']
dt = datetime.datetime.strptime(first_ts, '%Y-%m-%dT%H:%M:%S')
self._session_start_time = dt.timestamp()
except:
self._session_start_time = time.time()
self.prior_session_entries = entries
self.prior_disc_entries = disc_entries
self.prior_tool_calls = final_tool_calls
self.is_viewing_prior_session = True
self._trigger_gui_refresh()
self._set_status(f"viewing prior session: {session_dir.name} ({len(entries)} entries)")
def cb_exit_prior_session(self):
self.is_viewing_prior_session = False
if self._current_session_usage:
self.session_usage = self._current_session_usage
self._current_session_usage = None
if self._current_mma_tier_usage:
self.mma_tier_usage = self._current_mma_tier_usage
self._current_mma_tier_usage = None
if self._current_token_history is not None:
self._token_history = self._current_token_history
self._current_token_history = None
if self._current_session_start_time is not None:
self._session_start_time = self._current_session_start_time
self._current_session_start_time = None
self.prior_session_entries.clear()
self.prior_disc_entries.clear()
self.prior_tool_calls.clear()
self._trigger_gui_refresh()
self._set_status('idle')
def cb_prune_logs(self) -> None:
"""Manually triggers the log pruning process with aggressive thresholds."""
self._set_status("Manual prune started (Age > 0d, Size < 100KB)...")
+1 -1
View File
@@ -12,7 +12,7 @@ class BackgroundShader:
self.ctx: Optional[nvg.Context] = None
def render(self, width: float, height: float):
if not self.enabled:
if not self.enabled or width <= 0 or height <= 0:
return
# In imgui-bundle, hello_imgui handles the background.
+311 -106
View File
@@ -21,6 +21,7 @@ from src import theme_2 as theme
from src import theme_nerv_fx as theme_fx
from src import api_hooks
import numpy as np
import OpenGL.GL as gl
from src import log_registry
from src import log_pruner
from src import models
@@ -28,7 +29,15 @@ from src import app_controller
from src import mcp_client
from src import markdown_helper
from src import bg_shader
from src.shader_manager import ShaderManager
import re
import subprocess
if sys.platform == "win32":
import win32gui
import win32con
else:
win32gui = None
win32con = None
from pydantic import BaseModel
from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed
@@ -185,26 +194,36 @@ class App:
self.show_windows.setdefault("Tier 3: Workers", False)
self.show_windows.setdefault("Tier 4: QA", 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.layout_presets = self.config.get("layout_presets", {})
self._new_preset_name = ""
self._show_save_preset_modal = False
self._comms_log_cache: list[dict[str, Any]] = []
self._comms_log_dirty: bool = True
self._tool_log_cache: list[dict[str, Any]] = []
self._tool_log_dirty: bool = True
self._last_ui_focus_agent: Optional[str] = None
self._log_registry: Optional[log_registry.LogRegistry] = None
self.perf_profiling_enabled = False
self.perf_show_graphs: dict[str, bool] = {}
self._token_stats: dict[str, Any] = {}
self._token_stats_dirty: bool = True
self.perf_history: dict[str, list] = {"frame_time": [0.0] * 100, "fps": [0.0] * 100}
self._nerv_crt = theme_fx.CRTFilter()
self.ui_crt_filter = True
self._nerv_alert = theme_fx.AlertPulsing()
self._nerv_flicker = theme_fx.StatusFlicker()
self.ui_tool_filter_category = "All"
self.ui_discussion_split_h = 300.0
self.shader_uniforms = {
'crt': 1.0,
'scanline': 0.5,
'bloom': 0.8,
'frosted_blur_radius': theme.get_frosted_blur_radius(),
'frosted_tint_intensity': theme.get_frosted_tint_intensity(),
'frosted_opacity': theme.get_frosted_opacity()
}
self.shader_manager = ShaderManager()
self.start_time = time.time()
def _handle_approve_tool(self, user_data=None) -> None:
"""UI-level wrapper for approving a pending tool execution ask."""
@@ -334,6 +353,111 @@ class App:
imgui.pop_style_var(2)
imgui.pop_id()
def _render_frosted_background(self, pos: imgui.ImVec2, size: imgui.ImVec2) -> None:
if not theme.get_frosted_glass_enabled(): return
if size.x <= 0 or size.y <= 0: return
try:
ws = immapp.get_window_size()
display_size = imgui.ImVec2(float(ws[0]), float(ws[1]))
except Exception:
display_size = imgui.get_io().display_size
if display_size.x <= 0 or display_size.y <= 0: return
uv_min = imgui.ImVec2(pos.x / display_size.x, 1.0 - pos.y / display_size.y)
uv_max = imgui.ImVec2((pos.x + size.x) / display_size.x, 1.0 - (pos.y + size.y) / display_size.y)
if self.shader_manager.blur_tex:
imgui.get_window_draw_list().add_image(
imgui.ImTextureRef(self.shader_manager.blur_tex),
pos,
imgui.ImVec2(pos.x + size.x, pos.y + size.y),
uv_min,
uv_max
)
else:
# Fallback: semi-transparent rectangle to help debug visibility
imgui.get_window_draw_list().add_rect_filled(
pos,
imgui.ImVec2(pos.x + size.x, pos.y + size.y),
imgui.get_color_u32(vec4(30, 30, 40, 0.7))
)
# Increase contrast with a 0.2 alpha tint overlay
imgui.get_window_draw_list().add_rect_filled(
pos,
imgui.ImVec2(pos.x + size.x, pos.y + size.y),
imgui.get_color_u32(vec4(0, 0, 0, 0.2))
)
def _begin_window(self, name: str, p_open: Any = None, flags: int = 0) -> tuple[bool, Any]:
frosted = theme.get_frosted_glass_enabled()
if frosted:
imgui.push_style_color(imgui.Col_.window_bg, vec4(0, 0, 0, 0))
expanded, opened = imgui.begin(name, p_open, flags)
if expanded and frosted:
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
return expanded, opened
def _end_window(self) -> None:
imgui.end()
if theme.get_frosted_glass_enabled():
imgui.pop_style_color()
def _render_operations_hub(self) -> None:
exp, opened = self._begin_window("Operations Hub", self.show_windows["Operations Hub"])
self.show_windows["Operations Hub"] = bool(opened)
if exp:
imgui.text("Focus Agent:")
imgui.same_line()
focus_label = self.ui_focus_agent or "All"
if imgui.begin_combo("##focus_agent", focus_label, imgui.ComboFlags_.width_fit_preview):
if imgui.selectable("All", self.ui_focus_agent is None)[0]:
self.ui_focus_agent = None
for tier in ["Tier 2", "Tier 3", "Tier 4"]:
if imgui.selectable(tier, self.ui_focus_agent == tier)[0]:
self.ui_focus_agent = tier
imgui.end_combo()
imgui.same_line()
if self.ui_focus_agent:
if imgui.button("x##clear_focus"):
self.ui_focus_agent = None
imgui.push_style_var(imgui.StyleVar_.item_spacing, imgui.ImVec2(10, 4))
ch1, self.ui_separate_tool_calls_panel = imgui.checkbox("Pop Out Tool Calls", self.ui_separate_tool_calls_panel)
if ch1: self.show_windows["Tool Calls"] = self.ui_separate_tool_calls_panel
imgui.same_line()
ch2, self.ui_separate_usage_analytics = imgui.checkbox("Pop Out Usage Analytics", self.ui_separate_usage_analytics)
if ch2: self.show_windows["Usage Analytics"] = self.ui_separate_usage_analytics
imgui.same_line()
ch3, self.ui_separate_external_tools = imgui.checkbox('Pop Out External Tools', self.ui_separate_external_tools)
if ch3: self.show_windows['External Tools'] = self.ui_separate_external_tools
imgui.pop_style_var()
show_tc_tab = not self.ui_separate_tool_calls_panel
show_usage_tab = not self.ui_separate_usage_analytics
if imgui.begin_tab_bar("ops_tabs"):
if imgui.begin_tab_item("Comms History")[0]:
self._render_comms_history_panel()
imgui.end_tab_item()
if show_tc_tab:
if imgui.begin_tab_item("Tool Calls")[0]:
self._render_tool_calls_panel()
imgui.end_tab_item()
if show_usage_tab:
if imgui.begin_tab_item("Usage Analytics")[0]:
self._render_usage_analytics_panel()
imgui.end_tab_item()
if not self.ui_separate_external_tools:
if imgui.begin_tab_item("External Tools")[0]:
self._render_external_tools_panel()
imgui.end_tab_item()
imgui.end_tab_bar()
self._end_window()
def _show_menus(self) -> None:
if imgui.begin_menu("manual slop"):
if imgui.menu_item("Quit", "Ctrl+Q", False)[0]:
@@ -365,21 +489,123 @@ class App:
except Exception as e:
self.ai_status = f"error: {e}"
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:
# Obsolete, removed since it renders behind the full screen dock space.
# Controls are now embedded in _show_menus.
pass
def _render_shader_live_editor(self) -> None:
if self.show_windows.get('Shader Editor', False):
exp, opened = imgui.begin('Shader Editor', self.show_windows['Shader Editor'])
self.show_windows['Shader Editor'] = bool(opened)
if exp:
changed_crt, self.shader_uniforms['crt'] = imgui.slider_float('CRT Curvature', self.shader_uniforms['crt'], 0.0, 2.0)
changed_scan, self.shader_uniforms['scanline'] = imgui.slider_float('Scanline Intensity', self.shader_uniforms['scanline'], 0.0, 1.0)
changed_bloom, self.shader_uniforms['bloom'] = imgui.slider_float('Bloom Threshold', self.shader_uniforms['bloom'], 0.0, 1.0)
imgui.separator()
imgui.text("Frosted Glass")
changed_fbr, self.shader_uniforms['frosted_blur_radius'] = imgui.slider_float('Blur Radius', self.shader_uniforms['frosted_blur_radius'], 0.0, 64.0)
if changed_fbr: theme.set_frosted_blur_radius(self.shader_uniforms['frosted_blur_radius'])
changed_fti, self.shader_uniforms['frosted_tint_intensity'] = imgui.slider_float('Tint Intensity', self.shader_uniforms['frosted_tint_intensity'], 0.0, 1.0)
if changed_fti: theme.set_frosted_tint_intensity(self.shader_uniforms['frosted_tint_intensity'])
changed_fo, self.shader_uniforms['frosted_opacity'] = imgui.slider_float('Frosted Opacity', self.shader_uniforms['frosted_opacity'], 0.0, 1.0)
if changed_fo: theme.set_frosted_opacity(self.shader_uniforms['frosted_opacity'])
imgui.end()
def _gui_func(self) -> None:
self._render_custom_title_bar()
self._render_shader_live_editor()
pushed_prior_tint = False
pushed_frosted_style = False
# Render background shader
bg = bg_shader.get_bg()
if bg.enabled:
frosted_enabled = theme.get_frosted_glass_enabled()
if bg.enabled and not frosted_enabled:
ws = imgui.get_io().display_size
bg.render(ws.x, ws.y)
if frosted_enabled:
ws = imgui.get_io().display_size
self.shader_manager.prepare_global_blur(
int(ws.x), int(ws.y),
self.shader_uniforms['frosted_blur_radius'],
self.shader_uniforms['frosted_tint_intensity'],
self.shader_uniforms['frosted_opacity'],
time.time()
)
# Draw background texture
dl = imgui.get_background_draw_list()
dl.add_image(imgui.ImTextureRef(self.shader_manager.scene_tex), (0, 0), (ws.x, ws.y))
imgui.push_style_color(imgui.Col_.window_bg, vec4(0, 0, 0, 0))
pushed_frosted_style = True
if theme.is_nerv_active():
ws = imgui.get_io().display_size
self._nerv_alert.update(self.ai_status)
self._nerv_alert.render(ws.x, ws.y)
self._nerv_crt.enabled = self.ui_crt_filter
self._nerv_crt.render(ws.x, ws.y)
if self.perf_profiling_enabled: self.perf_monitor.start_component("_gui_func")
if self.is_viewing_prior_session:
imgui.push_style_color(imgui.Col_.window_bg, vec4(50, 40, 20))
@@ -441,15 +667,18 @@ class App:
self._comms_log_dirty = False
if self._tool_log_dirty:
log_raw = list(self._tool_log)
if self.ui_focus_agent:
self._tool_log_cache = [e for e in log_raw if e.get("source_tier", "").startswith(self.ui_focus_agent)]
if self.is_viewing_prior_session:
self._tool_log_cache = self.prior_tool_calls
else:
self._tool_log_cache = log_raw
log_raw = list(self._tool_log)
if self.ui_focus_agent:
self._tool_log_cache = [e for e in log_raw if e.get("source_tier", "").startswith(self.ui_focus_agent)]
else:
self._tool_log_cache = log_raw
self._tool_log_dirty = False
if self.show_windows.get("Context Hub", False):
exp, opened = imgui.begin("Context Hub", self.show_windows["Context Hub"])
exp, opened = self._begin_window("Context Hub", self.show_windows["Context Hub"])
self.show_windows["Context Hub"] = bool(opened)
if exp:
if imgui.begin_tab_bar('context_hub_tabs'):
@@ -460,18 +689,18 @@ class App:
self._render_paths_panel()
imgui.end_tab_item()
imgui.end_tab_bar()
imgui.end()
self._end_window()
if self.show_windows.get("Files & Media", False):
exp, opened = imgui.begin("Files & Media", self.show_windows["Files & Media"])
exp, opened = self._begin_window("Files & Media", self.show_windows["Files & Media"])
self.show_windows["Files & Media"] = bool(opened)
if exp:
if imgui.collapsing_header("Files"):
self._render_files_panel()
if imgui.collapsing_header("Screenshots"):
self._render_screenshots_panel()
imgui.end()
self._end_window()
if self.show_windows.get("AI Settings", False):
exp, opened = imgui.begin("AI Settings", self.show_windows["AI Settings"])
exp, opened = self._begin_window("AI Settings", self.show_windows["AI Settings"])
self.show_windows["AI Settings"] = bool(opened)
if exp:
self._render_persona_selector_panel()
@@ -481,64 +710,68 @@ class App:
self._render_system_prompts_panel()
self._render_agent_tools_panel()
imgui.end()
self._end_window()
if self.ui_separate_usage_analytics and self.show_windows.get("Usage Analytics", False):
exp, opened = imgui.begin("Usage Analytics", self.show_windows["Usage Analytics"])
exp, opened = self._begin_window("Usage Analytics", self.show_windows["Usage Analytics"])
self.show_windows["Usage Analytics"] = bool(opened)
if exp:
self._render_usage_analytics_panel()
imgui.end()
self._end_window()
if self.show_windows.get("MMA Dashboard", False):
exp, opened = imgui.begin("MMA Dashboard", self.show_windows["MMA Dashboard"])
exp, opened = self._begin_window("MMA Dashboard", self.show_windows["MMA Dashboard"])
self.show_windows["MMA Dashboard"] = bool(opened)
if exp:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_mma_dashboard")
self._render_mma_dashboard()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_mma_dashboard")
imgui.end()
self._end_window()
if self.ui_separate_task_dag and self.show_windows.get("Task DAG", False):
exp, opened = imgui.begin("Task DAG", self.show_windows["Task DAG"])
exp, opened = self._begin_window("Task DAG", self.show_windows["Task DAG"])
self.show_windows["Task DAG"] = bool(opened)
if exp:
self._render_task_dag_panel()
imgui.end()
self._end_window()
if self.ui_separate_tier1 and self.show_windows.get("Tier 1: Strategy", False):
exp, opened = imgui.begin("Tier 1: Strategy", self.show_windows["Tier 1: Strategy"])
exp, opened = self._begin_window("Tier 1: Strategy", self.show_windows["Tier 1: Strategy"])
self.show_windows["Tier 1: Strategy"] = bool(opened)
if exp:
self._render_tier_stream_panel("Tier 1", "Tier 1")
imgui.end()
self._end_window()
if self.ui_separate_tier2 and self.show_windows.get("Tier 2: Tech Lead", False):
exp, opened = imgui.begin("Tier 2: Tech Lead", self.show_windows["Tier 2: Tech Lead"])
exp, opened = self._begin_window("Tier 2: Tech Lead", self.show_windows["Tier 2: Tech Lead"])
self.show_windows["Tier 2: Tech Lead"] = bool(opened)
if exp:
self._render_tier_stream_panel("Tier 2", "Tier 2 (Tech Lead)")
imgui.end()
self._end_window()
if self.ui_separate_tier3 and self.show_windows.get("Tier 3: Workers", False):
exp, opened = imgui.begin("Tier 3: Workers", self.show_windows["Tier 3: Workers"])
exp, opened = self._begin_window("Tier 3: Workers", self.show_windows["Tier 3: Workers"])
self.show_windows["Tier 3: Workers"] = bool(opened)
if exp:
self._render_tier_stream_panel("Tier 3", None)
imgui.end()
self._end_window()
if self.ui_separate_tier4 and self.show_windows.get("Tier 4: QA", False):
exp, opened = imgui.begin("Tier 4: QA", self.show_windows["Tier 4: QA"])
exp, opened = self._begin_window("Tier 4: QA", self.show_windows["Tier 4: QA"])
self.show_windows["Tier 4: QA"] = bool(opened)
if exp:
self._render_tier_stream_panel("Tier 4", "Tier 4 (QA)")
imgui.end()
self._end_window()
if self.show_windows.get("Theme", False):
self._render_theme_panel()
if self.show_windows.get("Discussion Hub", False):
exp, opened = imgui.begin("Discussion Hub", self.show_windows["Discussion Hub"])
exp, opened = self._begin_window("Discussion Hub", self.show_windows["Discussion Hub"])
self.show_windows["Discussion Hub"] = bool(opened)
if exp:
# Top part for the history
imgui.begin_child("HistoryChild", size=(0, -200))
imgui.begin_child("HistoryChild", size=(0, -self.ui_discussion_split_h))
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_discussion_panel")
self._render_discussion_panel()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_discussion_panel")
imgui.end_child()
# Splitter
imgui.button("###discussion_splitter", imgui.ImVec2(-1, 4))
if imgui.is_item_active():
self.ui_discussion_split_h = max(150.0, min(imgui.get_window_height() - 150.0, self.ui_discussion_split_h - imgui.get_io().mouse_delta.y))
# Bottom part with tabs for message and response
# Detach controls
imgui.push_style_var(imgui.StyleVar_.item_spacing, imgui.ImVec2(10, 4))
@@ -573,86 +806,37 @@ class App:
else:
imgui.text_disabled("Message & Response panels are detached.")
imgui.end()
self._end_window()
if self.show_windows.get("Operations Hub", False):
exp, opened = imgui.begin("Operations Hub", self.show_windows["Operations Hub"])
self.show_windows["Operations Hub"] = bool(opened)
if exp:
imgui.text("Focus Agent:")
imgui.same_line()
focus_label = self.ui_focus_agent or "All"
if imgui.begin_combo("##focus_agent", focus_label, imgui.ComboFlags_.width_fit_preview):
if imgui.selectable("All", self.ui_focus_agent is None)[0]:
self.ui_focus_agent = None
for tier in ["Tier 2", "Tier 3", "Tier 4"]:
if imgui.selectable(tier, self.ui_focus_agent == tier)[0]:
self.ui_focus_agent = tier
imgui.end_combo()
imgui.same_line()
if self.ui_focus_agent:
if imgui.button("x##clear_focus"):
self.ui_focus_agent = None
if exp:
imgui.push_style_var(imgui.StyleVar_.item_spacing, imgui.ImVec2(10, 4))
ch1, self.ui_separate_tool_calls_panel = imgui.checkbox("Pop Out Tool Calls", self.ui_separate_tool_calls_panel)
if ch1: self.show_windows["Tool Calls"] = self.ui_separate_tool_calls_panel
imgui.same_line()
ch2, self.ui_separate_usage_analytics = imgui.checkbox("Pop Out Usage Analytics", self.ui_separate_usage_analytics)
if ch2: self.show_windows["Usage Analytics"] = self.ui_separate_usage_analytics
imgui.same_line()
ch3, self.ui_separate_external_tools = imgui.checkbox('Pop Out External Tools', self.ui_separate_external_tools)
if ch3: self.show_windows['External Tools'] = self.ui_separate_external_tools
imgui.pop_style_var()
show_tc_tab = not self.ui_separate_tool_calls_panel
show_usage_tab = not self.ui_separate_usage_analytics
if imgui.begin_tab_bar("ops_tabs"):
if imgui.begin_tab_item("Comms History")[0]:
self._render_comms_history_panel()
imgui.end_tab_item()
if show_tc_tab:
if imgui.begin_tab_item("Tool Calls")[0]:
self._render_tool_calls_panel()
imgui.end_tab_item()
if show_usage_tab:
if imgui.begin_tab_item("Usage Analytics")[0]:
self._render_usage_analytics_panel()
imgui.end_tab_item()
if not self.ui_separate_external_tools:
if imgui.begin_tab_item("External Tools")[0]:
self._render_external_tools_panel()
imgui.end_tab_item()
imgui.end_tab_bar()
imgui.end()
self._render_operations_hub()
if self.ui_separate_message_panel and self.show_windows.get("Message", False):
exp, opened = imgui.begin("Message", self.show_windows["Message"])
exp, opened = self._begin_window("Message", self.show_windows["Message"])
self.show_windows["Message"] = bool(opened)
if exp:
self._render_message_panel()
imgui.end()
self._end_window()
if self.ui_separate_response_panel and self.show_windows.get("Response", False):
exp, opened = imgui.begin("Response", self.show_windows["Response"])
exp, opened = self._begin_window("Response", self.show_windows["Response"])
self.show_windows["Response"] = bool(opened)
if exp:
self._render_response_panel()
imgui.end()
self._end_window()
if self.ui_separate_tool_calls_panel and self.show_windows.get("Tool Calls", False):
exp, opened = imgui.begin("Tool Calls", self.show_windows["Tool Calls"])
exp, opened = self._begin_window("Tool Calls", self.show_windows["Tool Calls"])
self.show_windows["Tool Calls"] = bool(opened)
if exp:
self._render_tool_calls_panel()
imgui.end()
self._end_window()
if self.ui_separate_external_tools and self.show_windows.get('External Tools', False):
exp, opened = imgui.begin('External Tools', self.show_windows['External Tools'])
exp, opened = self._begin_window('External Tools', self.show_windows['External Tools'])
self.show_windows['External Tools'] = bool(opened)
if exp:
self._render_external_tools_panel()
imgui.end()
self._end_window()
if self.show_windows.get("Log Management", False):
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_log_management")
@@ -843,6 +1027,7 @@ class App:
expanded, opened = imgui.begin("Last Script Output", self.show_script_output)
self.show_script_output = bool(opened)
if expanded:
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
imgui.text("Script:")
imgui.same_line()
self._render_text_viewer("Last Script", self.ui_last_script_text)
@@ -874,6 +1059,7 @@ class App:
expanded, opened = imgui.begin(f"Text Viewer - {self.text_viewer_title}", self.show_text_viewer)
self.show_text_viewer = bool(opened)
if expanded:
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
if self.ui_word_wrap:
imgui.begin_child("tv_wrap", imgui.ImVec2(-1, -1), False)
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
@@ -933,6 +1119,8 @@ class App:
if pushed_prior_tint:
imgui.pop_style_color()
if pushed_frosted_style:
imgui.pop_style_color()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_gui_func")
@@ -1819,7 +2007,6 @@ class App:
imgui.text(f"History: {key}")
hist_data = self.perf_monitor.get_history(key)
if hist_data:
import numpy as np
imgui.plot_lines(f"##plot_{key}", np.array(hist_data, dtype=np.float32), graph_size=imgui.ImVec2(-1, 60))
else:
imgui.text_disabled(f"(no history data for {key})")
@@ -1967,6 +2154,7 @@ def hello():
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_screenshots_panel")
def _render_discussion_panel(self) -> None:
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_discussion_panel")
# THINKING indicator
is_thinking = self.ai_status in ['sending...', 'streaming...', 'running powershell...']
@@ -1984,9 +2172,7 @@ def hello():
imgui.text_colored(vec4(255, 200, 100), "VIEWING PRIOR SESSION")
imgui.same_line()
if imgui.button("Exit Prior Session"):
self.is_viewing_prior_session = False
self.prior_session_entries.clear()
self.prior_disc_entries.clear()
self.controller.cb_exit_prior_session()
self._comms_log_dirty = True
imgui.separator()
imgui.begin_child("prior_scroll", imgui.ImVec2(0, 0), False)
@@ -2589,17 +2775,23 @@ def hello():
ch, self.ui_ai_input = imgui.input_text_multiline("##ai_in", self.ui_ai_input, imgui.ImVec2(-1, -40))
# Keyboard shortcuts
io = imgui.get_io()
ctrl_enter = io.key_ctrl and imgui.is_key_pressed(imgui.Key.enter)
ctrl_l = io.key_ctrl and imgui.is_key_pressed(imgui.Key.l)
if ctrl_l:
self.ui_ai_input = ""
imgui.separator()
is_busy = self.ai_status in ['sending...', 'streaming...']
send_busy = False
with self._send_thread_lock:
if self.send_thread and self.send_thread.is_alive():
send_busy = True
if (imgui.button("Gen + Send") or ctrl_enter) and not send_busy:
if is_busy: send_busy = True
imgui.begin_disabled(send_busy)
ctrl_enter = io.key_ctrl and imgui.is_key_pressed(imgui.Key.enter)
label = "Gen + Send (Busy)" if send_busy else "Gen + Send"
if (imgui.button(label) or ctrl_enter) and not send_busy:
self._handle_generate_send()
imgui.end_disabled()
imgui.same_line()
if imgui.button("MD Only"):
self._handle_md_only()
@@ -2715,10 +2907,8 @@ def hello():
if self.is_viewing_prior_session:
imgui.same_line()
if imgui.button("Exit Prior Session"):
self.is_viewing_prior_session = False
self.prior_session_entries.clear()
self.controller.cb_exit_prior_session()
self._comms_log_dirty = True
self.ai_status = "idle"
imgui.separator()
imgui.text_colored(C_OUT, "OUT")
@@ -3788,6 +3978,7 @@ def hello():
exp, opened = imgui.begin("Theme", self.show_windows["Theme"])
self.show_windows["Theme"] = bool(opened)
if exp:
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
imgui.text("Palette")
cp = theme.get_current_palette()
if imgui.begin_combo("##pal", cp):
@@ -3861,6 +4052,10 @@ def hello():
gui_cfg["crt_filter_enabled"] = self.ui_crt_filter
self._flush_to_config()
models.save_config(self.config)
ch_fg, fg_val = imgui.checkbox("Frosted Glass Effect", theme.get_frosted_glass_enabled())
if ch_fg:
theme.set_frosted_glass_enabled(fg_val)
self._flush_to_config()
models.save_config(self.config)
imgui.end()
@@ -3906,6 +4101,8 @@ def hello():
def _post_init(self) -> None:
theme.apply_current()
self.shader_manager.setup_background_shader()
self.shader_manager.setup_frosted_glass_shader()
def run(self) -> None:
"""Initializes the ImGui runner and starts the main application loop."""
@@ -3921,6 +4118,13 @@ def hello():
theme.load_from_config(self.config)
self.runner_params = hello_imgui.RunnerParams()
self.runner_params.app_window_params.window_title = "manual slop"
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.imgui_window_params.enable_viewports = getattr(self, "ui_multi_viewport", False)
self.runner_params.imgui_window_params.remember_theme = True
@@ -3931,15 +4135,16 @@ def hello():
user_scale = theme.get_current_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
try:
# Use PowerShell to get max refresh rate across all controllers
cmd = "powershell -NoProfile -Command \"Get-CimInstance -ClassName Win32_VideoController | Select-Object -ExpandProperty CurrentRefreshRate\""
out = subprocess.check_output(cmd, shell=True).decode().splitlines()
rates = [float(r.strip()) for r in out if r.strip().isdigit()]
if rates: fps_cap = max(rates)
except Exception: pass
if sys.platform == "win32":
try:
# Use PowerShell to get max refresh rate across all controllers
cmd = "powershell -NoProfile -Command \"Get-CimInstance -ClassName Win32_VideoController | Select-Object -ExpandProperty CurrentRefreshRate\""
out = subprocess.check_output(cmd, shell=True).decode().splitlines()
rates = [float(r.strip()) for r in out if r.strip().isdigit()]
if rates: fps_cap = max(rates)
except Exception: pass
# Enable idling with monitor refresh rate to effectively cap FPS
self.runner_params.fps_idling.enable_idling = True
+404
View File
@@ -0,0 +1,404 @@
import OpenGL.GL as gl
class ShaderManager:
def __init__(self):
self.program = None
self.bg_program = None
self.pp_program = None
self.blur_h_program = None
self.blur_v_program = None
self.blur_fbo = None
self.scene_fbo = None
self.temp_fbo = None
self.scene_tex = None
self.blur_tex = None
self.temp_tex = None
self.fbo_width = 0
self.fbo_height = 0
self._vao = None
def _ensure_vao(self):
if self._vao is None:
try:
import sys
if sys.platform == "win32":
self._vao = gl.glGenVertexArrays(1)
else:
# Some non-win32 environments might not support VAOs or need different handling
self._vao = gl.glGenVertexArrays(1)
except Exception:
pass
if self._vao is not None:
gl.glBindVertexArray(self._vao)
def setup_capture_fbo(self, width, height):
if self.blur_fbo is not None:
gl.glDeleteFramebuffers(1, [self.blur_fbo])
if self.scene_fbo is not None:
gl.glDeleteFramebuffers(1, [self.scene_fbo])
if self.temp_fbo is not None:
gl.glDeleteFramebuffers(1, [self.temp_fbo])
if self.scene_tex is not None:
gl.glDeleteTextures(1, [self.scene_tex])
if self.blur_tex is not None:
gl.glDeleteTextures(1, [self.blur_tex])
if self.temp_tex is not None:
gl.glDeleteTextures(1, [self.temp_tex])
self.scene_tex = gl.glGenTextures(1)
gl.glBindTexture(gl.GL_TEXTURE_2D, self.scene_tex)
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, width, height, 0, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, None)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
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)
self.scene_fbo = gl.glGenFramebuffers(1)
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.scene_fbo)
gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D, self.scene_tex, 0)
if gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER) != gl.GL_FRAMEBUFFER_COMPLETE:
raise RuntimeError("Scene Framebuffer not complete")
self.temp_tex = gl.glGenTextures(1)
gl.glBindTexture(gl.GL_TEXTURE_2D, self.temp_tex)
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, width, height, 0, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, None)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
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)
self.temp_fbo = gl.glGenFramebuffers(1)
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.temp_fbo)
gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D, self.temp_tex, 0)
if gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER) != gl.GL_FRAMEBUFFER_COMPLETE:
raise RuntimeError("Temp Framebuffer not complete")
self.blur_tex = gl.glGenTextures(1)
gl.glBindTexture(gl.GL_TEXTURE_2D, self.blur_tex)
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, width, height, 0, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, None)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
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)
self.blur_fbo = gl.glGenFramebuffers(1)
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo)
gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D, self.blur_tex, 0)
if gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER) != gl.GL_FRAMEBUFFER_COMPLETE:
raise RuntimeError("Blur Framebuffer not complete")
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
self.fbo_width = width
self.fbo_height = height
def render_background_to_fbo(self, width, height, time):
if self.scene_fbo is None or self.fbo_width != width or self.fbo_height != height:
self.setup_capture_fbo(width, height)
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.scene_fbo)
gl.glViewport(0, 0, width, height)
self.render_background(width, height, time)
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
def prepare_global_blur(self, width, height, radius, tint, opacity, time):
self.render_background_to_fbo(width, height, time)
self.render_blur(self.scene_tex, width, height, radius, tint, opacity)
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
def capture_begin(self, width, height):
if self.blur_fbo is None or self.fbo_width != width or self.fbo_height != height:
self.setup_capture_fbo(width, height)
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo)
gl.glViewport(0, 0, width, height)
def capture_end(self):
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
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;
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(mix(hash(i + vec2(0.0, 0.0)), hash(i + vec2(1.0, 0.0)), u.x),
mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x), u.y);
}
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
vec2 p = uv * 2.0 - 1.0;
p.x *= u_resolution.x / u_resolution.y;
// Deep sea background gradient (dark blue)
vec3 col = mix(vec3(0.01, 0.03, 0.08), vec3(0.0, 0.08, 0.15), uv.y);
// Moving blobs / caustics
float n = 0.0;
float t = u_time * 0.15;
n += noise(p * 1.2 + vec2(t * 0.8, t * 0.5)) * 0.4;
n += noise(p * 2.5 - vec2(t * 0.4, t * 0.9)) * 0.2;
col += vec3(0.05, 0.12, 0.22) * n;
// Bright highlights (caustics approximation)
float c = 0.0;
for(int i=0; i<3; i++) {
vec2 p2 = p * (float(i) + 1.0) * 0.4;
p2 += vec2(sin(t + p2.y * 1.5), cos(t + p2.x * 1.5));
c += abs(0.015 / (length(p2) - 0.4));
}
col += vec3(0.1, 0.25, 0.45) * c * 0.12;
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))
self._ensure_vao()
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))
self._ensure_vao()
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
gl.glUseProgram(0)
def setup_frosted_glass_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_h = """
#version 330 core
in vec2 v_uv;
uniform sampler2D u_texture;
uniform float u_blur_radius;
uniform vec2 u_direction;
out vec4 FragColor;
void main() {
float weight[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
vec2 res = vec2(textureSize(u_texture, 0));
vec2 tex_offset = (u_blur_radius / res) * u_direction * 2.5; // Multiplied by 2.5 for milky effect
vec4 result = texture(u_texture, v_uv) * weight[0];
for(int i = 1; i < 5; ++i) {
result += texture(u_texture, v_uv + tex_offset * float(i)) * weight[i];
result += texture(u_texture, v_uv - tex_offset * float(i)) * weight[i];
}
FragColor = result;
}
"""
fragment_src_v = """
#version 330 core
in vec2 v_uv;
uniform sampler2D u_texture;
uniform float u_blur_radius;
uniform vec2 u_direction;
uniform float u_tint_intensity;
uniform float u_opacity;
out vec4 FragColor;
void main() {
float weight[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
vec2 res = vec2(textureSize(u_texture, 0));
vec2 tex_offset = (u_blur_radius / res) * u_direction * 2.5; // Multiplied by 2.5 for milky effect
vec4 result = texture(u_texture, v_uv) * weight[0];
for(int i = 1; i < 5; ++i) {
result += texture(u_texture, v_uv + tex_offset * float(i)) * weight[i];
result += texture(u_texture, v_uv - tex_offset * float(i)) * weight[i];
}
vec3 tint_color = vec3(0.05, 0.07, 0.12); // Slightly deeper tint
vec3 tinted = mix(result.rgb, tint_color, u_tint_intensity);
FragColor = vec4(tinted, result.a * u_opacity);
}
"""
self.blur_h_program = self.compile_shader(vertex_src, fragment_src_h)
self.blur_v_program = self.compile_shader(vertex_src, fragment_src_v)
def render_blur(self, texture_id, width, height, radius, tint, opacity):
if not self.blur_h_program or not self.blur_v_program:
return
self._ensure_vao()
# Pass 1: Horizontal blur to temp_fbo
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.temp_fbo)
gl.glViewport(0, 0, width, height)
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
gl.glUseProgram(self.blur_h_program)
gl.glActiveTexture(gl.GL_TEXTURE0)
gl.glBindTexture(gl.GL_TEXTURE_2D, texture_id)
gl.glUniform1i(gl.glGetUniformLocation(self.blur_h_program, "u_texture"), 0)
gl.glUniform1f(gl.glGetUniformLocation(self.blur_h_program, "u_blur_radius"), float(radius))
gl.glUniform2f(gl.glGetUniformLocation(self.blur_h_program, "u_direction"), 1.0, 0.0)
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
# Pass 2: Vertical blur to blur_fbo
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo)
gl.glViewport(0, 0, width, height)
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
gl.glUseProgram(self.blur_v_program)
gl.glActiveTexture(gl.GL_TEXTURE0)
gl.glBindTexture(gl.GL_TEXTURE_2D, self.temp_tex)
gl.glUniform1i(gl.glGetUniformLocation(self.blur_v_program, "u_texture"), 0)
gl.glUniform1f(gl.glGetUniformLocation(self.blur_v_program, "u_blur_radius"), float(radius))
gl.glUniform2f(gl.glGetUniformLocation(self.blur_v_program, "u_direction"), 0.0, 1.0)
gl.glUniform1f(gl.glGetUniformLocation(self.blur_v_program, "u_tint_intensity"), float(tint))
gl.glUniform1f(gl.glGetUniformLocation(self.blur_v_program, "u_opacity"), float(opacity))
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
gl.glUseProgram(0)
+19
View File
@@ -268,6 +268,12 @@ _current_palette: str = "DPG Default"
_current_font_path: str = ""
_current_font_size: float = 14.0
_current_scale: float = 1.0
_shader_config: dict[str, Any] = {
"crt": False,
"bloom": False,
"bg": "none",
"custom_window_frame": False,
}
# ------------------------------------------------------------------ public API
@@ -286,6 +292,14 @@ def get_current_font_size() -> float:
def get_current_scale() -> float:
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]:
"""Return a copy of the colour dict for the named palette."""
return dict(_PALETTES.get(name, {}))
@@ -388,4 +402,9 @@ def load_from_config(config: dict[str, Any]) -> None:
if font_path:
apply_font(font_path, font_size)
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)
+41 -1
View File
@@ -235,6 +235,10 @@ _current_font_size: float = 16.0
_current_scale: float = 1.0
_transparency: float = 1.0
_child_transparency: float = 1.0
_frosted_glass_enabled: bool = False
_frosted_blur_radius: float = 8.0
_frosted_tint_intensity: float = 0.1
_frosted_opacity: float = 1.0
# ------------------------------------------------------------------ public API
@@ -269,6 +273,34 @@ def set_child_transparency(val: float) -> None:
_child_transparency = val
apply(_current_palette)
def get_frosted_glass_enabled() -> bool:
return _frosted_glass_enabled
def set_frosted_glass_enabled(val: bool) -> None:
global _frosted_glass_enabled
_frosted_glass_enabled = val
def get_frosted_blur_radius() -> float:
return _frosted_blur_radius
def set_frosted_blur_radius(val: float) -> None:
global _frosted_blur_radius
_frosted_blur_radius = val
def get_frosted_tint_intensity() -> float:
return _frosted_tint_intensity
def set_frosted_tint_intensity(val: float) -> None:
global _frosted_tint_intensity
_frosted_tint_intensity = val
def get_frosted_opacity() -> float:
return _frosted_opacity
def set_frosted_opacity(val: float) -> None:
global _frosted_opacity
_frosted_opacity = val
def apply(palette_name: str) -> None:
"""
Apply a named palette by setting all ImGui style colors and applying global professional styling.
@@ -350,13 +382,17 @@ def save_to_config(config: dict) -> None:
config["theme"]["scale"] = _current_scale
config["theme"]["transparency"] = _transparency
config["theme"]["child_transparency"] = _child_transparency
config["theme"]["frosted_glass_enabled"] = _frosted_glass_enabled
config["theme"]["frosted_blur_radius"] = _frosted_blur_radius
config["theme"]["frosted_tint_intensity"] = _frosted_tint_intensity
config["theme"]["frosted_opacity"] = _frosted_opacity
sys.stderr.write(f"[DEBUG theme_2] save_to_config: palette={_current_palette}, transparency={_transparency}\n")
sys.stderr.flush()
def load_from_config(config: dict) -> None:
"""Read [theme] from config. Font is handled separately at startup."""
import sys
global _current_font_path, _current_font_size, _current_scale, _current_palette, _transparency, _child_transparency
global _current_font_path, _current_font_size, _current_scale, _current_palette, _transparency, _child_transparency, _frosted_glass_enabled, _frosted_blur_radius, _frosted_tint_intensity, _frosted_opacity
t = config.get("theme", {})
sys.stderr.write(f"[DEBUG theme_2] load_from_config raw: {t}\n")
sys.stderr.flush()
@@ -369,6 +405,10 @@ def load_from_config(config: dict) -> None:
_current_scale = float(t.get("scale", 1.0))
_transparency = float(t.get("transparency", 1.0))
_child_transparency = float(t.get("child_transparency", 1.0))
_frosted_glass_enabled = bool(t.get("frosted_glass_enabled", False))
_frosted_blur_radius = float(t.get("frosted_blur_radius", 8.0))
_frosted_tint_intensity = float(t.get("frosted_tint_intensity", 0.1))
_frosted_opacity = float(t.get("frosted_opacity", 1.0))
sys.stderr.write(f"[DEBUG theme_2] load_from_config effective: palette={_current_palette}, transparency={_transparency}\n")
sys.stderr.flush()
+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()
+42
View File
@@ -0,0 +1,42 @@
import pytest
from unittest.mock import patch, MagicMock
import OpenGL.GL as gl
def test_shader_manager_fbo_initialization():
with patch("src.shader_manager.gl") as mock_gl:
mock_gl.glGenFramebuffers.side_effect = [1, 2]
mock_gl.glGenTextures.side_effect = [3, 4]
mock_gl.glCheckFramebufferStatus.return_value = mock_gl.GL_FRAMEBUFFER_COMPLETE
from src.shader_manager import ShaderManager
manager = ShaderManager()
manager.setup_capture_fbo(800, 600)
assert manager.scene_fbo == 1
assert manager.blur_fbo == 2
assert manager.scene_tex == 3
assert manager.blur_tex == 4
assert mock_gl.glGenFramebuffers.call_count == 2
assert mock_gl.glGenTextures.call_count == 2
assert mock_gl.glCheckFramebufferStatus.call_count == 2
def test_shader_manager_capture_lifecycle():
with patch("src.shader_manager.gl") as mock_gl:
mock_gl.glCheckFramebufferStatus.return_value = mock_gl.GL_FRAMEBUFFER_COMPLETE
mock_gl.glGenFramebuffers.side_effect = [1, 2]
mock_gl.glGenTextures.side_effect = [3, 4]
from src.shader_manager import ShaderManager
manager = ShaderManager()
# Ensure setup is called on first capture
manager.capture_begin(1024, 768)
assert manager.fbo_width == 1024
assert manager.fbo_height == 768
# Should bind the blur FBO
mock_gl.glBindFramebuffer.assert_any_call(mock_gl.GL_FRAMEBUFFER, manager.blur_fbo)
mock_gl.glBindFramebuffer.reset_mock()
manager.capture_end()
# Verify unbind (glBindFramebuffer(..., 0))
mock_gl.glBindFramebuffer.assert_called_with(mock_gl.GL_FRAMEBUFFER, 0)
+19
View File
@@ -0,0 +1,19 @@
import pytest
from unittest.mock import patch, MagicMock
def test_shader_manager_frosted_glass_compilation():
# Mock OpenGL before importing 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()
# This should fail initially because the method doesn't exist
manager.setup_frosted_glass_shader()
assert manager.blur_program is not None
assert mock_gl.glCreateProgram.called
+15
View File
@@ -0,0 +1,15 @@
import pytest
from unittest.mock import patch
from src.gui_2 import App
@patch("src.gui_2.immapp.run")
@patch("src.gui_2.session_logger.close_session")
@patch("src.gui_2.imgui.save_ini_settings_to_disk")
@patch("sys.argv", ["gui_2.py"])
def test_app_window_is_borderless(mock_save_ini, mock_close, mock_run):
app = App()
app.run()
assert app.runner_params is not None
# This assertion will fail initially because we haven't implemented it yet
assert getattr(app.runner_params.app_window_params, 'borderless', False) is True, "Window should be borderless"
+89
View File
@@ -0,0 +1,89 @@
import pytest
import time
from unittest.mock import patch, MagicMock
def test_gui_frosted_background_call():
# Mock ShaderManager and OpenGL functions
with patch("src.gui_2.ShaderManager") as mock_sm_class, \
patch("src.gui_2.gl") as mock_gl, \
patch("src.gui_2.imgui") as mock_imgui, \
patch("src.gui_2.theme") as mock_theme:
mock_sm = mock_sm_class.return_value
mock_sm.blur_tex = 2
mock_theme.get_frosted_glass_enabled.return_value = True
mock_imgui.get_io().display_size = MagicMock(x=1920, y=1080)
from src.gui_2 import App
with patch.object(App, '__init__', return_value=None):
app = App()
app.shader_manager = mock_sm
# Simulate frame
app._render_frosted_background(pos=MagicMock(x=10, y=10), size=MagicMock(x=100, y=100))
# Now it should only call add_image
assert mock_imgui.get_window_draw_list().add_image.called
# It no longer calls these
assert not mock_sm.setup_capture_fbo.called
assert not mock_sm.render_blur.called
assert not mock_sm.capture_begin.called
assert not mock_sm.capture_end.called
def test_gui_global_blur_call():
with patch("src.gui_2.ShaderManager") as mock_sm_class, \
patch("src.gui_2.imgui") as mock_imgui, \
patch("src.gui_2.theme") as mock_theme, \
patch("src.gui_2.bg_shader") as mock_bg:
mock_sm = mock_sm_class.return_value
mock_theme.get_frosted_glass_enabled.return_value = True
mock_theme.is_nerv_active.return_value = False
mock_imgui.get_io().display_size = MagicMock(x=1920, y=1080)
mock_bg.get_bg.return_value = MagicMock(enabled=False)
from src.gui_2 import App
with patch.object(App, '__init__', return_value=None):
app = App()
app.shader_manager = mock_sm
app.shader_uniforms = {
'frosted_blur_radius': 10.0,
'frosted_tint_intensity': 0.5,
'frosted_opacity': 0.8
}
app.ai_status = "idle"
app.ui_crt_filter = False
app.controller = MagicMock()
app.perf_profiling_enabled = False
app.is_viewing_prior_session = False
app.config = {}
app.project = {}
app.show_windows = {}
app.ui_auto_scroll_comms = False
app._comms_log_dirty = False
app._tool_log_dirty = False
app._pending_comms_lock = MagicMock()
app._pending_comms = []
app.ui_focus_agent = None
app._last_ui_focus_agent = None
app.perf_monitor = MagicMock()
app._process_pending_gui_tasks = MagicMock()
app._process_pending_history_adds = MagicMock()
app._render_track_proposal_modal = MagicMock()
app._render_patch_modal = MagicMock()
app._render_save_preset_modal = MagicMock()
app._render_preset_manager_window = MagicMock()
app._render_tool_preset_manager_window = MagicMock()
app._render_persona_editor_window = MagicMock()
app._last_autosave = time.time()
app._autosave_interval = 60
app._render_custom_title_bar = MagicMock()
app._render_shader_live_editor = MagicMock()
try:
app._gui_func()
except Exception:
pass
assert mock_sm.prepare_global_blur.called
+43
View File
@@ -0,0 +1,43 @@
import pytest
from unittest.mock import patch, MagicMock
from imgui_bundle import imgui
def test_gui_window_controls_minimize_maximize_close():
# We will test the logic of the title bar controls that we are about to implement.
from src.gui_2 import App
app = App()
with patch("src.gui_2.win32gui") as mock_win32gui, \
patch("src.gui_2.win32con") as mock_win32con, \
patch("src.gui_2.imgui") as mock_imgui, \
patch("ctypes.pythonapi.PyCapsule_GetPointer") as mock_get_pointer:
# Setup mock for HWND
mock_viewport = MagicMock()
mock_viewport.platform_handle_raw = "mock_capsule"
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
# Let's say _render_custom_title_bar uses imgui.button
# We will test the close button logic
# Since it's UI code, we just simulate the conditions
mock_imgui.button.return_value = True # Simulate all buttons being clicked
# Avoid hitting actual menu logic that requires real runner_params
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
# Since all buttons returned True, all actions should be triggered in this dummy test
assert mock_win32gui.ShowWindow.called
assert mock_win32gui.PostMessage.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
+51
View File
@@ -0,0 +1,51 @@
import pytest
from unittest.mock import patch, MagicMock
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)
+41 -26
View File
@@ -3,33 +3,48 @@ from unittest.mock import MagicMock
from src import theme_2 as theme
def test_theme_apply_sets_rounding_and_padding(monkeypatch):
# Mock imgui
mock_style = MagicMock()
mock_imgui = MagicMock()
mock_imgui.get_style.return_value = mock_style
mock_imgui.ImVec2.side_effect = lambda x, y: (x, y)
monkeypatch.setattr(theme, "imgui", mock_imgui)
# Mock imgui
mock_style = MagicMock()
mock_imgui = MagicMock()
mock_imgui.get_style.return_value = mock_style
mock_imgui.ImVec2.side_effect = lambda x, y: (x, y)
monkeypatch.setattr(theme, "imgui", mock_imgui)
# Call apply with the default palette
theme.apply("ImGui Dark")
# Call apply with the default palette
theme.apply("ImGui Dark")
# Verify subtle rounding styles
assert mock_style.window_rounding == 6.0
assert mock_style.child_rounding == 4.0
assert mock_style.frame_rounding == 4.0
assert mock_style.popup_rounding == 4.0
assert mock_style.scrollbar_rounding == 12.0
assert mock_style.grab_rounding == 4.0
assert mock_style.tab_rounding == 4.0
# Verify subtle rounding styles
assert mock_style.window_rounding == 6.0
assert mock_style.child_rounding == 4.0
assert mock_style.frame_rounding == 4.0
assert mock_style.popup_rounding == 4.0
assert mock_style.scrollbar_rounding == 12.0
assert mock_style.grab_rounding == 4.0
assert mock_style.tab_rounding == 4.0
# Verify borders
assert mock_style.window_border_size == 1.0
assert mock_style.frame_border_size == 1.0
assert mock_style.popup_border_size == 1.0
# Verify borders
assert mock_style.window_border_size == 1.0
assert mock_style.frame_border_size == 1.0
assert mock_style.popup_border_size == 1.0
# Verify padding/spacing
assert mock_style.window_padding == (8.0, 8.0)
assert mock_style.frame_padding == (8.0, 4.0)
assert mock_style.item_spacing == (8.0, 4.0)
assert mock_style.item_inner_spacing == (4.0, 4.0)
assert mock_style.scrollbar_size == 14.0
# Verify padding/spacing
assert mock_style.window_padding == (8.0, 8.0)
assert mock_style.frame_padding == (8.0, 4.0)
assert mock_style.item_spacing == (8.0, 4.0)
assert mock_style.item_inner_spacing == (4.0, 4.0)
assert mock_style.scrollbar_size == 14.0
def test_frosted_glass_enabled_toggle():
theme.set_frosted_glass_enabled(True)
assert theme.get_frosted_glass_enabled() is True
theme.set_frosted_glass_enabled(False)
assert theme.get_frosted_glass_enabled() is False
def test_theme_config_frosted_glass():
config = {"theme": {"frosted_glass_enabled": True}}
theme.load_from_config(config)
assert theme.get_frosted_glass_enabled() is True
new_config = {}
theme.save_to_config(new_config)
assert new_config["theme"]["frosted_glass_enabled"] is True