Compare commits
44 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 |
@@ -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.
|
||||
|
||||
|
||||
+10
-1
@@ -35,6 +35,9 @@ This file tracks all major tracks for the project. Each track has its own detail
|
||||
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
|
||||
@@ -57,7 +60,7 @@ This file tracks all major tracks for the project. Each track has its own detail
|
||||
|
||||
5. [x] **Track: NERV UI Theme Integration** (Archived 2026-03-09)
|
||||
|
||||
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**
|
||||
@@ -76,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
|
||||
|
||||
@@ -13,23 +13,23 @@
|
||||
- [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 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,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)
|
||||
+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.
|
||||
|
||||
+271
-109
@@ -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,9 +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
|
||||
@@ -187,6 +194,7 @@ 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 = ""
|
||||
@@ -205,6 +213,17 @@ class App:
|
||||
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]:
|
||||
@@ -366,53 +490,122 @@ class App:
|
||||
self.ai_status = f"error: {e}"
|
||||
imgui.end_menu()
|
||||
|
||||
def _render_custom_title_bar(self) -> None:
|
||||
hwnd = imgui.get_main_viewport().platform_handle
|
||||
if not hwnd: return
|
||||
bar_height = 30
|
||||
imgui.set_next_window_pos((0, 0))
|
||||
imgui.set_next_window_size((imgui.get_io().display_size.x, bar_height))
|
||||
flags = (imgui.WindowFlags_.no_title_bar | imgui.WindowFlags_.no_resize |
|
||||
imgui.WindowFlags_.no_move | imgui.WindowFlags_.no_scrollbar |
|
||||
imgui.WindowFlags_.no_saved_settings | imgui.WindowFlags_.no_bring_to_front_on_focus)
|
||||
imgui.push_style_var(imgui.StyleVar_.window_rounding, 0)
|
||||
imgui.push_style_var(imgui.StyleVar_.window_border_size, 0)
|
||||
imgui.push_style_var(imgui.StyleVar_.window_padding, (10, 5))
|
||||
if imgui.begin("##custom_title_bar", None, flags):
|
||||
imgui.text("manual slop")
|
||||
if imgui.is_window_hovered() and imgui.is_mouse_dragging(0):
|
||||
# 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)
|
||||
btn_w = 40
|
||||
spacing = imgui.get_style().item_spacing.x
|
||||
right_x = imgui.get_window_width() - (btn_w * 3 + spacing * 2 + 10)
|
||||
imgui.same_line(right_x)
|
||||
if imgui.button("_", (btn_w, 20)):
|
||||
|
||||
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.same_line()
|
||||
if imgui.button("[]", (btn_w, 20)):
|
||||
win32gui.ShowWindow(hwnd, win32con.SW_MAXIMIZE)
|
||||
imgui.same_line()
|
||||
if imgui.button("X", (btn_w, 20)):
|
||||
|
||||
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()
|
||||
imgui.pop_style_var(3)
|
||||
|
||||
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))
|
||||
@@ -485,7 +678,7 @@ class App:
|
||||
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'):
|
||||
@@ -496,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()
|
||||
@@ -517,56 +710,56 @@ 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
|
||||
@@ -613,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")
|
||||
@@ -883,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)
|
||||
@@ -914,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)
|
||||
@@ -973,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")
|
||||
|
||||
@@ -1859,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})")
|
||||
@@ -2007,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...']
|
||||
@@ -3830,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):
|
||||
@@ -3903,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()
|
||||
@@ -3948,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."""
|
||||
@@ -3963,7 +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
|
||||
@@ -3974,8 +4135,9 @@ 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
|
||||
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\""
|
||||
|
||||
@@ -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,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
|
||||
@@ -9,21 +9,33 @@ def test_gui_window_controls_minimize_maximize_close():
|
||||
|
||||
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("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 = 12345
|
||||
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
|
||||
|
||||
# Call the method (to be implemented)
|
||||
app._render_custom_title_bar()
|
||||
# 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
|
||||
|
||||
@@ -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)
|
||||
@@ -33,3 +33,18 @@ def test_theme_apply_sets_rounding_and_padding(monkeypatch):
|
||||
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