Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 70bfcc61c2 | |||
| 976a3b63e0 | |||
| 0e0b185290 | |||
| 5b196dccf0 | |||
| 1771c32006 | |||
| 6a39f440d9 | |||
| cecbe2245a | |||
| ad9ffb68f5 | |||
| e9b7875de5 | |||
| 3d5c768598 | |||
| f297e7a3bd | |||
| 4a705a8060 | |||
| 55f3bd87e2 | |||
| e56d4af097 | |||
| 1328bc159b | |||
| 3a0d388502 | |||
| 879e0991c9 | |||
| d96adca67c | |||
| 4b0ebe44ff | |||
| 6b8151235f | |||
| 69107a75d3 | |||
| 89c9f62f0c | |||
| 87e6b5c665 | |||
| 9f8dd48a2e | |||
| 87bd2ae11c | |||
| a57a3c78d4 | |||
| ca01397885 | |||
| c76aba64e4 | |||
| 96de21b2b2 | |||
| 25d7d97455 | |||
| da478191e9 | |||
| 9b79044caa | |||
| 229fbe2b3f | |||
| d69434e85f | |||
| 830bd7b1fb | |||
| 50f98deb74 | |||
| 67ed51056e | |||
| 905ac00e3f | |||
| 836168a2a8 | |||
| 2dbd570d59 | |||
| 5ebce894bb | |||
| 6c4c567ed0 | |||
| 09383960be | |||
| ac4f63b76e | |||
| 356d5f3618 | |||
| b9ca69fbae | |||
| 3f4ae21708 | |||
| 59d7368bd7 | |||
| 02fca1f8ba | |||
| 841e54aa47 | |||
| 815ee55981 | |||
| 4e5ec31876 | |||
| 5f4da366f1 | |||
| 82722999a8 | |||
| ad93a294fb | |||
| b677228a96 | |||
| f2c5ae43d7 | |||
| cf5ee6c0f1 |
@@ -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}")
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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`.
|
||||
@@ -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":{}}
|
||||
@@ -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
@@ -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>>>;;;
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user