Compare commits
15 Commits
FKING-GLAS
...
BOTCHED-SH
| Author | SHA1 | Date | |
|---|---|---|---|
| 70bfcc61c2 | |||
| 976a3b63e0 | |||
| 0e0b185290 | |||
| 5b196dccf0 | |||
| 1771c32006 | |||
| 6a39f440d9 | |||
| cecbe2245a | |||
| ad9ffb68f5 | |||
| e9b7875de5 | |||
| 3d5c768598 | |||
| f297e7a3bd | |||
| 4a705a8060 | |||
| 55f3bd87e2 | |||
| e56d4af097 | |||
| 1328bc159b |
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: Tier 2 Tech Lead for architectural design and track execution with persistent memory
|
description: Tier 2 Tech Lead for architectural design and track execution with persistent memory
|
||||||
mode: primary
|
mode: primary
|
||||||
|
model: MiniMax-M2.5
|
||||||
temperature: 0.4
|
temperature: 0.4
|
||||||
permission:
|
permission:
|
||||||
edit: ask
|
edit: ask
|
||||||
@@ -13,9 +14,9 @@ ONLY output the requested text. No pleasantries.
|
|||||||
|
|
||||||
## Context Management
|
## Context Management
|
||||||
|
|
||||||
**MANUAL COMPACTION ONLY** <EFBFBD> Never rely on automatic context summarization.
|
**MANUAL COMPACTION ONLY** — Never rely on automatic context summarization.
|
||||||
Use `/compact` command explicitly when context needs reduction.
|
Use `/compact` command explicitly when context needs reduction.
|
||||||
You maintain PERSISTENT MEMORY throughout track execution <EFBFBD> do NOT apply Context Amnesia to your own session.
|
You maintain PERSISTENT MEMORY throughout track execution — do NOT apply Context Amnesia to your own session.
|
||||||
|
|
||||||
## CRITICAL: MCP Tools Only (Native Tools Banned)
|
## CRITICAL: MCP Tools Only (Native Tools Banned)
|
||||||
|
|
||||||
@@ -133,14 +134,14 @@ Before implementing:
|
|||||||
- Zero-assertion ban: Tests MUST have meaningful assertions
|
- Zero-assertion ban: Tests MUST have meaningful assertions
|
||||||
- Delegate test creation to Tier 3 Worker via Task tool
|
- Delegate test creation to Tier 3 Worker via Task tool
|
||||||
- Run tests and confirm they FAIL as expected
|
- Run tests and confirm they FAIL as expected
|
||||||
- **CONFIRM FAILURE** <EFBFBD> this is the Red phase
|
- **CONFIRM FAILURE** — this is the Red phase
|
||||||
|
|
||||||
### 3. Green Phase: Implement to Pass
|
### 3. Green Phase: Implement to Pass
|
||||||
|
|
||||||
- **Pre-delegation checkpoint**: Stage current progress (`git add .`)
|
- **Pre-delegation checkpoint**: Stage current progress (`git add .`)
|
||||||
- Delegate implementation to Tier 3 Worker via Task tool
|
- Delegate implementation to Tier 3 Worker via Task tool
|
||||||
- Run tests and confirm they PASS
|
- Run tests and confirm they PASS
|
||||||
- **CONFIRM PASS** <EFBFBD> this is the Green phase
|
- **CONFIRM PASS** — this is the Green phase
|
||||||
|
|
||||||
### 4. Refactor Phase (Optional)
|
### 4. Refactor Phase (Optional)
|
||||||
|
|
||||||
|
|||||||
@@ -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/`.
|
- **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.
|
- **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.
|
- **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.
|
- **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.
|
- **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.
|
- **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.
|
||||||
|
|||||||
@@ -70,6 +70,6 @@
|
|||||||
- **Synchronous IPC Approval Flow:** A specialized bridge mechanism that allows headless AI providers (like Gemini CLI) to synchronously request and receive human approval for tool calls via the GUI's REST API hooks.
|
- **Synchronous IPC Approval Flow:** A specialized bridge mechanism that allows headless AI providers (like Gemini CLI) to synchronously request and receive human approval for tool calls via the GUI's REST API hooks.
|
||||||
- **High-Fidelity Selectable Labels:** Implements a pattern for making read-only UI text selectable by wrapping `imgui.input_text` with `imgui.InputTextFlags_.read_only`. Includes a specialized `_render_selectable_label` helper that resets frame backgrounds, borders, and padding to mimic standard labels while enabling OS-level clipboard support (Ctrl+C).
|
- **High-Fidelity Selectable Labels:** Implements a pattern for making read-only UI text selectable by wrapping `imgui.input_text` with `imgui.InputTextFlags_.read_only`. Includes a specialized `_render_selectable_label` helper that resets frame backgrounds, borders, and padding to mimic standard labels while enabling OS-level clipboard support (Ctrl+C).
|
||||||
- **Hybrid Markdown Rendering:** Employs a custom `MarkdownRenderer` that orchestrates `imgui_markdown` for standard text and headers while intercepting code blocks to render them via cached `ImGuiColorTextEdit` instances. This ensures high-performance rich text rendering with robust syntax highlighting and stateful text selection.
|
- **Hybrid Markdown Rendering:** Employs a custom `MarkdownRenderer` that orchestrates `imgui_markdown` for standard text and headers while intercepting code blocks to render them via cached `ImGuiColorTextEdit` instances. This ensures high-performance rich text rendering with robust syntax highlighting and stateful text selection.
|
||||||
- **Hybrid Shader Pipeline:** Utilizes an optimized `ImDrawList`-based batching technique to simulate UI effects such as soft shadows and acrylic glass overlays without the overhead of heavy GPU-resident shaders. Supplemented by a true GPU shader pipeline using `PyOpenGL` and Framebuffer Objects (FBOs) for complex post-processing (CRT scanlines, bloom) and dynamic backgrounds.
|
- **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.
|
- **Interface-Driven Development (IDD):** Enforces a "Stub-and-Resolve" pattern where cross-module dependencies are resolved by generating signatures/contracts before implementation.
|
||||||
|
|
||||||
|
|||||||
@@ -82,9 +82,8 @@ This file tracks all major tracks for the project. Each track has its own detail
|
|||||||
11. [ ] **Track: Advanced Text Viewer with Syntax Highlighting**
|
11. [ ] **Track: Advanced Text Viewer with Syntax Highlighting**
|
||||||
*Link: [./tracks/text_viewer_rich_rendering_20260313/](./tracks/text_viewer_rich_rendering_20260313/)*
|
*Link: [./tracks/text_viewer_rich_rendering_20260313/](./tracks/text_viewer_rich_rendering_20260313/)*
|
||||||
|
|
||||||
12. [ ] ~~**Track: Frosted Glass Background Effect**~~ THIS IS A LOST CAUSE DON'T BOTHER.
|
12. [x] **Track: Frosted Glass Background Effect**
|
||||||
*Link: [./tracks/frosted_glass_20260313/](./tracks/frosted_glass_20260313/)*
|
*Link: [./tracks/frosted_glass_20260313/](./tracks/frosted_glass_20260313/)*
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
# Debrief: Failed Frosted Glass Implementation (Attempt 1)
|
|
||||||
|
|
||||||
## 1. Post-Mortem Summary
|
|
||||||
The initial implementation of the Frosted Glass effect was a catastrophic failure resulting in application crashes (`RecursionError`, `AttributeError`, `RuntimeError`) and visual non-functionality (black backgrounds or invisible blurs).
|
|
||||||
|
|
||||||
## 2. Root Causes
|
|
||||||
|
|
||||||
### A. Architectural Blindness (ImGui Timing)
|
|
||||||
I attempted to use `glCopyTexImage2D` to capture the "backbuffer" during the `_gui_func` execution. In an immediate-mode GUI (ImGui), the backbuffer is cleared at the start of the frame and draw commands are only recorded during `_gui_func`. The actual GPU rendering happens **after** `_gui_func` finishes. Consequently, I was capturing and blurring an empty black screen every frame.
|
|
||||||
|
|
||||||
### B. Sub-Agent Fragmentation (Class Scope Breaks)
|
|
||||||
By delegating massive file refactors to the `generalist` sub-agent, I lost control over the strict 1-space indentation required by this project. The sub-agent introduced unindented blocks that silently closed the `App` class scope, causing all subsequent methods to become global functions. This lead to the avalanche of `AttributeError: 'App' object has no attribute '_render_operations_hub_contents'` and similar errors.
|
|
||||||
|
|
||||||
### C. Style Stack Imbalance
|
|
||||||
The implementation of `_begin_window` and `_end_window` wrappers failed to account for mid-render state changes. Toggling the "Frosted Glass" checkbox mid-frame resulted in mismatched `PushStyleColor` and `PopStyleColor` calls, triggering internal ImGui assertions and hard crashes.
|
|
||||||
|
|
||||||
### D. High-DPI Math Errors
|
|
||||||
The UV coordinate math failed to correctly account for `display_framebuffer_scale`. On high-resolution screens, the blur sampling was offset by thousands of pixels, rendering the effect physically invisible or distorted.
|
|
||||||
|
|
||||||
TODO:
|
|
||||||
|
|
||||||
LOOK AT THIS SHIT:
|
|
||||||
https://www.unknowncheats.me/forum/general-programming-and-reversing/617284-blurring-imgui-basically-window-using-acrylic-blur.html
|
|
||||||
https://github.com/Speykious/opengl-playground/blob/main/src/scenes/blurring.rs
|
|
||||||
https://www.intel.com/content/www/us/en/developer/articles/technical/an-investigation-of-fast-real-time-gpu-based-image-blur-algorithms.html
|
|
||||||
https://github.com/cofenberg/unrimp/blob/45aa431286ce597c018675c1a9730d98e6ccfc64/Renderer/RendererRuntime/src/DebugGui/DebugGuiManager.cpp
|
|
||||||
https://github.com/cofenberg/unrimp/blob/45aa431286ce597c018675c1a9730d98e6ccfc64/Renderer/RendererRuntime/src/DebugGui/Detail/Shader/DebugGui_GLSL_410.h
|
|
||||||
https://github.com/itsRythem/ImGui-Blur
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
# Track frosted_glass_20260313 Context (REPAIR)
|
# Track frosted_glass_20260313 Context
|
||||||
|
|
||||||
- [Debrief](./debrief.md)
|
|
||||||
- [Specification](./spec.md)
|
- [Specification](./spec.md)
|
||||||
- [Implementation Plan](./plan.md)
|
- [Implementation Plan](./plan.md)
|
||||||
- [Metadata](./metadata.json)
|
- [Metadata](./metadata.json)
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
"type": "feature",
|
"type": "feature",
|
||||||
"status": "new",
|
"status": "new",
|
||||||
"created_at": "2026-03-13T14:39:00Z",
|
"created_at": "2026-03-13T14:39:00Z",
|
||||||
"updated_at": "2026-03-13T18:55:00Z",
|
"updated_at": "2026-03-13T14:39:00Z",
|
||||||
"description": "REPAIR: Implement stable frosted glass using native Windows DWM APIs."
|
"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."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,26 @@
|
|||||||
# Implementation Plan: Frosted Glass Background Effect (REPAIR - TRUE GPU)
|
# Implementation Plan: Frosted Glass Background Effect
|
||||||
|
|
||||||
## Phase 1: Robust Shader & FBO Foundation
|
## Phase 1: Shader Development & Integration [checkpoint: 55f3bd8]
|
||||||
- [x] Task: Implement: Create `ShaderManager` methods for downsampled FBO setup (scene, temp, blur). [d9148ac]
|
- [x] Task: Audit `src/shader_manager.py` to identify existing background/post-process integration points. [1328bc1]
|
||||||
- [x] Task: Implement: Develop the "Deep Sea" background shader and integrate it as the FBO source. [d85dc3a]
|
- [x] Task: Write Tests: Verify `ShaderManager` can compile and bind a multi-pass blur shader. [1328bc1]
|
||||||
- [x] Task: Implement: Develop the 2-pass Gaussian blur shaders with a wide tap distribution. [c8b7fca]
|
- [x] Task: Implement: Add `FrostedGlassShader` (GLSL) to `src/shader_manager.py`. [1328bc1]
|
||||||
- [x] Task: Conductor - User Manual Verification 'Phase 1: Robust Foundation' (Protocol in workflow.md)
|
- [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: High-Performance Blur Pipeline
|
## Phase 2: Framebuffer Capture Pipeline [checkpoint: e9b7875]
|
||||||
- [x] Task: Implement: Create the `prepare_global_blur` method that renders the background and blurs it at 1/4 resolution. [9c2078a]
|
- [x] Task: Write Tests: Verify the FBO capture mechanism correctly samples the back buffer and stores it in a texture. [f297e7a]
|
||||||
- [x] Task: Implement: Ensure the pipeline correctly handles high-DPI scaling (`fb_scale`) for internal FBO dimensions. [9c2078a]
|
- [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: Conductor - User Manual Verification 'Phase 2: High-Performance Pipeline' (Protocol in workflow.md)
|
- [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 & Screen-Space Sampling
|
## Phase 3: GUI Integration & Rendering [checkpoint: cecbe22]
|
||||||
- [x] Task: Implement: Update `_render_frosted_background` to perform normalized screen-space UV sampling. [926318f]
|
- [x] Task: Write Tests: Verify that a mocked ImGui window successfully calls the frosted glass rendering logic. [cecbe22]
|
||||||
- [x] Task: Fix crash when display_size is invalid at startup. [db00fba]
|
- [x] Task: Implement: Create a `_render_frosted_background(self, pos, size)` helper in `src/gui_2.py`. [cecbe22]
|
||||||
## Phase 3: GUI Integration & Screen-Space Sampling
|
- [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: Implement: Update `_render_frosted_background` to perform normalized screen-space UV sampling. [a862119]
|
- [x] Task: Conductor - User Manual Verification 'Phase 3: GUI Integration & Rendering' (Protocol in workflow.md) [cecbe22]
|
||||||
- [~] Task: Implement: Update `_begin_window` and `_end_window` to manage global transparency and call the blur renderer.
|
|
||||||
|
## 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]
|
||||||
|
|||||||
@@ -1,30 +1,34 @@
|
|||||||
# Specification: Frosted Glass Background Effect (REPAIR - TRUE GPU)
|
# Specification: Frosted Glass Background Effect
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
Implement a high-fidelity "frosted glass" (acrylic) background effect using a dedicated OpenGL pipeline. This implementation follows professional rendering patterns (downsampling, multi-pass blurring, and screen-space sampling) to ensure a smooth, milky look that remains performant on high-DPI displays.
|
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
|
## Functional Requirements
|
||||||
- **Dedicated Background Pipeline:**
|
- **GPU-Accelerated Blur:**
|
||||||
- Render the animated "Deep Sea" background shader to an off-screen `SceneFBO` once per frame.
|
- Implement a GLSL fragment shader (e.g., Gaussian or Kawase blur) within the existing `ShaderManager` pipeline.
|
||||||
- **Multi-Scale Downsampled Blur:**
|
- The shader must sample the current frame buffer background and render a blurred version behind the active window's background.
|
||||||
- Downsample the `SceneFBO` texture to 1/4 or 1/8 resolution.
|
- **Global Integration:**
|
||||||
- Perform 2-pass Gaussian blurring on the downsampled texture to achieve a creamy "milky" aesthetic.
|
- The effect must automatically apply to all standard ImGui panels and popups.
|
||||||
- **ImGui Panel Integration:**
|
- Integrate with `imgui.begin()` and `imgui.begin_popup()` (or via a reusable wrapper helper).
|
||||||
- Each ImGui panel must sample its background from the blurred texture using screen-space UV coordinates.
|
- **Real-Time Tuning:**
|
||||||
- Automatically force window transparency (`alpha 0.0`) when the effect is active.
|
- Add controls to the **Live Shader Editor** to adjust the following parameters:
|
||||||
- **Real-Time Shader Tuning:**
|
- **Blur Radius:** Control the intensity of the Gaussian blur.
|
||||||
- Control blur radius, tint intensity, and opacity via the Live Shader Editor.
|
- **Tint Intensity:** Control the strength of the "frost" overlay color.
|
||||||
- **Stability:**
|
- **Base Opacity:** Control the overall transparency of the frosted layer.
|
||||||
- Balanced style-stack management to prevent ImGui assertion crashes.
|
- **Persistence:**
|
||||||
- Strict 1-space indentation and class scope protection.
|
- Save frosted glass parameters to `config.toml` under the `theme` or `shader` section.
|
||||||
|
|
||||||
## Technical Implementation
|
## Technical Implementation
|
||||||
- **FBO Management:** Persistent FBOs for scene, temp, and blur textures.
|
- **Shader Pipeline:** Use `PyOpenGL` to manage a dedicated background texture/FBO for sampling.
|
||||||
- **UV Math:** `(window_pos / screen_res)` mapping to handle high-DPI scaling and vertical flipping.
|
- **Coordinate Mapping:** Ensure the blur shader correctly maps screen coordinates to the region behind the current ImGui window.
|
||||||
- **DrawList Callbacks:** (If necessary) use callbacks to ensure the background is ready before panels draw.
|
- **State Integration:** Store tuning parameters in `App.shader_uniforms` and ensure they are updated every frame.
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
- [ ] Toggling the effect does not crash the app.
|
- [ ] Panels and popups have a distinct, blurred background that clearly separates them from the content behind them.
|
||||||
- [ ] Windows show a deep, high-quality blur of the background shader.
|
- [ ] Changing the "Blur Radius" slider in the Shader Editor immediately updates the visual frostiness.
|
||||||
- [ ] Blur follows windows perfectly during drag/resize.
|
- [ ] The effect remains stable during window dragging and resizing.
|
||||||
- [ ] The "Milky" look is highly visible even at low radii.
|
- [ ] 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).
|
||||||
|
|||||||
12
config.toml
12
config.toml
@@ -52,7 +52,7 @@ separate_external_tools = true
|
|||||||
"Operations Hub" = true
|
"Operations Hub" = true
|
||||||
Message = false
|
Message = false
|
||||||
Response = true
|
Response = true
|
||||||
"Tool Calls" = false
|
"Tool Calls" = true
|
||||||
Theme = true
|
Theme = true
|
||||||
"Log Management" = true
|
"Log Management" = true
|
||||||
Diagnostics = false
|
Diagnostics = false
|
||||||
@@ -62,10 +62,14 @@ Diagnostics = false
|
|||||||
[theme]
|
[theme]
|
||||||
palette = "Nord Dark"
|
palette = "Nord Dark"
|
||||||
font_path = "C:/projects/manual_slop/assets/fonts/MapleMono-Regular.ttf"
|
font_path = "C:/projects/manual_slop/assets/fonts/MapleMono-Regular.ttf"
|
||||||
font_size = 18.0
|
font_size = 16.0
|
||||||
scale = 1.0
|
scale = 1.0
|
||||||
transparency = 0.4399999976158142
|
transparency = 0.699999988079071
|
||||||
child_transparency = 0.5099999904632568
|
child_transparency = 0.6899999976158142
|
||||||
|
frosted_blur_radius = 29.68400001525879
|
||||||
|
frosted_tint_intensity = 0.5659999847412109
|
||||||
|
frosted_opacity = 0.5389999747276306
|
||||||
|
frosted_glass_enabled = true
|
||||||
|
|
||||||
[mma]
|
[mma]
|
||||||
max_workers = 4
|
max_workers = 4
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ ViewportPos=43,95
|
|||||||
ViewportId=0x78C57832
|
ViewportId=0x78C57832
|
||||||
Size=897,649
|
Size=897,649
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000001,0
|
DockId=0x00000005,0
|
||||||
|
|
||||||
[Window][Files]
|
[Window][Files]
|
||||||
ViewportPos=3125,170
|
ViewportPos=3125,170
|
||||||
@@ -33,7 +33,7 @@ DockId=0x0000000A,0
|
|||||||
Pos=0,17
|
Pos=0,17
|
||||||
Size=1680,730
|
Size=1680,730
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000001,0
|
DockId=0x00000005,0
|
||||||
|
|
||||||
[Window][Provider]
|
[Window][Provider]
|
||||||
ViewportPos=43,95
|
ViewportPos=43,95
|
||||||
@@ -41,10 +41,10 @@ ViewportId=0x78C57832
|
|||||||
Pos=0,651
|
Pos=0,651
|
||||||
Size=897,468
|
Size=897,468
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000001,0
|
DockId=0x00000005,0
|
||||||
|
|
||||||
[Window][Message]
|
[Window][Message]
|
||||||
Pos=661,1426
|
Pos=661,1321
|
||||||
Size=716,455
|
Size=716,455
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
@@ -54,10 +54,9 @@ Size=1111,773
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Tool Calls]
|
[Window][Tool Calls]
|
||||||
Pos=520,1144
|
Pos=1039,464
|
||||||
Size=663,232
|
Size=587,510
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000006,0
|
|
||||||
|
|
||||||
[Window][Comms History]
|
[Window][Comms History]
|
||||||
ViewportPos=43,95
|
ViewportPos=43,95
|
||||||
@@ -74,10 +73,10 @@ Collapsed=0
|
|||||||
DockId=0xAFC85805,2
|
DockId=0xAFC85805,2
|
||||||
|
|
||||||
[Window][Theme]
|
[Window][Theme]
|
||||||
Pos=0,543
|
Pos=2671,24
|
||||||
Size=387,737
|
Size=1169,2136
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,2
|
DockId=0x00000002,1
|
||||||
|
|
||||||
[Window][Text Viewer - Entry #7]
|
[Window][Text Viewer - Entry #7]
|
||||||
Pos=379,324
|
Pos=379,324
|
||||||
@@ -88,13 +87,13 @@ Collapsed=0
|
|||||||
Pos=1649,24
|
Pos=1649,24
|
||||||
Size=580,1284
|
Size=580,1284
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000010,2
|
DockId=0x00000004,2
|
||||||
|
|
||||||
[Window][Context Hub]
|
[Window][Context Hub]
|
||||||
Pos=0,543
|
Pos=0,1719
|
||||||
Size=387,737
|
Size=999,441
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,1
|
DockId=0x00000006,0
|
||||||
|
|
||||||
[Window][AI Settings Hub]
|
[Window][AI Settings Hub]
|
||||||
Pos=406,17
|
Pos=406,17
|
||||||
@@ -103,28 +102,28 @@ Collapsed=0
|
|||||||
DockId=0x0000000D,0
|
DockId=0x0000000D,0
|
||||||
|
|
||||||
[Window][Discussion Hub]
|
[Window][Discussion Hub]
|
||||||
Pos=1169,26
|
Pos=1762,24
|
||||||
Size=950,1254
|
Size=907,2136
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000013,0
|
DockId=0x00000011,0
|
||||||
|
|
||||||
[Window][Operations Hub]
|
[Window][Operations Hub]
|
||||||
Pos=389,26
|
Pos=1001,24
|
||||||
Size=778,1254
|
Size=759,2136
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000005,0
|
DockId=0x00000010,0
|
||||||
|
|
||||||
[Window][Files & Media]
|
[Window][Files & Media]
|
||||||
Pos=0,543
|
Pos=0,1719
|
||||||
Size=387,737
|
Size=999,441
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,0
|
DockId=0x00000006,1
|
||||||
|
|
||||||
[Window][AI Settings]
|
[Window][AI Settings]
|
||||||
Pos=0,26
|
Pos=0,24
|
||||||
Size=387,515
|
Size=999,1693
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000001,0
|
DockId=0x00000005,0
|
||||||
|
|
||||||
[Window][Approve Tool Execution]
|
[Window][Approve Tool Execution]
|
||||||
Pos=3,524
|
Pos=3,524
|
||||||
@@ -132,16 +131,16 @@ Size=416,325
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][MMA Dashboard]
|
[Window][MMA Dashboard]
|
||||||
Pos=2121,26
|
Pos=2671,24
|
||||||
Size=653,1254
|
Size=1169,2136
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000010,0
|
DockId=0x00000002,0
|
||||||
|
|
||||||
[Window][Log Management]
|
[Window][Log Management]
|
||||||
Pos=2121,26
|
Pos=1931,24
|
||||||
Size=653,1254
|
Size=629,1416
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000010,1
|
DockId=0x00000002,1
|
||||||
|
|
||||||
[Window][Track Proposal]
|
[Window][Track Proposal]
|
||||||
Pos=709,326
|
Pos=709,326
|
||||||
@@ -167,7 +166,7 @@ Collapsed=0
|
|||||||
Pos=2822,1717
|
Pos=2822,1717
|
||||||
Size=1018,420
|
Size=1018,420
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000011,0
|
DockId=0x00000004,0
|
||||||
|
|
||||||
[Window][Approve PowerShell Command]
|
[Window][Approve PowerShell Command]
|
||||||
Pos=649,435
|
Pos=649,435
|
||||||
@@ -210,7 +209,7 @@ Size=3840,32
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Text Viewer - message]
|
[Window][Text Viewer - message]
|
||||||
Pos=568,1226
|
Pos=562,588
|
||||||
Size=900,700
|
Size=900,700
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
@@ -220,7 +219,7 @@ Size=900,700
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Text Viewer - text]
|
[Window][Text Viewer - text]
|
||||||
Pos=60,60
|
Pos=555,644
|
||||||
Size=900,700
|
Size=900,700
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
@@ -240,7 +239,7 @@ Size=900,700
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Text Viewer - tool_calls]
|
[Window][Text Viewer - tool_calls]
|
||||||
Pos=60,60
|
Pos=589,490
|
||||||
Size=900,700
|
Size=900,700
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
@@ -330,8 +329,8 @@ Size=967,499
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Usage Analytics]
|
[Window][Usage Analytics]
|
||||||
Pos=1627,680
|
Pos=1702,689
|
||||||
Size=480,343
|
Size=566,438
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Tool Preset Manager]
|
[Window][Tool Preset Manager]
|
||||||
@@ -350,7 +349,7 @@ Size=1000,800
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][External Tools]
|
[Window][External Tools]
|
||||||
Pos=1968,516
|
Pos=531,376
|
||||||
Size=616,409
|
Size=616,409
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
@@ -365,7 +364,7 @@ Size=900,700
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Text Viewer - Entry #4]
|
[Window][Text Viewer - Entry #4]
|
||||||
Pos=1127,922
|
Pos=828,397
|
||||||
Size=900,700
|
Size=900,700
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
@@ -380,8 +379,13 @@ Size=900,700
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Shader Editor]
|
[Window][Shader Editor]
|
||||||
Pos=998,497
|
Pos=753,637
|
||||||
Size=493,369
|
Size=493,487
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
|
[Window][Text Viewer - list_directory]
|
||||||
|
Pos=60,60
|
||||||
|
Size=900,700
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Table][0xFB6E3870,4]
|
[Table][0xFB6E3870,4]
|
||||||
@@ -441,7 +445,7 @@ Column 3 Width=120
|
|||||||
RefScale=16
|
RefScale=16
|
||||||
Column 0 Width=48
|
Column 0 Width=48
|
||||||
Column 1 Weight=1.0000
|
Column 1 Weight=1.0000
|
||||||
Column 2 Width=118
|
Column 2 Width=117
|
||||||
Column 3 Width=48
|
Column 3 Width=48
|
||||||
|
|
||||||
[Table][0xD99F45C5,4]
|
[Table][0xD99F45C5,4]
|
||||||
@@ -497,21 +501,19 @@ Column 1 Weight=1.0000
|
|||||||
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
|
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
|
||||||
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
|
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
|
||||||
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
|
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
|
||||||
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,26 Size=2774,1254 Split=X
|
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,24 Size=3840,2136 Split=X
|
||||||
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=1980,1183 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=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2
|
||||||
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=680,858 Split=Y Selected=0x8CA2375C
|
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=999,858 Split=Y Selected=0x7BD57D6A
|
||||||
DockNode ID=0x00000001 Parent=0x00000007 SizeRef=824,525 CentralNode=1 Selected=0x7BD57D6A
|
DockNode ID=0x00000005 Parent=0x00000007 SizeRef=639,904 CentralNode=1 Selected=0x7BD57D6A
|
||||||
DockNode ID=0x00000002 Parent=0x00000007 SizeRef=824,737 Selected=0x8CA2375C
|
DockNode ID=0x00000006 Parent=0x00000007 SizeRef=639,441 Selected=0x1DCB2623
|
||||||
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=1730,858 Split=X Selected=0x418C7449
|
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=2839,858 Split=X Selected=0x418C7449
|
||||||
DockNode ID=0x00000012 Parent=0x0000000E SizeRef=778,402 Split=Y Selected=0x418C7449
|
DockNode ID=0x00000001 Parent=0x0000000E SizeRef=1668,1288 Split=X Selected=0x6F2B5B04
|
||||||
DockNode ID=0x00000005 Parent=0x00000012 SizeRef=876,1749 Selected=0x418C7449
|
DockNode ID=0x00000010 Parent=0x00000001 SizeRef=759,1416 Selected=0x418C7449
|
||||||
DockNode ID=0x00000006 Parent=0x00000012 SizeRef=876,362 Selected=0x1D56B311
|
DockNode ID=0x00000011 Parent=0x00000001 SizeRef=907,1416 Selected=0x6F2B5B04
|
||||||
DockNode ID=0x00000013 Parent=0x0000000E SizeRef=950,402 Selected=0x6F2B5B04
|
DockNode ID=0x00000002 Parent=0x0000000E SizeRef=1169,1288 Selected=0x8CA2375C
|
||||||
DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6
|
DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6
|
||||||
DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=653,1183 Split=Y Selected=0x3AEC3498
|
DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=511,1183 Selected=0x3AEC3498
|
||||||
DockNode ID=0x00000010 Parent=0x00000004 SizeRef=1199,1689 Selected=0x2C0206CE
|
|
||||||
DockNode ID=0x00000011 Parent=0x00000004 SizeRef=1199,420 Selected=0xDEB547B6
|
|
||||||
|
|
||||||
;;;<<<Layout_655921752_Default>>>;;;
|
;;;<<<Layout_655921752_Default>>>;;;
|
||||||
;;;<<<HelloImGui_Misc>>>;;;
|
;;;<<<HelloImGui_Misc>>>;;;
|
||||||
|
|||||||
336
src/gui_2.py
336
src/gui_2.py
@@ -21,6 +21,7 @@ from src import theme_2 as theme
|
|||||||
from src import theme_nerv_fx as theme_fx
|
from src import theme_nerv_fx as theme_fx
|
||||||
from src import api_hooks
|
from src import api_hooks
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import OpenGL.GL as gl
|
||||||
from src import log_registry
|
from src import log_registry
|
||||||
from src import log_pruner
|
from src import log_pruner
|
||||||
from src import models
|
from src import models
|
||||||
@@ -28,6 +29,7 @@ from src import app_controller
|
|||||||
from src import mcp_client
|
from src import mcp_client
|
||||||
from src import markdown_helper
|
from src import markdown_helper
|
||||||
from src import bg_shader
|
from src import bg_shader
|
||||||
|
from src.shader_manager import ShaderManager
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
@@ -39,7 +41,6 @@ else:
|
|||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed
|
from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed
|
||||||
from src.shader_manager import BlurPipeline
|
|
||||||
|
|
||||||
PROVIDERS: list[str] = ["gemini", "anthropic", "gemini_cli", "deepseek", "minimax"]
|
PROVIDERS: list[str] = ["gemini", "anthropic", "gemini_cli", "deepseek", "minimax"]
|
||||||
COMMS_CLAMP_CHARS: int = 300
|
COMMS_CLAMP_CHARS: int = 300
|
||||||
@@ -212,34 +213,17 @@ class App:
|
|||||||
self._nerv_flicker = theme_fx.StatusFlicker()
|
self._nerv_flicker = theme_fx.StatusFlicker()
|
||||||
self.ui_tool_filter_category = "All"
|
self.ui_tool_filter_category = "All"
|
||||||
self.ui_discussion_split_h = 300.0
|
self.ui_discussion_split_h = 300.0
|
||||||
self.shader_uniforms = {'crt': 1.0, 'scanline': 0.5, 'bloom': 0.8}
|
self.shader_uniforms = {
|
||||||
self.ui_frosted_glass_enabled = False
|
'crt': 1.0,
|
||||||
self._blur_pipeline: BlurPipeline | None = None
|
'scanline': 0.5,
|
||||||
self.ui_frosted_glass_enabled = False
|
'bloom': 0.8,
|
||||||
self._blur_pipeline = None
|
'frosted_blur_radius': theme.get_frosted_blur_radius(),
|
||||||
|
'frosted_tint_intensity': theme.get_frosted_tint_intensity(),
|
||||||
|
'frosted_opacity': theme.get_frosted_opacity()
|
||||||
|
}
|
||||||
|
|
||||||
def _pre_render_blur(self):
|
self.shader_manager = ShaderManager()
|
||||||
if not self.ui_frosted_glass_enabled:
|
self.start_time = time.time()
|
||||||
return
|
|
||||||
if not self._blur_pipeline:
|
|
||||||
return
|
|
||||||
ws = imgui.get_io().display_size
|
|
||||||
fb_scale = imgui.get_io().display_framebuffer_scale.x
|
|
||||||
import time
|
|
||||||
t = time.time()
|
|
||||||
self._blur_pipeline.prepare_global_blur(int(ws.x), int(ws.y), t, fb_scale)
|
|
||||||
|
|
||||||
def _render_custom_background(self):
|
|
||||||
return # DISABLED - imgui-bundle can't sample OpenGL textures
|
|
||||||
|
|
||||||
def _draw_blurred_rect(self, dl, p_min, p_max, tex_id, uv_min, uv_max):
|
|
||||||
import OpenGL.GL as gl
|
|
||||||
gl.glEnable(gl.GL_BLEND)
|
|
||||||
gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
|
|
||||||
imgui.push_texture_id(tex_id)
|
|
||||||
dl.add_image_quad(p_min, p_max, uv_min, uv_max, imgui.get_color_u32((1, 1, 1, 1)))
|
|
||||||
imgui.pop_texture_id()
|
|
||||||
gl.glDisable(gl.GL_BLEND)
|
|
||||||
|
|
||||||
def _handle_approve_tool(self, user_data=None) -> None:
|
def _handle_approve_tool(self, user_data=None) -> None:
|
||||||
"""UI-level wrapper for approving a pending tool execution ask."""
|
"""UI-level wrapper for approving a pending tool execution ask."""
|
||||||
@@ -369,6 +353,111 @@ class App:
|
|||||||
imgui.pop_style_var(2)
|
imgui.pop_style_var(2)
|
||||||
imgui.pop_id()
|
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:
|
def _show_menus(self) -> None:
|
||||||
if imgui.begin_menu("manual slop"):
|
if imgui.begin_menu("manual slop"):
|
||||||
if imgui.menu_item("Quit", "Ctrl+Q", False)[0]:
|
if imgui.menu_item("Quit", "Ctrl+Q", False)[0]:
|
||||||
@@ -465,29 +554,58 @@ class App:
|
|||||||
exp, opened = imgui.begin('Shader Editor', self.show_windows['Shader Editor'])
|
exp, opened = imgui.begin('Shader Editor', self.show_windows['Shader Editor'])
|
||||||
self.show_windows['Shader Editor'] = bool(opened)
|
self.show_windows['Shader Editor'] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
_, self.ui_frosted_glass_enabled = imgui.checkbox('Frosted Glass', self.ui_frosted_glass_enabled)
|
|
||||||
imgui.separator()
|
|
||||||
changed_crt, self.shader_uniforms['crt'] = imgui.slider_float('CRT Curvature', self.shader_uniforms['crt'], 0.0, 2.0)
|
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_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)
|
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.end()
|
||||||
|
|
||||||
def _gui_func(self) -> None:
|
def _gui_func(self) -> None:
|
||||||
self._render_custom_title_bar()
|
self._render_custom_title_bar()
|
||||||
self._render_shader_live_editor()
|
self._render_shader_live_editor()
|
||||||
pushed_prior_tint = False
|
pushed_prior_tint = False
|
||||||
|
pushed_frosted_style = False
|
||||||
|
|
||||||
# Render background shader
|
# Render background shader
|
||||||
bg = bg_shader.get_bg()
|
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
|
ws = imgui.get_io().display_size
|
||||||
bg.render(ws.x, ws.y)
|
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():
|
if theme.is_nerv_active():
|
||||||
ws = imgui.get_io().display_size
|
ws = imgui.get_io().display_size
|
||||||
self._nerv_alert.update(self.ai_status)
|
self._nerv_alert.update(self.ai_status)
|
||||||
self._nerv_alert.render(ws.x, ws.y)
|
self._nerv_alert.render(ws.x, ws.y)
|
||||||
self._nerv_crt.enabled = self.ui_crt_filter
|
self._nerv_crt.enabled = self.ui_crt_filter
|
||||||
self._nerv_crt.render(ws.x, ws.y)
|
self._nerv_crt.render(ws.x, ws.y)
|
||||||
|
|
||||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_gui_func")
|
if self.perf_profiling_enabled: self.perf_monitor.start_component("_gui_func")
|
||||||
if self.is_viewing_prior_session:
|
if self.is_viewing_prior_session:
|
||||||
imgui.push_style_color(imgui.Col_.window_bg, vec4(50, 40, 20))
|
imgui.push_style_color(imgui.Col_.window_bg, vec4(50, 40, 20))
|
||||||
@@ -560,7 +678,7 @@ class App:
|
|||||||
self._tool_log_dirty = False
|
self._tool_log_dirty = False
|
||||||
|
|
||||||
if self.show_windows.get("Context Hub", 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)
|
self.show_windows["Context Hub"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
if imgui.begin_tab_bar('context_hub_tabs'):
|
if imgui.begin_tab_bar('context_hub_tabs'):
|
||||||
@@ -571,18 +689,18 @@ class App:
|
|||||||
self._render_paths_panel()
|
self._render_paths_panel()
|
||||||
imgui.end_tab_item()
|
imgui.end_tab_item()
|
||||||
imgui.end_tab_bar()
|
imgui.end_tab_bar()
|
||||||
imgui.end()
|
self._end_window()
|
||||||
if self.show_windows.get("Files & Media", False):
|
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)
|
self.show_windows["Files & Media"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
if imgui.collapsing_header("Files"):
|
if imgui.collapsing_header("Files"):
|
||||||
self._render_files_panel()
|
self._render_files_panel()
|
||||||
if imgui.collapsing_header("Screenshots"):
|
if imgui.collapsing_header("Screenshots"):
|
||||||
self._render_screenshots_panel()
|
self._render_screenshots_panel()
|
||||||
imgui.end()
|
self._end_window()
|
||||||
if self.show_windows.get("AI Settings", False):
|
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)
|
self.show_windows["AI Settings"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_persona_selector_panel()
|
self._render_persona_selector_panel()
|
||||||
@@ -592,56 +710,56 @@ class App:
|
|||||||
self._render_system_prompts_panel()
|
self._render_system_prompts_panel()
|
||||||
self._render_agent_tools_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):
|
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)
|
self.show_windows["Usage Analytics"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_usage_analytics_panel()
|
self._render_usage_analytics_panel()
|
||||||
imgui.end()
|
self._end_window()
|
||||||
if self.show_windows.get("MMA Dashboard", False):
|
if self.show_windows.get("MMA Dashboard", False):
|
||||||
exp, opened = imgui.begin("MMA Dashboard", self.show_windows["MMA Dashboard"])
|
exp, opened = self._begin_window("MMA Dashboard", self.show_windows["MMA Dashboard"])
|
||||||
self.show_windows["MMA Dashboard"] = bool(opened)
|
self.show_windows["MMA Dashboard"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_mma_dashboard")
|
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_mma_dashboard")
|
||||||
self._render_mma_dashboard()
|
self._render_mma_dashboard()
|
||||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_mma_dashboard")
|
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_mma_dashboard")
|
||||||
imgui.end()
|
self._end_window()
|
||||||
|
|
||||||
if self.ui_separate_task_dag and self.show_windows.get("Task DAG", False):
|
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)
|
self.show_windows["Task DAG"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_task_dag_panel()
|
self._render_task_dag_panel()
|
||||||
imgui.end()
|
self._end_window()
|
||||||
if self.ui_separate_tier1 and self.show_windows.get("Tier 1: Strategy", False):
|
if self.ui_separate_tier1 and self.show_windows.get("Tier 1: Strategy", False):
|
||||||
exp, opened = imgui.begin("Tier 1: Strategy", self.show_windows["Tier 1: Strategy"])
|
exp, opened = self._begin_window("Tier 1: Strategy", self.show_windows["Tier 1: Strategy"])
|
||||||
self.show_windows["Tier 1: Strategy"] = bool(opened)
|
self.show_windows["Tier 1: Strategy"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_tier_stream_panel("Tier 1", "Tier 1")
|
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):
|
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)
|
self.show_windows["Tier 2: Tech Lead"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_tier_stream_panel("Tier 2", "Tier 2 (Tech Lead)")
|
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):
|
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)
|
self.show_windows["Tier 3: Workers"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_tier_stream_panel("Tier 3", None)
|
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):
|
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)
|
self.show_windows["Tier 4: QA"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_tier_stream_panel("Tier 4", "Tier 4 (QA)")
|
self._render_tier_stream_panel("Tier 4", "Tier 4 (QA)")
|
||||||
imgui.end()
|
self._end_window()
|
||||||
if self.show_windows.get("Theme", False):
|
if self.show_windows.get("Theme", False):
|
||||||
self._render_theme_panel()
|
self._render_theme_panel()
|
||||||
if self.show_windows.get("Discussion Hub", False):
|
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)
|
self.show_windows["Discussion Hub"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
# Top part for the history
|
# Top part for the history
|
||||||
@@ -688,86 +806,37 @@ class App:
|
|||||||
else:
|
else:
|
||||||
imgui.text_disabled("Message & Response panels are detached.")
|
imgui.text_disabled("Message & Response panels are detached.")
|
||||||
|
|
||||||
imgui.end()
|
self._end_window()
|
||||||
if self.show_windows.get("Operations Hub", False):
|
if self.show_windows.get("Operations Hub", False):
|
||||||
exp, opened = imgui.begin("Operations Hub", self.show_windows["Operations Hub"])
|
self._render_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()
|
|
||||||
|
|
||||||
if self.ui_separate_message_panel and self.show_windows.get("Message", False):
|
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)
|
self.show_windows["Message"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_message_panel()
|
self._render_message_panel()
|
||||||
imgui.end()
|
self._end_window()
|
||||||
|
|
||||||
if self.ui_separate_response_panel and self.show_windows.get("Response", False):
|
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)
|
self.show_windows["Response"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_response_panel()
|
self._render_response_panel()
|
||||||
imgui.end()
|
self._end_window()
|
||||||
|
|
||||||
if self.ui_separate_tool_calls_panel and self.show_windows.get("Tool Calls", False):
|
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)
|
self.show_windows["Tool Calls"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_tool_calls_panel()
|
self._render_tool_calls_panel()
|
||||||
imgui.end()
|
self._end_window()
|
||||||
|
|
||||||
if self.ui_separate_external_tools and self.show_windows.get('External Tools', False):
|
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)
|
self.show_windows['External Tools'] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
self._render_external_tools_panel()
|
self._render_external_tools_panel()
|
||||||
imgui.end()
|
self._end_window()
|
||||||
|
|
||||||
if self.show_windows.get("Log Management", False):
|
if self.show_windows.get("Log Management", False):
|
||||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_log_management")
|
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_log_management")
|
||||||
@@ -958,6 +1027,7 @@ class App:
|
|||||||
expanded, opened = imgui.begin("Last Script Output", self.show_script_output)
|
expanded, opened = imgui.begin("Last Script Output", self.show_script_output)
|
||||||
self.show_script_output = bool(opened)
|
self.show_script_output = bool(opened)
|
||||||
if expanded:
|
if expanded:
|
||||||
|
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
|
||||||
imgui.text("Script:")
|
imgui.text("Script:")
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
self._render_text_viewer("Last Script", self.ui_last_script_text)
|
self._render_text_viewer("Last Script", self.ui_last_script_text)
|
||||||
@@ -989,6 +1059,7 @@ class App:
|
|||||||
expanded, opened = imgui.begin(f"Text Viewer - {self.text_viewer_title}", self.show_text_viewer)
|
expanded, opened = imgui.begin(f"Text Viewer - {self.text_viewer_title}", self.show_text_viewer)
|
||||||
self.show_text_viewer = bool(opened)
|
self.show_text_viewer = bool(opened)
|
||||||
if expanded:
|
if expanded:
|
||||||
|
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
|
||||||
if self.ui_word_wrap:
|
if self.ui_word_wrap:
|
||||||
imgui.begin_child("tv_wrap", imgui.ImVec2(-1, -1), False)
|
imgui.begin_child("tv_wrap", imgui.ImVec2(-1, -1), False)
|
||||||
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
|
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
|
||||||
@@ -1048,6 +1119,8 @@ class App:
|
|||||||
|
|
||||||
if pushed_prior_tint:
|
if pushed_prior_tint:
|
||||||
imgui.pop_style_color()
|
imgui.pop_style_color()
|
||||||
|
if pushed_frosted_style:
|
||||||
|
imgui.pop_style_color()
|
||||||
|
|
||||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_gui_func")
|
if self.perf_profiling_enabled: self.perf_monitor.end_component("_gui_func")
|
||||||
|
|
||||||
@@ -1934,7 +2007,6 @@ class App:
|
|||||||
imgui.text(f"History: {key}")
|
imgui.text(f"History: {key}")
|
||||||
hist_data = self.perf_monitor.get_history(key)
|
hist_data = self.perf_monitor.get_history(key)
|
||||||
if hist_data:
|
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))
|
imgui.plot_lines(f"##plot_{key}", np.array(hist_data, dtype=np.float32), graph_size=imgui.ImVec2(-1, 60))
|
||||||
else:
|
else:
|
||||||
imgui.text_disabled(f"(no history data for {key})")
|
imgui.text_disabled(f"(no history data for {key})")
|
||||||
@@ -2082,6 +2154,7 @@ def hello():
|
|||||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_screenshots_panel")
|
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_screenshots_panel")
|
||||||
|
|
||||||
def _render_discussion_panel(self) -> None:
|
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")
|
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_discussion_panel")
|
||||||
# THINKING indicator
|
# THINKING indicator
|
||||||
is_thinking = self.ai_status in ['sending...', 'streaming...', 'running powershell...']
|
is_thinking = self.ai_status in ['sending...', 'streaming...', 'running powershell...']
|
||||||
@@ -3905,6 +3978,7 @@ def hello():
|
|||||||
exp, opened = imgui.begin("Theme", self.show_windows["Theme"])
|
exp, opened = imgui.begin("Theme", self.show_windows["Theme"])
|
||||||
self.show_windows["Theme"] = bool(opened)
|
self.show_windows["Theme"] = bool(opened)
|
||||||
if exp:
|
if exp:
|
||||||
|
self._render_frosted_background(imgui.get_window_pos(), imgui.get_window_size())
|
||||||
imgui.text("Palette")
|
imgui.text("Palette")
|
||||||
cp = theme.get_current_palette()
|
cp = theme.get_current_palette()
|
||||||
if imgui.begin_combo("##pal", cp):
|
if imgui.begin_combo("##pal", cp):
|
||||||
@@ -3978,6 +4052,10 @@ def hello():
|
|||||||
gui_cfg["crt_filter_enabled"] = self.ui_crt_filter
|
gui_cfg["crt_filter_enabled"] = self.ui_crt_filter
|
||||||
self._flush_to_config()
|
self._flush_to_config()
|
||||||
models.save_config(self.config)
|
models.save_config(self.config)
|
||||||
|
|
||||||
|
ch_fg, fg_val = imgui.checkbox("Frosted Glass Effect", theme.get_frosted_glass_enabled())
|
||||||
|
if ch_fg:
|
||||||
|
theme.set_frosted_glass_enabled(fg_val)
|
||||||
self._flush_to_config()
|
self._flush_to_config()
|
||||||
models.save_config(self.config)
|
models.save_config(self.config)
|
||||||
imgui.end()
|
imgui.end()
|
||||||
@@ -4023,36 +4101,8 @@ def hello():
|
|||||||
|
|
||||||
def _post_init(self) -> None:
|
def _post_init(self) -> None:
|
||||||
theme.apply_current()
|
theme.apply_current()
|
||||||
|
self.shader_manager.setup_background_shader()
|
||||||
def _init_blur_pipeline(self):
|
self.shader_manager.setup_frosted_glass_shader()
|
||||||
if self._blur_pipeline is None:
|
|
||||||
self._blur_pipeline = BlurPipeline()
|
|
||||||
ws = imgui.get_io().display_size
|
|
||||||
fb_scale = imgui.get_io().display_framebuffer_scale.x
|
|
||||||
if ws.x <= 0 or ws.y <= 0:
|
|
||||||
return False
|
|
||||||
if fb_scale <= 0:
|
|
||||||
fb_scale = 1.0
|
|
||||||
self._blur_pipeline.setup_fbos(int(ws.x), int(ws.y), fb_scale)
|
|
||||||
self._blur_pipeline.compile_deepsea_shader()
|
|
||||||
self._blur_pipeline.compile_blur_shaders()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _pre_new_frame(self) -> None:
|
|
||||||
if not self.ui_frosted_glass_enabled:
|
|
||||||
return
|
|
||||||
ws = imgui.get_io().display_size
|
|
||||||
fb_scale = imgui.get_io().display_framebuffer_scale.x
|
|
||||||
if ws.x <= 0 or ws.y <= 0:
|
|
||||||
return
|
|
||||||
if fb_scale <= 0:
|
|
||||||
fb_scale = 1.0
|
|
||||||
if self._blur_pipeline is None:
|
|
||||||
if not self._init_blur_pipeline():
|
|
||||||
return
|
|
||||||
import time
|
|
||||||
t = time.time()
|
|
||||||
self._blur_pipeline.prepare_global_blur(int(ws.x), int(ws.y), t, fb_scale)
|
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
"""Initializes the ImGui runner and starts the main application loop."""
|
"""Initializes the ImGui runner and starts the main application loop."""
|
||||||
@@ -4109,8 +4159,6 @@ def hello():
|
|||||||
self.runner_params.callbacks.load_additional_fonts = self._load_fonts
|
self.runner_params.callbacks.load_additional_fonts = self._load_fonts
|
||||||
self.runner_params.callbacks.setup_imgui_style = theme.apply_current
|
self.runner_params.callbacks.setup_imgui_style = theme.apply_current
|
||||||
self.runner_params.callbacks.post_init = self._post_init
|
self.runner_params.callbacks.post_init = self._post_init
|
||||||
self.runner_params.callbacks.pre_new_frame = self._pre_new_frame
|
|
||||||
self.runner_params.callbacks.custom_background = self._render_custom_background
|
|
||||||
self._fetch_models(self.current_provider)
|
self._fetch_models(self.current_provider)
|
||||||
md_options = markdown_helper.get_renderer().options
|
md_options = markdown_helper.get_renderer().options
|
||||||
immapp.run(self.runner_params, add_ons_params=immapp.AddOnsParams(with_markdown_options=md_options))
|
immapp.run(self.runner_params, add_ons_params=immapp.AddOnsParams(with_markdown_options=md_options))
|
||||||
|
|||||||
@@ -5,6 +5,113 @@ class ShaderManager:
|
|||||||
self.program = None
|
self.program = None
|
||||||
self.bg_program = None
|
self.bg_program = None
|
||||||
self.pp_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:
|
def compile_shader(self, vertex_src: str, fragment_src: str) -> int:
|
||||||
program = gl.glCreateProgram()
|
program = gl.glCreateProgram()
|
||||||
@@ -79,9 +186,44 @@ void main() {
|
|||||||
uniform float u_time;
|
uniform float u_time;
|
||||||
uniform vec2 u_resolution;
|
uniform vec2 u_resolution;
|
||||||
out vec4 FragColor;
|
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() {
|
void main() {
|
||||||
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
|
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
|
||||||
vec3 col = 0.5 + 0.5 * cos(u_time + uv.xyx + vec3(0, 2, 4));
|
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);
|
FragColor = vec4(col, 1.0);
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
@@ -97,6 +239,7 @@ void main() {
|
|||||||
u_res_loc = gl.glGetUniformLocation(self.bg_program, "u_resolution")
|
u_res_loc = gl.glGetUniformLocation(self.bg_program, "u_resolution")
|
||||||
if u_res_loc != -1:
|
if u_res_loc != -1:
|
||||||
gl.glUniform2f(u_res_loc, float(width), float(height))
|
gl.glUniform2f(u_res_loc, float(width), float(height))
|
||||||
|
self._ensure_vao()
|
||||||
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
||||||
gl.glUseProgram(0)
|
gl.glUseProgram(0)
|
||||||
|
|
||||||
@@ -148,327 +291,114 @@ void main() {
|
|||||||
u_time_loc = gl.glGetUniformLocation(self.pp_program, "u_time")
|
u_time_loc = gl.glGetUniformLocation(self.pp_program, "u_time")
|
||||||
if u_time_loc != -1:
|
if u_time_loc != -1:
|
||||||
gl.glUniform1f(u_time_loc, float(time))
|
gl.glUniform1f(u_time_loc, float(time))
|
||||||
|
self._ensure_vao()
|
||||||
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
||||||
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
|
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
|
||||||
|
|
||||||
class BlurPipeline:
|
|
||||||
def __init__(self):
|
|
||||||
self.scene_fbo: int | None = None
|
|
||||||
self.scene_tex: int | None = None
|
|
||||||
self.blur_fbo_a: int | None = None
|
|
||||||
self.blur_tex_a: int | None = None
|
|
||||||
self.blur_fbo_b: int | None = None
|
|
||||||
self.blur_tex_b: int | None = None
|
|
||||||
self.h_blur_program: int | None = None
|
|
||||||
self.v_blur_program: int | None = None
|
|
||||||
self.deepsea_program: int | None = None
|
|
||||||
self._quad_vao: int | None = None
|
|
||||||
self._fb_width: int = 0
|
|
||||||
self._fb_height: int = 0
|
|
||||||
self._fb_scale: int = 1
|
|
||||||
|
|
||||||
def _compile_shader(self, vertex_src: str, fragment_src: str) -> int:
|
|
||||||
program = gl.glCreateProgram()
|
|
||||||
def _compile(src, shader_type):
|
|
||||||
shader = gl.glCreateShader(shader_type)
|
|
||||||
gl.glShaderSource(shader, src)
|
|
||||||
gl.glCompileShader(shader)
|
|
||||||
if not gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS):
|
|
||||||
info_log = gl.glGetShaderInfoLog(shader)
|
|
||||||
if hasattr(info_log, "decode"):
|
|
||||||
info_log = info_log.decode()
|
|
||||||
raise RuntimeError(f"Shader compilation failed: {info_log}")
|
|
||||||
return shader
|
|
||||||
vert_shader = _compile(vertex_src, gl.GL_VERTEX_SHADER)
|
|
||||||
frag_shader = _compile(fragment_src, gl.GL_FRAGMENT_SHADER)
|
|
||||||
gl.glAttachShader(program, vert_shader)
|
|
||||||
gl.glAttachShader(program, frag_shader)
|
|
||||||
gl.glLinkProgram(program)
|
|
||||||
if not gl.glGetProgramiv(program, gl.GL_LINK_STATUS):
|
|
||||||
info_log = gl.glGetProgramInfoLog(program)
|
|
||||||
if hasattr(info_log, "decode"):
|
|
||||||
info_log = info_log.decode()
|
|
||||||
raise RuntimeError(f"Program linking failed: {info_log}")
|
|
||||||
gl.glDeleteShader(vert_shader)
|
|
||||||
gl.glDeleteShader(frag_shader)
|
|
||||||
return program
|
|
||||||
|
|
||||||
def _create_fbo(self, width: int, height: int) -> tuple[int, int]:
|
|
||||||
if width <= 0 or height <= 0:
|
|
||||||
raise ValueError(f"Invalid FBO dimensions: {width}x{height}")
|
|
||||||
tex = gl.glGenTextures(1)
|
|
||||||
gl.glBindTexture(gl.GL_TEXTURE_2D, tex)
|
|
||||||
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA8, width, height, 0, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, None)
|
|
||||||
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
|
|
||||||
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
|
|
||||||
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE)
|
|
||||||
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE)
|
|
||||||
fbo = gl.glGenFramebuffers(1)
|
|
||||||
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, fbo)
|
|
||||||
gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D, tex, 0)
|
|
||||||
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
|
|
||||||
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
|
|
||||||
return fbo, tex
|
|
||||||
|
|
||||||
def _create_quad_vao(self) -> int:
|
|
||||||
import ctypes
|
|
||||||
vao = gl.glGenVertexArrays(1)
|
|
||||||
gl.glBindVertexArray(vao)
|
|
||||||
vertices = (ctypes.c_float * 16)(
|
|
||||||
-1.0, -1.0, 0.0, 0.0,
|
|
||||||
1.0, -1.0, 1.0, 0.0,
|
|
||||||
-1.0, 1.0, 0.0, 1.0,
|
|
||||||
1.0, 1.0, 1.0, 1.0
|
|
||||||
)
|
|
||||||
vbo = gl.glGenBuffers(1)
|
|
||||||
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vbo)
|
|
||||||
gl.glBufferData(gl.GL_ARRAY_BUFFER, ctypes.sizeof(vertices), vertices, gl.GL_STATIC_DRAW)
|
|
||||||
gl.glEnableVertexAttribArray(0)
|
|
||||||
gl.glVertexAttribPointer(0, 2, gl.GL_FLOAT, gl.GL_FALSE, 16, None)
|
|
||||||
gl.glEnableVertexAttribArray(1)
|
|
||||||
gl.glVertexAttribPointer(1, 2, gl.GL_FLOAT, gl.GL_FALSE, 16, ctypes.c_void_p(8))
|
|
||||||
gl.glBindVertexArray(0)
|
|
||||||
return vao
|
|
||||||
|
|
||||||
def setup_fbos(self, width: int, height: int, fb_scale: float = 1.0):
|
|
||||||
scale = max(1, int(fb_scale))
|
|
||||||
blur_w = max(1, (width * scale) // 4)
|
|
||||||
blur_h = max(1, (height * scale) // 4)
|
|
||||||
self._fb_width = blur_w
|
|
||||||
self._fb_height = blur_h
|
|
||||||
self._fb_scale = scale
|
|
||||||
scene_w = width * scale
|
|
||||||
scene_h = height * scale
|
|
||||||
self.scene_fbo, self.scene_tex = self._create_fbo(scene_w, scene_h)
|
|
||||||
self.blur_fbo_a, self.blur_tex_a = self._create_fbo(blur_w, blur_h)
|
|
||||||
self.blur_fbo_b, self.blur_tex_b = self._create_fbo(blur_w, blur_h)
|
|
||||||
|
|
||||||
def compile_blur_shaders(self):
|
|
||||||
vert_src = """
|
|
||||||
#version 330 core
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texcoord;
|
|
||||||
out vec2 v_uv;
|
|
||||||
void main() {
|
|
||||||
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
||||||
v_uv = a_texcoord;
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
h_frag_src = """
|
|
||||||
#version 330 core
|
|
||||||
in vec2 v_uv;
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform vec2 u_texel_size;
|
|
||||||
out vec4 FragColor;
|
|
||||||
void main() {
|
|
||||||
vec2 offset = vec2(u_texel_size.x, 0.0);
|
|
||||||
vec4 sum = vec4(0.0);
|
|
||||||
sum += texture(u_texture, v_uv - offset * 6.0) * 0.0152;
|
|
||||||
sum += texture(u_texture, v_uv - offset * 5.0) * 0.0300;
|
|
||||||
sum += texture(u_texture, v_uv - offset * 4.0) * 0.0525;
|
|
||||||
sum += texture(u_texture, v_uv - offset * 3.0) * 0.0812;
|
|
||||||
sum += texture(u_texture, v_uv - offset * 2.0) * 0.1110;
|
|
||||||
sum += texture(u_texture, v_uv - offset * 1.0) * 0.1342;
|
|
||||||
sum += texture(u_texture, v_uv) * 0.1432;
|
|
||||||
sum += texture(u_texture, v_uv + offset * 1.0) * 0.1342;
|
|
||||||
sum += texture(u_texture, v_uv + offset * 2.0) * 0.1110;
|
|
||||||
sum += texture(u_texture, v_uv + offset * 3.0) * 0.0812;
|
|
||||||
sum += texture(u_texture, v_uv + offset * 4.0) * 0.0525;
|
|
||||||
sum += texture(u_texture, v_uv + offset * 5.0) * 0.0300;
|
|
||||||
sum += texture(u_texture, v_uv + offset * 6.0) * 0.0152;
|
|
||||||
FragColor = sum;
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
v_frag_src = """
|
|
||||||
#version 330 core
|
|
||||||
in vec2 v_uv;
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform vec2 u_texel_size;
|
|
||||||
out vec4 FragColor;
|
|
||||||
void main() {
|
|
||||||
vec2 offset = vec2(0.0, u_texel_size.y);
|
|
||||||
vec4 sum = vec4(0.0);
|
|
||||||
sum += texture(u_texture, v_uv - offset * 6.0) * 0.0152;
|
|
||||||
sum += texture(u_texture, v_uv - offset * 5.0) * 0.0300;
|
|
||||||
sum += texture(u_texture, v_uv - offset * 4.0) * 0.0525;
|
|
||||||
sum += texture(u_texture, v_uv - offset * 3.0) * 0.0812;
|
|
||||||
sum += texture(u_texture, v_uv - offset * 2.0) * 0.1110;
|
|
||||||
sum += texture(u_texture, v_uv - offset * 1.0) * 0.1342;
|
|
||||||
sum += texture(u_texture, v_uv) * 0.1432;
|
|
||||||
sum += texture(u_texture, v_uv + offset * 1.0) * 0.1342;
|
|
||||||
sum += texture(u_texture, v_uv + offset * 2.0) * 0.1110;
|
|
||||||
sum += texture(u_texture, v_uv + offset * 3.0) * 0.0812;
|
|
||||||
sum += texture(u_texture, v_uv + offset * 4.0) * 0.0525;
|
|
||||||
sum += texture(u_texture, v_uv + offset * 5.0) * 0.0300;
|
|
||||||
sum += texture(u_texture, v_uv + offset * 6.0) * 0.0152;
|
|
||||||
FragColor = sum;
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
self.h_blur_program = self._compile_shader(vert_src, h_frag_src)
|
|
||||||
self.v_blur_program = self._compile_shader(vert_src, v_frag_src)
|
|
||||||
|
|
||||||
def compile_deepsea_shader(self):
|
|
||||||
vert_src = """
|
|
||||||
#version 330 core
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texcoord;
|
|
||||||
out vec2 v_uv;
|
|
||||||
void main() {
|
|
||||||
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
||||||
v_uv = a_texcoord;
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
frag_src = """
|
|
||||||
#version 330 core
|
|
||||||
in vec2 v_uv;
|
|
||||||
uniform float u_time;
|
|
||||||
uniform vec2 u_resolution;
|
|
||||||
out vec4 FragColor;
|
|
||||||
|
|
||||||
float hash(vec2 p) {
|
|
||||||
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
|
|
||||||
}
|
|
||||||
|
|
||||||
float noise(vec2 p) {
|
|
||||||
vec2 i = floor(p);
|
|
||||||
vec2 f = fract(p);
|
|
||||||
f = f * f * (3.0 - 2.0 * f);
|
|
||||||
float a = hash(i);
|
|
||||||
float b = hash(i + vec2(1.0, 0.0));
|
|
||||||
float c = hash(i + vec2(0.0, 1.0));
|
|
||||||
float d = hash(i + vec2(1.0, 1.0));
|
|
||||||
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
float fbm(vec2 p) {
|
|
||||||
float v = 0.0;
|
|
||||||
float a = 0.5;
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
v += a * noise(p);
|
|
||||||
p *= 2.0;
|
|
||||||
a *= 0.5;
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 uv = v_uv;
|
|
||||||
float t = u_time * 0.3;
|
|
||||||
vec3 col = vec3(0.01, 0.05, 0.12);
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
float phase = t * (0.1 + float(i) * 0.05);
|
|
||||||
vec2 blob_uv = uv + vec2(sin(phase), cos(phase * 0.8)) * 0.3;
|
|
||||||
float blob = fbm(blob_uv * 3.0 + t * 0.2);
|
|
||||||
col = mix(col, vec3(0.02, 0.20, 0.40), blob * 0.4);
|
|
||||||
}
|
|
||||||
float line_alpha = 0.0;
|
|
||||||
for (int i = 0; i < 12; i++) {
|
|
||||||
float fi = float(i);
|
|
||||||
float offset = mod(t * 15.0 + fi * (u_resolution.x / 12.0), u_resolution.x);
|
|
||||||
float line_x = offset / u_resolution.x;
|
|
||||||
float dist = abs(uv.x - line_x);
|
|
||||||
float alpha = smoothstep(0.02, 0.0, dist) * (0.1 + 0.05 * sin(t + fi));
|
|
||||||
line_alpha += alpha;
|
|
||||||
}
|
|
||||||
col += vec3(0.04, 0.35, 0.55) * line_alpha;
|
|
||||||
float vignette = 1.0 - length(uv - 0.5) * 0.8;
|
|
||||||
col *= vignette;
|
|
||||||
FragColor = vec4(col, 1.0);
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
self.deepsea_program = self._compile_shader(vert_src, frag_src)
|
|
||||||
self._quad_vao = self._create_quad_vao()
|
|
||||||
|
|
||||||
def render_deepsea_to_fbo(self, width: int, height: int, time: float):
|
|
||||||
if not self.deepsea_program or not self.scene_fbo or not self._quad_vao:
|
|
||||||
return
|
|
||||||
scene_w = width * self._fb_scale
|
|
||||||
scene_h = height * self._fb_scale
|
|
||||||
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.scene_fbo)
|
|
||||||
gl.glViewport(0, 0, scene_w, scene_h)
|
|
||||||
gl.glClearColor(0.01, 0.05, 0.12, 1.0)
|
|
||||||
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
|
|
||||||
gl.glUseProgram(self.deepsea_program)
|
|
||||||
u_time_loc = gl.glGetUniformLocation(self.deepsea_program, "u_time")
|
|
||||||
if u_time_loc != -1:
|
|
||||||
gl.glUniform1f(u_time_loc, time)
|
|
||||||
u_res_loc = gl.glGetUniformLocation(self.deepsea_program, "u_resolution")
|
|
||||||
if u_res_loc != -1:
|
|
||||||
gl.glUniform2f(u_res_loc, float(scene_w), float(scene_h))
|
|
||||||
gl.glBindVertexArray(self._quad_vao)
|
|
||||||
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
|
||||||
gl.glBindVertexArray(0)
|
|
||||||
gl.glUseProgram(0)
|
gl.glUseProgram(0)
|
||||||
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
|
|
||||||
|
|
||||||
def _render_quad(self, program: int, src_tex: int, texel_size: tuple[float, float]):
|
def setup_frosted_glass_shader(self):
|
||||||
gl.glUseProgram(program)
|
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.glActiveTexture(gl.GL_TEXTURE0)
|
||||||
gl.glBindTexture(gl.GL_TEXTURE_2D, src_tex)
|
gl.glBindTexture(gl.GL_TEXTURE_2D, texture_id)
|
||||||
u_tex = gl.glGetUniformLocation(program, "u_texture")
|
|
||||||
if u_tex != -1:
|
gl.glUniform1i(gl.glGetUniformLocation(self.blur_h_program, "u_texture"), 0)
|
||||||
gl.glUniform1i(u_tex, 0)
|
gl.glUniform1f(gl.glGetUniformLocation(self.blur_h_program, "u_blur_radius"), float(radius))
|
||||||
u_ts = gl.glGetUniformLocation(program, "u_texel_size")
|
gl.glUniform2f(gl.glGetUniformLocation(self.blur_h_program, "u_direction"), 1.0, 0.0)
|
||||||
if u_ts != -1:
|
|
||||||
gl.glUniform2f(u_ts, texel_size[0], texel_size[1])
|
|
||||||
gl.glBindVertexArray(self._quad_vao)
|
|
||||||
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
|
||||||
gl.glBindVertexArray(0)
|
|
||||||
|
# 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.glBindTexture(gl.GL_TEXTURE_2D, 0)
|
||||||
gl.glUseProgram(0)
|
gl.glUseProgram(0)
|
||||||
|
|
||||||
def prepare_blur(self, width: int, height: int, time: float):
|
|
||||||
if not self.h_blur_program or not self.v_blur_program:
|
|
||||||
return
|
|
||||||
if not self.blur_fbo_a or not self.blur_fbo_b:
|
|
||||||
return
|
|
||||||
blur_w = max(1, self._fb_width)
|
|
||||||
blur_h = max(1, self._fb_height)
|
|
||||||
texel_x = 1.0 / float(blur_w)
|
|
||||||
texel_y = 1.0 / float(blur_h)
|
|
||||||
gl.glViewport(0, 0, blur_w, blur_h)
|
|
||||||
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo_a)
|
|
||||||
gl.glClearColor(0.0, 0.0, 0.0, 0.0)
|
|
||||||
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
|
|
||||||
self._render_quad(self.h_blur_program, self.scene_tex, (texel_x, texel_y))
|
|
||||||
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.blur_fbo_b)
|
|
||||||
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
|
|
||||||
self._render_quad(self.v_blur_program, self.blur_tex_a, (texel_x, texel_y))
|
|
||||||
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
|
|
||||||
restore_w = width * self._fb_scale
|
|
||||||
restore_h = height * self._fb_scale
|
|
||||||
gl.glViewport(0, 0, restore_w, restore_h)
|
|
||||||
|
|
||||||
def prepare_global_blur(self, width: int, height: int, time: float, fb_scale: float = 1.0):
|
|
||||||
if not self.scene_fbo:
|
|
||||||
if self._fb_scale != int(fb_scale):
|
|
||||||
self.setup_fbos(width, height, fb_scale)
|
|
||||||
self.render_deepsea_to_fbo(width, height, time)
|
|
||||||
self.prepare_blur(width, height, time)
|
|
||||||
|
|
||||||
def get_blur_texture(self) -> int | None:
|
|
||||||
return self.blur_tex_b
|
|
||||||
|
|
||||||
def cleanup(self):
|
|
||||||
fbos = [f for f in [self.scene_fbo, self.blur_fbo_a, self.blur_fbo_b] if f is not None]
|
|
||||||
texs = [t for t in [self.scene_tex, self.blur_tex_a, self.blur_tex_b] if t is not None]
|
|
||||||
progs = [p for p in [self.h_blur_program, self.v_blur_program, self.deepsea_program] if p is not None]
|
|
||||||
if fbos:
|
|
||||||
gl.glDeleteFramebuffers(len(fbos), fbos)
|
|
||||||
if texs:
|
|
||||||
gl.glDeleteTextures(len(texs), texs)
|
|
||||||
if progs:
|
|
||||||
for p in progs:
|
|
||||||
gl.glDeleteProgram(p)
|
|
||||||
if self._quad_vao:
|
|
||||||
gl.glDeleteVertexArrays(1, [self._quad_vao])
|
|
||||||
self.scene_fbo = None
|
|
||||||
self.scene_tex = None
|
|
||||||
self.blur_fbo_a = None
|
|
||||||
self.blur_tex_a = None
|
|
||||||
self.blur_fbo_b = None
|
|
||||||
self.blur_tex_b = None
|
|
||||||
self.h_blur_program = None
|
|
||||||
self.v_blur_program = None
|
|
||||||
self.deepsea_program = None
|
|
||||||
self._quad_vao = None
|
|
||||||
|
|||||||
@@ -235,6 +235,10 @@ _current_font_size: float = 16.0
|
|||||||
_current_scale: float = 1.0
|
_current_scale: float = 1.0
|
||||||
_transparency: float = 1.0
|
_transparency: float = 1.0
|
||||||
_child_transparency: float = 1.0
|
_child_transparency: float = 1.0
|
||||||
|
_frosted_glass_enabled: bool = False
|
||||||
|
_frosted_blur_radius: float = 8.0
|
||||||
|
_frosted_tint_intensity: float = 0.1
|
||||||
|
_frosted_opacity: float = 1.0
|
||||||
|
|
||||||
# ------------------------------------------------------------------ public API
|
# ------------------------------------------------------------------ public API
|
||||||
|
|
||||||
@@ -269,6 +273,34 @@ def set_child_transparency(val: float) -> None:
|
|||||||
_child_transparency = val
|
_child_transparency = val
|
||||||
apply(_current_palette)
|
apply(_current_palette)
|
||||||
|
|
||||||
|
def get_frosted_glass_enabled() -> bool:
|
||||||
|
return _frosted_glass_enabled
|
||||||
|
|
||||||
|
def set_frosted_glass_enabled(val: bool) -> None:
|
||||||
|
global _frosted_glass_enabled
|
||||||
|
_frosted_glass_enabled = val
|
||||||
|
|
||||||
|
def get_frosted_blur_radius() -> float:
|
||||||
|
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:
|
def apply(palette_name: str) -> None:
|
||||||
"""
|
"""
|
||||||
Apply a named palette by setting all ImGui style colors and applying global professional styling.
|
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"]["scale"] = _current_scale
|
||||||
config["theme"]["transparency"] = _transparency
|
config["theme"]["transparency"] = _transparency
|
||||||
config["theme"]["child_transparency"] = _child_transparency
|
config["theme"]["child_transparency"] = _child_transparency
|
||||||
|
config["theme"]["frosted_glass_enabled"] = _frosted_glass_enabled
|
||||||
|
config["theme"]["frosted_blur_radius"] = _frosted_blur_radius
|
||||||
|
config["theme"]["frosted_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.write(f"[DEBUG theme_2] save_to_config: palette={_current_palette}, transparency={_transparency}\n")
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
|
|
||||||
def load_from_config(config: dict) -> None:
|
def load_from_config(config: dict) -> None:
|
||||||
"""Read [theme] from config. Font is handled separately at startup."""
|
"""Read [theme] from config. Font is handled separately at startup."""
|
||||||
import sys
|
import sys
|
||||||
global _current_font_path, _current_font_size, _current_scale, _current_palette, _transparency, _child_transparency
|
global _current_font_path, _current_font_size, _current_scale, _current_palette, _transparency, _child_transparency, _frosted_glass_enabled, _frosted_blur_radius, _frosted_tint_intensity, _frosted_opacity
|
||||||
t = config.get("theme", {})
|
t = config.get("theme", {})
|
||||||
sys.stderr.write(f"[DEBUG theme_2] load_from_config raw: {t}\n")
|
sys.stderr.write(f"[DEBUG theme_2] load_from_config raw: {t}\n")
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
@@ -369,6 +405,10 @@ def load_from_config(config: dict) -> None:
|
|||||||
_current_scale = float(t.get("scale", 1.0))
|
_current_scale = float(t.get("scale", 1.0))
|
||||||
_transparency = float(t.get("transparency", 1.0))
|
_transparency = float(t.get("transparency", 1.0))
|
||||||
_child_transparency = float(t.get("child_transparency", 1.0))
|
_child_transparency = float(t.get("child_transparency", 1.0))
|
||||||
|
_frosted_glass_enabled = bool(t.get("frosted_glass_enabled", False))
|
||||||
|
_frosted_blur_radius = float(t.get("frosted_blur_radius", 8.0))
|
||||||
|
_frosted_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.write(f"[DEBUG theme_2] load_from_config effective: palette={_current_palette}, transparency={_transparency}\n")
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
|||||||
42
tests/test_fbo_capture.py
Normal file
42
tests/test_fbo_capture.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import pytest
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
import OpenGL.GL as gl
|
||||||
|
|
||||||
|
def test_shader_manager_fbo_initialization():
|
||||||
|
with patch("src.shader_manager.gl") as mock_gl:
|
||||||
|
mock_gl.glGenFramebuffers.side_effect = [1, 2]
|
||||||
|
mock_gl.glGenTextures.side_effect = [3, 4]
|
||||||
|
mock_gl.glCheckFramebufferStatus.return_value = mock_gl.GL_FRAMEBUFFER_COMPLETE
|
||||||
|
|
||||||
|
from src.shader_manager import ShaderManager
|
||||||
|
manager = ShaderManager()
|
||||||
|
|
||||||
|
manager.setup_capture_fbo(800, 600)
|
||||||
|
|
||||||
|
assert manager.scene_fbo == 1
|
||||||
|
assert manager.blur_fbo == 2
|
||||||
|
assert manager.scene_tex == 3
|
||||||
|
assert manager.blur_tex == 4
|
||||||
|
assert mock_gl.glGenFramebuffers.call_count == 2
|
||||||
|
assert mock_gl.glGenTextures.call_count == 2
|
||||||
|
assert mock_gl.glCheckFramebufferStatus.call_count == 2
|
||||||
|
|
||||||
|
def test_shader_manager_capture_lifecycle():
|
||||||
|
with patch("src.shader_manager.gl") as mock_gl:
|
||||||
|
mock_gl.glCheckFramebufferStatus.return_value = mock_gl.GL_FRAMEBUFFER_COMPLETE
|
||||||
|
mock_gl.glGenFramebuffers.side_effect = [1, 2]
|
||||||
|
mock_gl.glGenTextures.side_effect = [3, 4]
|
||||||
|
from src.shader_manager import ShaderManager
|
||||||
|
manager = ShaderManager()
|
||||||
|
|
||||||
|
# Ensure setup is called on first capture
|
||||||
|
manager.capture_begin(1024, 768)
|
||||||
|
assert manager.fbo_width == 1024
|
||||||
|
assert manager.fbo_height == 768
|
||||||
|
# Should bind the blur FBO
|
||||||
|
mock_gl.glBindFramebuffer.assert_any_call(mock_gl.GL_FRAMEBUFFER, manager.blur_fbo)
|
||||||
|
|
||||||
|
mock_gl.glBindFramebuffer.reset_mock()
|
||||||
|
manager.capture_end()
|
||||||
|
# Verify unbind (glBindFramebuffer(..., 0))
|
||||||
|
mock_gl.glBindFramebuffer.assert_called_with(mock_gl.GL_FRAMEBUFFER, 0)
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import pytest
|
|
||||||
from unittest.mock import patch, MagicMock
|
|
||||||
from src.gui_2 import App
|
|
||||||
|
|
||||||
|
|
||||||
def test_frosted_glass_disabled():
|
|
||||||
with patch("src.gui_2.imgui") as mock_imgui:
|
|
||||||
with patch("src.gui_2.gl") as mock_gl:
|
|
||||||
app = App()
|
|
||||||
app.ui_frosted_glass_enabled = False
|
|
||||||
app._render_frosted_background((0, 0), (100, 100))
|
|
||||||
assert app._blur_pipeline is None
|
|
||||||
mock_gl.glEnable.assert_not_called()
|
|
||||||
mock_gl.glBlendFunc.assert_not_called()
|
|
||||||
mock_gl.glBindTexture.assert_not_called()
|
|
||||||
mock_gl.glBegin.assert_not_called()
|
|
||||||
mock_gl.glEnd.assert_not_called()
|
|
||||||
mock_gl.glDisable.assert_not_called()
|
|
||||||
mock_imgui.get_io().display_size.assert_not_called()
|
|
||||||
mock_imgui.get_io().display_framebuffer_scale.assert_not_called()
|
|
||||||
mock_imgui.get_window_draw_list.assert_not_called()
|
|
||||||
mock_imgui.get_window_pos.assert_not_called()
|
|
||||||
mock_imgui.get_window_size.assert_not_called()
|
|
||||||
mock_imgui.get_color_u32.assert_not_called()
|
|
||||||
mock_imgui.push_texture_id.assert_not_called()
|
|
||||||
mock_imgui.pop_texture_id.assert_not_called()
|
|
||||||
19
tests/test_frosted_glass_shader.py
Normal file
19
tests/test_frosted_glass_shader.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import pytest
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
|
def test_shader_manager_frosted_glass_compilation():
|
||||||
|
# Mock OpenGL before importing ShaderManager
|
||||||
|
with patch("src.shader_manager.gl") as mock_gl:
|
||||||
|
mock_gl.glCreateProgram.return_value = 1
|
||||||
|
mock_gl.glCreateShader.return_value = 2
|
||||||
|
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
|
||||||
|
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
|
||||||
|
|
||||||
|
from src.shader_manager import ShaderManager
|
||||||
|
manager = ShaderManager()
|
||||||
|
|
||||||
|
# This should fail initially because the method doesn't exist
|
||||||
|
manager.setup_frosted_glass_shader()
|
||||||
|
|
||||||
|
assert manager.blur_program is not None
|
||||||
|
assert mock_gl.glCreateProgram.called
|
||||||
@@ -26,84 +26,5 @@ def test_gui2_old_windows_removed_from_show_windows(app_instance: App) -> None:
|
|||||||
"Provider", "System Prompts",
|
"Provider", "System Prompts",
|
||||||
"Comms History"
|
"Comms History"
|
||||||
]
|
]
|
||||||
for old_win in old_windows:
|
|
||||||
from src.gui_2 import App
|
|
||||||
|
|
||||||
def test_gui2_hubs_exist_in_show_windows(app_instance: App) -> None:
|
|
||||||
expected_hubs = [
|
|
||||||
"Context Hub",
|
|
||||||
"AI Settings",
|
|
||||||
"Discussion Hub",
|
|
||||||
"Operations Hub",
|
|
||||||
"Files & Media",
|
|
||||||
"Theme",
|
|
||||||
]
|
|
||||||
for hub in expected_hubs:
|
|
||||||
assert hub in app_instance.show_windows, f"Expected hub window '{hub}' not found in show_windows"
|
|
||||||
|
|
||||||
def test_gui2_old_windows_removed_from_show_windows(app_instance: App) -> None:
|
|
||||||
old_windows = [
|
|
||||||
"Projects", "Files", "Screenshots",
|
|
||||||
"Provider", "System Prompts",
|
|
||||||
"Comms History"
|
|
||||||
]
|
|
||||||
for old_win in old_windows:
|
for old_win in old_windows:
|
||||||
assert old_win not in app_instance.show_windows, f"Old window '{old_win}' should have been removed from show_windows"
|
assert old_win not in app_instance.show_windows, f"Old window '{old_win}' should have been removed from show_windows"
|
||||||
|
|
||||||
def test_frosted_glass_disabled():
|
|
||||||
with patch("src.gui_2.imgui"):
|
|
||||||
app = App()
|
|
||||||
app.ui_frosted_glass_enabled = False
|
|
||||||
app._render_frosted_background((0, 0), (100, 100))
|
|
||||||
assert not app._blur_pipeline is None or not app._blur_pipeline.prepare_global_blur.called
|
|
||||||
imgui.get_io().display_size.assert_not_called()
|
|
||||||
imgui.get_io().display_framebuffer_scale.assert_not_called()
|
|
||||||
imgui.get_window_draw_list.assert_not_called()
|
|
||||||
imgui.get_window_pos.assert_not_called()
|
|
||||||
imgui.get_window_size.assert_not_called()
|
|
||||||
imgui.get_color_u32.assert_not_called()
|
|
||||||
imgui.push_texture_id.assert_not_called()
|
|
||||||
imgui.pop_texture_id.assert_not_called()
|
|
||||||
dl.add_image_quad.assert_not_called()
|
|
||||||
imgui.pop_texture_id.assert_not_called()
|
|
||||||
gl.glEnable.assert_not_called()
|
|
||||||
gl.glBlendFunc.assert_not_called()
|
|
||||||
gl.glBindTexture.assert_not_called()
|
|
||||||
gl.glBegin.assert_not_called()
|
|
||||||
gl.glEnd.assert_not_called()
|
|
||||||
gl.glDisable.assert_not_called()
|
|
||||||
gl.glUnbindTexture.assert_not_called()
|
|
||||||
gl.glDeleteTexture.assert_not_called()
|
|
||||||
gl.glDisable.assert_not_called()
|
|
||||||
|
|
||||||
def test_frosted_glass_enabled():
|
|
||||||
with patch("src.gui_2.imgui"):
|
|
||||||
with patch("src.gui_2.BlurPipeline") as mock_blur:
|
|
||||||
app = App()
|
|
||||||
app.ui_frosted_glass_enabled = True
|
|
||||||
app._blur_pipeline = mock_blur
|
|
||||||
mock_blur.return_value = BlurPipeline()
|
|
||||||
mock_blur.prepare_global_blur.return_value = None
|
|
||||||
mock_blur.get_blur_texture.return_value = 123
|
|
||||||
imgui.get_io().display_size = MagicMock(x=800.0, y=600.0)
|
|
||||||
imgui.get_io().display_framebuffer_scale = MagicMock(x=1.0, y=1.0)
|
|
||||||
imgui.get_window_draw_list.return_value = MagicMock()
|
|
||||||
imgui.get_window_pos.return_value = (100, 200)
|
|
||||||
imgui.get_window_size.return_value = (300, 400)
|
|
||||||
imgui.get_color_u32.return_value = 0xFFFFFFFF
|
|
||||||
dl = MagicMock()
|
|
||||||
imgui.get_window_draw_list.return_value = dl
|
|
||||||
app._render_frosted_background((100, 200), (300, 400))
|
|
||||||
mock_blur.get_blur_texture.assert_called_once()
|
|
||||||
assert dl.add_callback_texture_id.called
|
|
||||||
assert dl.add_callback_quadsDrawElements.called
|
|
||||||
imgui.push_texture_id.assert_called()
|
|
||||||
imgui.pop_texture_id.assert_called()
|
|
||||||
gl.glEnable.assert_called()
|
|
||||||
gl.glBlendFunc.assert_called()
|
|
||||||
gl.glBindTexture.assert_called()
|
|
||||||
gl.glBegin.assert_called()
|
|
||||||
gl.glEnd.assert_called()
|
|
||||||
gl.glDisable.assert_called()
|
|
||||||
gl.glUnbindTexture.assert_called()
|
|
||||||
gl.glDeleteTexture.assert_not_called()
|
|
||||||
|
|||||||
89
tests/test_gui_frosted_integration.py
Normal file
89
tests/test_gui_frosted_integration.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import pytest
|
||||||
|
import time
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
|
def test_gui_frosted_background_call():
|
||||||
|
# Mock ShaderManager and OpenGL functions
|
||||||
|
with patch("src.gui_2.ShaderManager") as mock_sm_class, \
|
||||||
|
patch("src.gui_2.gl") as mock_gl, \
|
||||||
|
patch("src.gui_2.imgui") as mock_imgui, \
|
||||||
|
patch("src.gui_2.theme") as mock_theme:
|
||||||
|
|
||||||
|
mock_sm = mock_sm_class.return_value
|
||||||
|
mock_sm.blur_tex = 2
|
||||||
|
mock_theme.get_frosted_glass_enabled.return_value = True
|
||||||
|
|
||||||
|
mock_imgui.get_io().display_size = MagicMock(x=1920, y=1080)
|
||||||
|
|
||||||
|
from src.gui_2 import App
|
||||||
|
with patch.object(App, '__init__', return_value=None):
|
||||||
|
app = App()
|
||||||
|
app.shader_manager = mock_sm
|
||||||
|
|
||||||
|
# Simulate frame
|
||||||
|
app._render_frosted_background(pos=MagicMock(x=10, y=10), size=MagicMock(x=100, y=100))
|
||||||
|
|
||||||
|
# Now it should only call add_image
|
||||||
|
assert mock_imgui.get_window_draw_list().add_image.called
|
||||||
|
# It no longer calls these
|
||||||
|
assert not mock_sm.setup_capture_fbo.called
|
||||||
|
assert not mock_sm.render_blur.called
|
||||||
|
assert not mock_sm.capture_begin.called
|
||||||
|
assert not mock_sm.capture_end.called
|
||||||
|
|
||||||
|
def test_gui_global_blur_call():
|
||||||
|
with patch("src.gui_2.ShaderManager") as mock_sm_class, \
|
||||||
|
patch("src.gui_2.imgui") as mock_imgui, \
|
||||||
|
patch("src.gui_2.theme") as mock_theme, \
|
||||||
|
patch("src.gui_2.bg_shader") as mock_bg:
|
||||||
|
|
||||||
|
mock_sm = mock_sm_class.return_value
|
||||||
|
mock_theme.get_frosted_glass_enabled.return_value = True
|
||||||
|
mock_theme.is_nerv_active.return_value = False
|
||||||
|
mock_imgui.get_io().display_size = MagicMock(x=1920, y=1080)
|
||||||
|
mock_bg.get_bg.return_value = MagicMock(enabled=False)
|
||||||
|
|
||||||
|
from src.gui_2 import App
|
||||||
|
with patch.object(App, '__init__', return_value=None):
|
||||||
|
app = App()
|
||||||
|
app.shader_manager = mock_sm
|
||||||
|
app.shader_uniforms = {
|
||||||
|
'frosted_blur_radius': 10.0,
|
||||||
|
'frosted_tint_intensity': 0.5,
|
||||||
|
'frosted_opacity': 0.8
|
||||||
|
}
|
||||||
|
app.ai_status = "idle"
|
||||||
|
app.ui_crt_filter = False
|
||||||
|
app.controller = MagicMock()
|
||||||
|
app.perf_profiling_enabled = False
|
||||||
|
app.is_viewing_prior_session = False
|
||||||
|
app.config = {}
|
||||||
|
app.project = {}
|
||||||
|
app.show_windows = {}
|
||||||
|
app.ui_auto_scroll_comms = False
|
||||||
|
app._comms_log_dirty = False
|
||||||
|
app._tool_log_dirty = False
|
||||||
|
app._pending_comms_lock = MagicMock()
|
||||||
|
app._pending_comms = []
|
||||||
|
app.ui_focus_agent = None
|
||||||
|
app._last_ui_focus_agent = None
|
||||||
|
app.perf_monitor = MagicMock()
|
||||||
|
app._process_pending_gui_tasks = MagicMock()
|
||||||
|
app._process_pending_history_adds = MagicMock()
|
||||||
|
app._render_track_proposal_modal = MagicMock()
|
||||||
|
app._render_patch_modal = MagicMock()
|
||||||
|
app._render_save_preset_modal = MagicMock()
|
||||||
|
app._render_preset_manager_window = MagicMock()
|
||||||
|
app._render_tool_preset_manager_window = MagicMock()
|
||||||
|
app._render_persona_editor_window = MagicMock()
|
||||||
|
app._last_autosave = time.time()
|
||||||
|
app._autosave_interval = 60
|
||||||
|
app._render_custom_title_bar = MagicMock()
|
||||||
|
app._render_shader_live_editor = MagicMock()
|
||||||
|
|
||||||
|
try:
|
||||||
|
app._gui_func()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert mock_sm.prepare_global_blur.called
|
||||||
@@ -1,172 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from unittest.mock import patch, MagicMock
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
def test_blur_pipeline_import():
|
|
||||||
with patch("src.shader_manager.gl") as mock_gl:
|
|
||||||
from src.shader_manager import BlurPipeline
|
|
||||||
pipeline = BlurPipeline()
|
|
||||||
assert pipeline is not None
|
|
||||||
assert pipeline.scene_fbo is None
|
|
||||||
assert pipeline.blur_fbo_a is None
|
|
||||||
assert pipeline.blur_fbo_b is None
|
|
||||||
assert pipeline.scene_tex is None
|
|
||||||
assert pipeline.blur_tex_a is None
|
|
||||||
assert pipeline.blur_tex_b is None
|
|
||||||
assert pipeline.h_blur_program is None
|
|
||||||
assert pipeline.v_blur_program is None
|
|
||||||
|
|
||||||
def test_blur_pipeline_setup_fbos():
|
|
||||||
with patch("src.shader_manager.gl") as mock_gl:
|
|
||||||
tex_counter = iter([10, 20, 30])
|
|
||||||
fbo_counter = iter([1, 2, 3])
|
|
||||||
mock_gl.glGenTextures.side_effect = lambda n: next(tex_counter)
|
|
||||||
mock_gl.glGenFramebuffers.side_effect = lambda n: next(fbo_counter)
|
|
||||||
from src.shader_manager import BlurPipeline
|
|
||||||
pipeline = BlurPipeline()
|
|
||||||
pipeline.setup_fbos(800, 600)
|
|
||||||
assert mock_gl.glGenFramebuffers.called
|
|
||||||
assert mock_gl.glGenTextures.called
|
|
||||||
assert pipeline.scene_fbo is not None
|
|
||||||
assert pipeline.blur_fbo_a is not None
|
|
||||||
assert pipeline.blur_fbo_b is not None
|
|
||||||
|
|
||||||
def test_blur_pipeline_compile_shaders():
|
|
||||||
with patch("src.shader_manager.gl") as mock_gl:
|
|
||||||
mock_gl.glCreateProgram.return_value = 100
|
|
||||||
mock_gl.glCreateShader.return_value = 200
|
|
||||||
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
|
|
||||||
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
|
|
||||||
from src.shader_manager import BlurPipeline
|
|
||||||
pipeline = BlurPipeline()
|
|
||||||
pipeline.compile_blur_shaders()
|
|
||||||
assert mock_gl.glCreateProgram.called
|
|
||||||
assert pipeline.h_blur_program is not None
|
|
||||||
assert pipeline.v_blur_program is not None
|
|
||||||
|
|
||||||
def test_blur_pipeline_wide_tap_distribution():
|
|
||||||
with patch("src.shader_manager.gl") as mock_gl:
|
|
||||||
mock_gl.glCreateProgram.return_value = 100
|
|
||||||
mock_gl.glCreateShader.return_value = 200
|
|
||||||
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
|
|
||||||
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
|
|
||||||
from src.shader_manager import BlurPipeline
|
|
||||||
pipeline = BlurPipeline()
|
|
||||||
pipeline.compile_blur_shaders()
|
|
||||||
assert mock_gl.glShaderSource.called
|
|
||||||
shader_sources = [call.args[1] for call in mock_gl.glShaderSource.call_args_list]
|
|
||||||
frag_sources = [s for s in shader_sources if 'texture(' in s and 'offset' in s]
|
|
||||||
assert len(frag_sources) >= 2
|
|
||||||
for src in frag_sources:
|
|
||||||
texture_calls = src.count('texture(u_texture')
|
|
||||||
assert texture_calls >= 11, f"Expected at least 11 texture samples for wide tap distribution, got {texture_calls}"
|
|
||||||
|
|
||||||
def test_blur_pipeline_render_deepsea_to_fbo():
|
|
||||||
with patch("src.shader_manager.gl") as mock_gl:
|
|
||||||
tex_counter = iter([10, 20, 30])
|
|
||||||
fbo_counter = iter([1, 2, 3])
|
|
||||||
mock_gl.glGenTextures.side_effect = lambda n: next(tex_counter)
|
|
||||||
mock_gl.glGenFramebuffers.side_effect = lambda n: next(fbo_counter)
|
|
||||||
mock_gl.glCreateProgram.return_value = 300
|
|
||||||
mock_gl.glCreateShader.return_value = 400
|
|
||||||
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
|
|
||||||
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
|
|
||||||
from src.shader_manager import BlurPipeline
|
|
||||||
pipeline = BlurPipeline()
|
|
||||||
pipeline.setup_fbos(800, 600)
|
|
||||||
pipeline.compile_deepsea_shader()
|
|
||||||
pipeline.render_deepsea_to_fbo(800, 600, 0.0)
|
|
||||||
assert mock_gl.glBindFramebuffer.called
|
|
||||||
assert mock_gl.glUseProgram.called
|
|
||||||
assert mock_gl.glDrawArrays.called
|
|
||||||
|
|
||||||
def test_blur_pipeline_deepsea_shader_compilation():
|
|
||||||
with patch("src.shader_manager.gl") as mock_gl:
|
|
||||||
mock_gl.glCreateProgram.return_value = 500
|
|
||||||
mock_gl.glCreateShader.return_value = 600
|
|
||||||
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
|
|
||||||
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
|
|
||||||
from src.shader_manager import BlurPipeline
|
|
||||||
pipeline = BlurPipeline()
|
|
||||||
pipeline.compile_deepsea_shader()
|
|
||||||
assert mock_gl.glCreateProgram.called
|
|
||||||
assert pipeline.deepsea_program is not None
|
|
||||||
|
|
||||||
def test_blur_pipeline_prepare_blur():
|
|
||||||
with patch("src.shader_manager.gl") as mock_gl:
|
|
||||||
mock_gl.glGenFramebuffers.return_value = None
|
|
||||||
mock_gl.glGenTextures.return_value = None
|
|
||||||
from src.shader_manager import BlurPipeline
|
|
||||||
pipeline = BlurPipeline()
|
|
||||||
pipeline.scene_fbo = 1
|
|
||||||
pipeline.scene_tex = 10
|
|
||||||
pipeline.blur_fbo_a = 2
|
|
||||||
pipeline.blur_tex_a = 20
|
|
||||||
pipeline.blur_fbo_b = 3
|
|
||||||
pipeline.blur_tex_b = 30
|
|
||||||
pipeline.h_blur_program = 100
|
|
||||||
pipeline.v_blur_program = 101
|
|
||||||
pipeline.prepare_blur(800, 600, 0.0)
|
|
||||||
assert mock_gl.glBindFramebuffer.called
|
|
||||||
assert mock_gl.glUseProgram.called
|
|
||||||
|
|
||||||
def test_blur_pipeline_prepare_global_blur():
|
|
||||||
with patch("src.shader_manager.gl") as mock_gl:
|
|
||||||
tex_counter = iter([10, 20, 30])
|
|
||||||
fbo_counter = iter([1, 2, 3])
|
|
||||||
mock_gl.glGenTextures.side_effect = lambda n: next(tex_counter)
|
|
||||||
mock_gl.glGenFramebuffers.side_effect = lambda n: next(fbo_counter)
|
|
||||||
mock_gl.glCreateProgram.return_value = 100
|
|
||||||
mock_gl.glCreateShader.return_value = 200
|
|
||||||
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
|
|
||||||
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
|
|
||||||
from src.shader_manager import BlurPipeline
|
|
||||||
pipeline = BlurPipeline()
|
|
||||||
pipeline.setup_fbos(800, 600)
|
|
||||||
pipeline.compile_deepsea_shader()
|
|
||||||
pipeline.compile_blur_shaders()
|
|
||||||
pipeline.prepare_global_blur(800, 600, 0.0)
|
|
||||||
assert mock_gl.glBindFramebuffer.called
|
|
||||||
assert mock_gl.glUseProgram.called
|
|
||||||
assert mock_gl.glViewport.called
|
|
||||||
blur_tex = pipeline.get_blur_texture()
|
|
||||||
assert blur_tex is not None
|
|
||||||
assert blur_tex == 30
|
|
||||||
|
|
||||||
def test_blur_pipeline_high_dpi_scaling():
|
|
||||||
with patch("src.shader_manager.gl") as mock_gl:
|
|
||||||
tex_counter = iter([10, 20, 30])
|
|
||||||
fbo_counter = iter([1, 2, 3])
|
|
||||||
mock_gl.glGenTextures.side_effect = lambda n: next(tex_counter)
|
|
||||||
mock_gl.glGenFramebuffers.side_effect = lambda n: next(fbo_counter)
|
|
||||||
mock_gl.glCreateProgram.return_value = 100
|
|
||||||
mock_gl.glCreateShader.return_value = 200
|
|
||||||
mock_gl.glGetShaderiv.return_value = mock_gl.GL_TRUE
|
|
||||||
mock_gl.glGetProgramiv.return_value = mock_gl.GL_TRUE
|
|
||||||
from src.shader_manager import BlurPipeline
|
|
||||||
pipeline = BlurPipeline()
|
|
||||||
fb_scale = 2.0
|
|
||||||
pipeline.setup_fbos(800, 600, fb_scale)
|
|
||||||
assert pipeline._fb_width == (800 * int(fb_scale)) // 4
|
|
||||||
assert pipeline._fb_height == (600 * int(fb_scale)) // 4
|
|
||||||
assert pipeline._fb_scale == int(fb_scale)
|
|
||||||
|
|
||||||
def test_blur_pipeline_cleanup():
|
|
||||||
with patch("src.shader_manager.gl") as mock_gl:
|
|
||||||
from src.shader_manager import BlurPipeline
|
|
||||||
pipeline = BlurPipeline()
|
|
||||||
pipeline.scene_fbo = 1
|
|
||||||
pipeline.blur_fbo_a = 2
|
|
||||||
pipeline.blur_fbo_b = 3
|
|
||||||
pipeline.scene_tex = 10
|
|
||||||
pipeline.blur_tex_a = 20
|
|
||||||
pipeline.blur_tex_b = 30
|
|
||||||
pipeline.h_blur_program = 100
|
|
||||||
pipeline.v_blur_program = 101
|
|
||||||
pipeline.cleanup()
|
|
||||||
assert mock_gl.glDeleteFramebuffers.called
|
|
||||||
assert mock_gl.glDeleteTextures.called
|
|
||||||
assert mock_gl.glDeleteProgram.called
|
|
||||||
|
|
||||||
def test_shader_manager_initialization_and_compilation():
|
def test_shader_manager_initialization_and_compilation():
|
||||||
# Import inside test to allow patching OpenGL before import if needed
|
# Import inside test to allow patching OpenGL before import if needed
|
||||||
# In this case, we patch the OpenGL.GL functions used by ShaderManager
|
# In this case, we patch the OpenGL.GL functions used by ShaderManager
|
||||||
|
|||||||
@@ -3,33 +3,48 @@ from unittest.mock import MagicMock
|
|||||||
from src import theme_2 as theme
|
from src import theme_2 as theme
|
||||||
|
|
||||||
def test_theme_apply_sets_rounding_and_padding(monkeypatch):
|
def test_theme_apply_sets_rounding_and_padding(monkeypatch):
|
||||||
# Mock imgui
|
# Mock imgui
|
||||||
mock_style = MagicMock()
|
mock_style = MagicMock()
|
||||||
mock_imgui = MagicMock()
|
mock_imgui = MagicMock()
|
||||||
mock_imgui.get_style.return_value = mock_style
|
mock_imgui.get_style.return_value = mock_style
|
||||||
mock_imgui.ImVec2.side_effect = lambda x, y: (x, y)
|
mock_imgui.ImVec2.side_effect = lambda x, y: (x, y)
|
||||||
monkeypatch.setattr(theme, "imgui", mock_imgui)
|
monkeypatch.setattr(theme, "imgui", mock_imgui)
|
||||||
|
|
||||||
# Call apply with the default palette
|
# Call apply with the default palette
|
||||||
theme.apply("ImGui Dark")
|
theme.apply("ImGui Dark")
|
||||||
|
|
||||||
# Verify subtle rounding styles
|
# Verify subtle rounding styles
|
||||||
assert mock_style.window_rounding == 6.0
|
assert mock_style.window_rounding == 6.0
|
||||||
assert mock_style.child_rounding == 4.0
|
assert mock_style.child_rounding == 4.0
|
||||||
assert mock_style.frame_rounding == 4.0
|
assert mock_style.frame_rounding == 4.0
|
||||||
assert mock_style.popup_rounding == 4.0
|
assert mock_style.popup_rounding == 4.0
|
||||||
assert mock_style.scrollbar_rounding == 12.0
|
assert mock_style.scrollbar_rounding == 12.0
|
||||||
assert mock_style.grab_rounding == 4.0
|
assert mock_style.grab_rounding == 4.0
|
||||||
assert mock_style.tab_rounding == 4.0
|
assert mock_style.tab_rounding == 4.0
|
||||||
|
|
||||||
# Verify borders
|
# Verify borders
|
||||||
assert mock_style.window_border_size == 1.0
|
assert mock_style.window_border_size == 1.0
|
||||||
assert mock_style.frame_border_size == 1.0
|
assert mock_style.frame_border_size == 1.0
|
||||||
assert mock_style.popup_border_size == 1.0
|
assert mock_style.popup_border_size == 1.0
|
||||||
|
|
||||||
# Verify padding/spacing
|
# Verify padding/spacing
|
||||||
assert mock_style.window_padding == (8.0, 8.0)
|
assert mock_style.window_padding == (8.0, 8.0)
|
||||||
assert mock_style.frame_padding == (8.0, 4.0)
|
assert mock_style.frame_padding == (8.0, 4.0)
|
||||||
assert mock_style.item_spacing == (8.0, 4.0)
|
assert mock_style.item_spacing == (8.0, 4.0)
|
||||||
assert mock_style.item_inner_spacing == (4.0, 4.0)
|
assert mock_style.item_inner_spacing == (4.0, 4.0)
|
||||||
assert mock_style.scrollbar_size == 14.0
|
assert mock_style.scrollbar_size == 14.0
|
||||||
|
|
||||||
|
def test_frosted_glass_enabled_toggle():
|
||||||
|
theme.set_frosted_glass_enabled(True)
|
||||||
|
assert theme.get_frosted_glass_enabled() is True
|
||||||
|
theme.set_frosted_glass_enabled(False)
|
||||||
|
assert theme.get_frosted_glass_enabled() is False
|
||||||
|
|
||||||
|
def test_theme_config_frosted_glass():
|
||||||
|
config = {"theme": {"frosted_glass_enabled": True}}
|
||||||
|
theme.load_from_config(config)
|
||||||
|
assert theme.get_frosted_glass_enabled() is True
|
||||||
|
|
||||||
|
new_config = {}
|
||||||
|
theme.save_to_config(new_config)
|
||||||
|
assert new_config["theme"]["frosted_glass_enabled"] is True
|
||||||
|
|||||||
Reference in New Issue
Block a user