Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cbccbb7229 | |||
| 710e95055e | |||
| e635c2925d | |||
| 9facecb7a5 |
@@ -0,0 +1,5 @@
|
|||||||
|
# Track nerv_ui_theme_20260309 Context
|
||||||
|
|
||||||
|
- [Specification](./spec.md)
|
||||||
|
- [Implementation Plan](./plan.md)
|
||||||
|
- [Metadata](./metadata.json)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"description": "Implement a NERV UI theme for ImGui/Dear PyGui, inspired by technical/military consoles, with CRT effects and a black-void aesthetic.",
|
||||||
|
"track_id": "nerv_ui_theme_20260309",
|
||||||
|
"type": "feature",
|
||||||
|
"created_at": "2026-03-09T00:35:48Z",
|
||||||
|
"status": "new",
|
||||||
|
"updated_at": "2026-03-09T00:35:48Z"
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Implementation Plan: NERV UI Theme
|
||||||
|
|
||||||
|
## Phase 1: Research & Theme Infrastructure [checkpoint: 4b78e77]
|
||||||
|
- [x] Task: Research existing theme implementation in src/theme.py and src/theme_2.py. 3fa4f64
|
||||||
|
- [x] Task: Create a new src/theme_nerv.py to house the NERV color constants and theme application logic. 3fa4f64
|
||||||
|
- [x] Task: Conductor - User Manual Verification 'Phase 1: Research & Theme Infrastructure' (Protocol in workflow.md) 4b78e77
|
||||||
|
|
||||||
|
## Phase 2: Base NERV Theme Implementation (Colors & Geometry) [checkpoint: 9c38ea7]
|
||||||
|
- [x] Task: Implement the "Black Void" and "Phosphor" color palette in src/theme_nerv.py. 3fa4f64
|
||||||
|
- [x] Task: Implement "Hard Edges" by setting all rounding parameters to 0.0 in the NERV theme. 3fa4f64
|
||||||
|
- [x] Task: Write unit tests to verify that the NERV theme correctly applies colors and geometry settings. de0d9f3
|
||||||
|
- [x] Task: Conductor - User Manual Verification 'Phase 2: Base NERV Theme Implementation' (Protocol in workflow.md) 9c38ea7
|
||||||
|
|
||||||
|
## Phase 3: Visual Effects (Scanlines & Status Flickering) [checkpoint: ceb0c7d]
|
||||||
|
- [x] Task: Research how to implement a scanline overlay in ImGui (e.g., using a full-screen transparent texture or a custom draw list). 05a2b8e
|
||||||
|
- [x] Task: Implement the subtle scanline overlay (6% opacity). 05a2b8e
|
||||||
|
- [x] Task: Implement "Status Flickering" logic for active system indicators (e.g., a periodic alpha modification for specific text elements). 05a2b8e
|
||||||
|
- [x] Task: Write tests to verify the visual effect triggers (e.g., checking if the scanline overlay is rendered). 4f4fa10
|
||||||
|
- [x] Task: Conductor - User Manual Verification 'Phase 3: Visual Effects' (Protocol in workflow.md) ceb0c7d
|
||||||
|
|
||||||
|
## Phase 4: Alert Pulsing & Error States [checkpoint: d9495f6]
|
||||||
|
- [x] Task: Implement "Alert Pulsing" logic that can be triggered by application error events. d9495f6
|
||||||
|
- [x] Task: Integrate Alert Pulsing with the NERV theme (shifting borders/background to Alert Red). d9495f6
|
||||||
|
- [x] Task: Write tests to verify that an error state triggers the pulsing effect in the NERV theme. d9495f6
|
||||||
|
- [x] Task: Conductor - User Manual Verification 'Phase 4: Alert Pulsing & Error States' (Protocol in workflow.md) d9495f6
|
||||||
|
|
||||||
|
## Phase 5: Integration & Theme Selector [checkpoint: afcb1bf]
|
||||||
|
- [x] Task: Add "NERV" to the theme selection dropdown in src/gui_2.py. afcb1bf
|
||||||
|
- [x] Task: Ensure that switching to the NERV theme correctly initializes all visual effects (scanlines, etc.). afcb1bf
|
||||||
|
- [x] Task: Final UX verification and performance check of the NERV theme. afcb1bf
|
||||||
|
- [x] Task: Conductor - User Manual Verification 'Phase 5: Integration & Theme Selector' (Protocol in workflow.md) afcb1bf
|
||||||
|
|
||||||
|
## Phase 6: NERV Theme Refinement (Contrast & Readability) [checkpoint: 9facecb]
|
||||||
|
- [x] Task: Fix text readability by ensuring high-contrast text on bright backgrounds (e.g., black text on orange title bars). 9facecb
|
||||||
|
- [x] Task: Adjust the NERV palette to use Data Green or Steel for standard text, reserving Orange for accents and backgrounds. 9facecb
|
||||||
|
- [x] Task: Update gui_2.py to push/pop style colors for headers if necessary to maintain readability. 9facecb
|
||||||
|
- [x] Task: Conductor - User Manual Verification 'Phase 6: NERV Theme Refinement' (Protocol in workflow.md) 9facecb
|
||||||
|
|
||||||
|
## Phase 7: CRT Filter Implementation [checkpoint: e635c29]
|
||||||
|
- [x] Task: Research and implement a more sophisticated "CRT Filter" beyond simple scanlines (e.g., adding a vignette, noise, or subtle color aberration). e635c29
|
||||||
|
- [x] Task: Implement a "CRT Filter" toggle in the theme settings. e635c29
|
||||||
|
- [x] Task: Integrate the new CRT filter into the gui_2.py rendering loop. e635c29
|
||||||
|
- [x] Task: Conductor - User Manual Verification 'Phase 7: CRT Filter Implementation' (Protocol in workflow.md) e635c29
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Specification: NERV UI Theme Integration
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This track aims to implement a new "NERV" visual theme for the manual_slop application, inspired by the aesthetic of technical/military consoles (e.g., Evangelion's NERV UI). The theme will be added as a selectable option within the application, allowing users to switch between the existing theme and the new NERV style without altering the core user experience or layout.
|
||||||
|
|
||||||
|
## Functional Requirements
|
||||||
|
- **Theme Selection:** Integrate a "NERV" theme option into the existing UI (e.g., in the configuration or theme settings).
|
||||||
|
- **Color Palette:** Implement the "Black Void" aesthetic using absolute black (#000000) for the background and CRT-inspired phosphor colors:
|
||||||
|
- **NERV Orange (#FF9830):** Primary accents, headers, active borders.
|
||||||
|
- **Data Green (#50FF50):** Terminal output, "Nominal" status, standard data.
|
||||||
|
- **Wire Cyan (#20F0FF):** Structural separators, inactive borders.
|
||||||
|
- **Alert Red (#FF4840):** Error states, critical alerts.
|
||||||
|
- **Steel (#E0E0D8):** Secondary text, timestamps.
|
||||||
|
- **Hard Edges:** Configure all UI elements (windows, frames, buttons) to have zero rounded corners (Rounding = 0.0).
|
||||||
|
- **Typography:** Utilize a monospace font (e.g., IBM Plex Mono or the project's current monospace font) for all text to maintain a technical look.
|
||||||
|
- **Visual Effects:**
|
||||||
|
- **Scanline Overlay:** Implement a subtle CRT-style scanline overlay (approx. 6% opacity).
|
||||||
|
- **Status Flickering:** Add subtle flickering effects to active system status indicators.
|
||||||
|
- **Alert Pulsing:** Implement red background or border pulsing during error or critical system states.
|
||||||
|
|
||||||
|
## Non-Functional Requirements
|
||||||
|
- **Performance:** Ensure the scanline overlay and status flickering do not significantly degrade UI responsiveness or increase CPU usage.
|
||||||
|
- **Maintainability:** The theme should be implemented in a way that is consistent with the existing theme.py or theme_2.py architecture.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
- [ ] Users can select "NERV" from the theme selector.
|
||||||
|
- [ ] The background is solid black (#000000).
|
||||||
|
- [ ] All borders and buttons have zero rounded corners.
|
||||||
|
- [ ] The NERV color palette is correctly applied to all UI elements.
|
||||||
|
- [ ] The scanline overlay is visible and subtle.
|
||||||
|
- [ ] Active status indicators exhibit the "Status Flickering" effect.
|
||||||
|
- [ ] Errors trigger the "Alert Pulsing" effect.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
- **Bilingual Labels:** Japanese sub-labels will not be implemented.
|
||||||
|
- **Layout Changes:** No radical changes to window positioning or spacing.
|
||||||
|
- **New Features:** This track is purely visual and does not add new application functionality.
|
||||||
+3
-5
@@ -48,10 +48,6 @@ This file tracks all major tracks for the project. Each track has its own detail
|
|||||||
*Link: [./tracks/markdown_highlighting_20260308/](./tracks/markdown_highlighting_20260308/)*
|
*Link: [./tracks/markdown_highlighting_20260308/](./tracks/markdown_highlighting_20260308/)*
|
||||||
*Goal: Add rich text rendering with GFM support and syntax highlighting for PowerShell, Python, and JSON/TOML in read-only message and log views.*
|
*Goal: Add rich text rendering with GFM support and syntax highlighting for PowerShell, Python, and JSON/TOML in read-only message and log views.*
|
||||||
|
|
||||||
5. [x] **Track: NERV UI Theme Integration**
|
|
||||||
*Link: [./tracks/nerv_ui_theme_20260309/](./tracks/nerv_ui_theme_20260309/)*
|
|
||||||
*Goal: Implement a NERV UI theme for ImGui/Dear PyGui, inspired by technical/military consoles, with CRT effects and a black-void aesthetic.*
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### C/C++ Language Support
|
### C/C++ Language Support
|
||||||
@@ -118,10 +114,12 @@ This file tracks all major tracks for the project. Each track has its own detail
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Completed / Archived
|
### Completed / Archived
|
||||||
|
|
||||||
|
- [x] **Track: NERV UI Theme Integration** (Archived 2026-03-09)
|
||||||
### Phase 3: Early Tracks (Archived 2026-03-08)
|
### Phase 3: Early Tracks (Archived 2026-03-08)
|
||||||
|
|
||||||
|
|
||||||
- [x] **Track: True Parallel Worker Execution (The DAG Realization)**
|
- [x] **Track: True Parallel Worker Execution (The DAG Realization)**
|
||||||
- [x] **Track: Deep AST-Driven Context Pruning (RAG for Code)**
|
- [x] **Track: Deep AST-Driven Context Pruning (RAG for Code)**
|
||||||
- [x] **Track: Visual DAG & Interactive Ticket Editing**
|
- [x] **Track: Visual DAG & Interactive Ticket Editing**
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
# Implementation Plan: NERV UI Theme
|
|
||||||
|
|
||||||
## Phase 1: Research & Theme Infrastructure [checkpoint: 4b78e77]
|
|
||||||
- [x] Task: Research existing theme implementation in src/theme.py and src/theme_2.py. 3fa4f64
|
|
||||||
- [x] Task: Create a new src/theme_nerv.py to house the NERV color constants and theme application logic. 3fa4f64
|
|
||||||
- [x] Task: Conductor - User Manual Verification 'Phase 1: Research & Theme Infrastructure' (Protocol in workflow.md) 4b78e77
|
|
||||||
|
|
||||||
## Phase 2: Base NERV Theme Implementation (Colors & Geometry) [checkpoint: 9c38ea7]
|
|
||||||
- [x] Task: Implement the "Black Void" and "Phosphor" color palette in src/theme_nerv.py. 3fa4f64
|
|
||||||
- [x] Task: Implement "Hard Edges" by setting all rounding parameters to 0.0 in the NERV theme. 3fa4f64
|
|
||||||
- [x] Task: Write unit tests to verify that the NERV theme correctly applies colors and geometry settings. de0d9f3
|
|
||||||
- [x] Task: Conductor - User Manual Verification 'Phase 2: Base NERV Theme Implementation' (Protocol in workflow.md) 9c38ea7
|
|
||||||
|
|
||||||
## Phase 3: Visual Effects (Scanlines & Status Flickering) [checkpoint: 4f4fa10]
|
|
||||||
- [x] Task: Research how to implement a scanline overlay in ImGui (e.g., using a full-screen transparent texture or a custom draw list). 05a2b8e
|
|
||||||
- [x] Task: Implement the subtle scanline overlay (6% opacity). 05a2b8e
|
|
||||||
- [x] Task: Implement "Status Flickering" logic for active system indicators (e.g., a periodic alpha modification for specific text elements). 05a2b8e
|
|
||||||
- [x] Task: Write tests to verify the visual effect triggers (e.g., checking if the scanline overlay is rendered). 4f4fa10
|
|
||||||
- [x] Task: Conductor - User Manual Verification 'Phase 3: Visual Effects' (Protocol in workflow.md) 4f4fa10
|
|
||||||
|
|
||||||
## Phase 4: Alert Pulsing & Error States
|
|
||||||
- [ ] Task: Implement "Alert Pulsing" logic that can be triggered by application error events.
|
|
||||||
- [ ] Task: Integrate Alert Pulsing with the NERV theme (shifting borders/background to Alert Red).
|
|
||||||
- [ ] Task: Write tests to verify that an error state triggers the pulsing effect in the NERV theme.
|
|
||||||
- [ ] Task: Conductor - User Manual Verification 'Phase 4: Alert Pulsing & Error States' (Protocol in workflow.md)
|
|
||||||
|
|
||||||
## Phase 5: Integration & Theme Selector
|
|
||||||
- [ ] Task: Add "NERV" to the theme selection dropdown in src/gui_2.py.
|
|
||||||
- [ ] Task: Ensure that switching to the NERV theme correctly initializes all visual effects (scanlines, etc.).
|
|
||||||
- [ ] Task: Final UX verification and performance check of the NERV theme.
|
|
||||||
- [ ] Task: Conductor - User Manual Verification 'Phase 5: Integration & Theme Selector' (Protocol in workflow.md)
|
|
||||||
+76
-17
@@ -131,7 +131,8 @@ class App:
|
|||||||
self._token_stats: dict[str, Any] = {}
|
self._token_stats: dict[str, Any] = {}
|
||||||
self._token_stats_dirty: bool = True
|
self._token_stats_dirty: bool = True
|
||||||
self.perf_history: dict[str, list] = {"frame_time": [0.0] * 100, "fps": [0.0] * 100}
|
self.perf_history: dict[str, list] = {"frame_time": [0.0] * 100, "fps": [0.0] * 100}
|
||||||
self._nerv_scanlines = theme_fx.ScanlineOverlay()
|
self._nerv_crt = theme_fx.CRTFilter()
|
||||||
|
self.ui_crt_filter = True
|
||||||
self._nerv_alert = theme_fx.AlertPulsing()
|
self._nerv_alert = theme_fx.AlertPulsing()
|
||||||
self._nerv_flicker = theme_fx.StatusFlicker()
|
self._nerv_flicker = theme_fx.StatusFlicker()
|
||||||
|
|
||||||
@@ -222,6 +223,9 @@ class App:
|
|||||||
is_md = label in ("message", "text", "content")
|
is_md = label in ("message", "text", "content")
|
||||||
ctx_id = f"heavy_{label}_{id_suffix}"
|
ctx_id = f"heavy_{label}_{id_suffix}"
|
||||||
|
|
||||||
|
is_nerv = theme.is_nerv_active()
|
||||||
|
if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))
|
||||||
|
|
||||||
if len(content) > COMMS_CLAMP_CHARS:
|
if len(content) > COMMS_CLAMP_CHARS:
|
||||||
imgui.begin_child(f"heavy_text_child_{label}_{id_suffix}", imgui.ImVec2(0, 80), True)
|
imgui.begin_child(f"heavy_text_child_{label}_{id_suffix}", imgui.ImVec2(0, 80), True)
|
||||||
if is_md:
|
if is_md:
|
||||||
@@ -234,6 +238,8 @@ class App:
|
|||||||
markdown_helper.render(content, context_id=ctx_id)
|
markdown_helper.render(content, context_id=ctx_id)
|
||||||
else:
|
else:
|
||||||
markdown_helper.render_code(content, context_id=ctx_id)
|
markdown_helper.render_code(content, context_id=ctx_id)
|
||||||
|
|
||||||
|
if is_nerv: imgui.pop_style_color()
|
||||||
# ---------------------------------------------------------------- gui
|
# ---------------------------------------------------------------- gui
|
||||||
|
|
||||||
|
|
||||||
@@ -292,6 +298,7 @@ class App:
|
|||||||
imgui.end_menu()
|
imgui.end_menu()
|
||||||
|
|
||||||
def _gui_func(self) -> None:
|
def _gui_func(self) -> None:
|
||||||
|
pushed_prior_tint = False
|
||||||
# Render background shader
|
# Render background shader
|
||||||
bg = bg_shader.get_bg()
|
bg = bg_shader.get_bg()
|
||||||
if bg.enabled:
|
if bg.enabled:
|
||||||
@@ -302,9 +309,8 @@ class App:
|
|||||||
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_scanlines.render(ws.x, ws.y)
|
self._nerv_crt.enabled = self.ui_crt_filter
|
||||||
|
self._nerv_crt.render(ws.x, ws.y)
|
||||||
pushed_prior_tint = False
|
|
||||||
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))
|
||||||
@@ -1367,7 +1373,10 @@ def hello():
|
|||||||
if is_thinking:
|
if is_thinking:
|
||||||
val = math.sin(time.time() * 10 * math.pi)
|
val = math.sin(time.time() * 10 * math.pi)
|
||||||
alpha = 1.0 if val > 0 else 0.0
|
alpha = 1.0 if val > 0 else 0.0
|
||||||
imgui.text_colored(imgui.ImVec4(1.0, 0.39, 0.39, alpha), "THINKING...")
|
c = vec4(255, 100, 100, alpha)
|
||||||
|
if theme.is_nerv_active():
|
||||||
|
c = vec4(255, 50, 50, alpha) # More vibrant for NERV
|
||||||
|
imgui.text_colored(c, "THINKING...")
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
# Prior session viewing mode
|
# Prior session viewing mode
|
||||||
if self.is_viewing_prior_session:
|
if self.is_viewing_prior_session:
|
||||||
@@ -1405,7 +1414,10 @@ def hello():
|
|||||||
if len(content) > 80: preview += "..."
|
if len(content) > 80: preview += "..."
|
||||||
imgui.text_colored(vec4(180, 180, 180), preview)
|
imgui.text_colored(vec4(180, 180, 180), preview)
|
||||||
else:
|
else:
|
||||||
|
is_nerv = theme.is_nerv_active()
|
||||||
|
if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))
|
||||||
markdown_helper.render(content, context_id=f'prior_disc_{idx}')
|
markdown_helper.render(content, context_id=f'prior_disc_{idx}')
|
||||||
|
if is_nerv: imgui.pop_style_color()
|
||||||
|
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
imgui.pop_id()
|
imgui.pop_id()
|
||||||
@@ -1563,15 +1575,21 @@ def hello():
|
|||||||
content = entry["content"]
|
content = entry["content"]
|
||||||
pattern = re.compile(r"\[Definition: (.*?) from (.*?) \(line (\d+)\)\](\s+```[\s\S]*?```)?")
|
pattern = re.compile(r"\[Definition: (.*?) from (.*?) \(line (\d+)\)\](\s+```[\s\S]*?```)?")
|
||||||
matches = list(pattern.finditer(content))
|
matches = list(pattern.finditer(content))
|
||||||
|
is_nerv = theme.is_nerv_active()
|
||||||
if not matches:
|
if not matches:
|
||||||
|
if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))
|
||||||
markdown_helper.render(content, context_id=f'disc_{i}')
|
markdown_helper.render(content, context_id=f'disc_{i}')
|
||||||
|
if is_nerv: imgui.pop_style_color()
|
||||||
else:
|
else:
|
||||||
imgui.begin_child(f"read_content_{i}", imgui.ImVec2(0, 150), True)
|
imgui.begin_child(f"read_content_{i}", imgui.ImVec2(0, 150), True)
|
||||||
if self.ui_word_wrap: imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
|
if self.ui_word_wrap: imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
|
||||||
last_idx = 0
|
last_idx = 0
|
||||||
for m_idx, match in enumerate(matches):
|
for m_idx, match in enumerate(matches):
|
||||||
before = content[last_idx:match.start()]
|
before = content[last_idx:match.start()]
|
||||||
if before: markdown_helper.render(before, context_id=f'disc_{i}_b_{m_idx}')
|
if before:
|
||||||
|
if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))
|
||||||
|
markdown_helper.render(before, context_id=f'disc_{i}_b_{m_idx}')
|
||||||
|
if is_nerv: imgui.pop_style_color()
|
||||||
header_text = match.group(0).split("\n")[0].strip()
|
header_text = match.group(0).split("\n")[0].strip()
|
||||||
path = match.group(2)
|
path = match.group(2)
|
||||||
code_block = match.group(4)
|
code_block = match.group(4)
|
||||||
@@ -1584,10 +1602,15 @@ def hello():
|
|||||||
self.show_text_viewer = True
|
self.show_text_viewer = True
|
||||||
if code_block:
|
if code_block:
|
||||||
# Render code block with highlighting
|
# Render code block with highlighting
|
||||||
|
if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))
|
||||||
markdown_helper.render(code_block, context_id=f'disc_{i}_c_{m_idx}')
|
markdown_helper.render(code_block, context_id=f'disc_{i}_c_{m_idx}')
|
||||||
|
if is_nerv: imgui.pop_style_color()
|
||||||
last_idx = match.end()
|
last_idx = match.end()
|
||||||
after = content[last_idx:]
|
after = content[last_idx:]
|
||||||
if after: markdown_helper.render(after, context_id=f'disc_{i}_a')
|
if after:
|
||||||
|
if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))
|
||||||
|
markdown_helper.render(after, context_id=f'disc_{i}_a')
|
||||||
|
if is_nerv: imgui.pop_style_color()
|
||||||
if self.ui_word_wrap: imgui.pop_text_wrap_pos()
|
if self.ui_word_wrap: imgui.pop_text_wrap_pos()
|
||||||
imgui.end_child()
|
imgui.end_child()
|
||||||
else:
|
else:
|
||||||
@@ -1850,7 +1873,10 @@ def hello():
|
|||||||
if is_live:
|
if is_live:
|
||||||
val = math.sin(time.time() * 10 * math.pi)
|
val = math.sin(time.time() * 10 * math.pi)
|
||||||
alpha = 1.0 if val > 0 else 0.0
|
alpha = 1.0 if val > 0 else 0.0
|
||||||
imgui.text_colored(imgui.ImVec4(0.39, 1.0, 0.39, alpha), "LIVE")
|
c = imgui.ImVec4(0.39, 1.0, 0.39, alpha)
|
||||||
|
if theme.is_nerv_active():
|
||||||
|
c = vec4(80, 255, 80, alpha) # DATA_GREEN for LIVE in NERV
|
||||||
|
imgui.text_colored(c, "LIVE")
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
ch, self.ui_ai_input = imgui.input_text_multiline("##ai_in", self.ui_ai_input, imgui.ImVec2(-1, -40))
|
ch, self.ui_ai_input = imgui.input_text_multiline("##ai_in", self.ui_ai_input, imgui.ImVec2(-1, -40))
|
||||||
# Keyboard shortcuts
|
# Keyboard shortcuts
|
||||||
@@ -1905,7 +1931,10 @@ def hello():
|
|||||||
# --- Always Render Content ---
|
# --- Always Render Content ---
|
||||||
|
|
||||||
imgui.begin_child("response_scroll_area", imgui.ImVec2(0, -40), True)
|
imgui.begin_child("response_scroll_area", imgui.ImVec2(0, -40), True)
|
||||||
|
is_nerv = theme.is_nerv_active()
|
||||||
|
if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))
|
||||||
markdown_helper.render(self.ai_response, context_id="response")
|
markdown_helper.render(self.ai_response, context_id="response")
|
||||||
|
if is_nerv: imgui.pop_style_color()
|
||||||
imgui.end_child()
|
imgui.end_child()
|
||||||
|
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
@@ -1918,7 +1947,10 @@ def hello():
|
|||||||
|
|
||||||
def _render_comms_history_panel(self) -> None:
|
def _render_comms_history_panel(self) -> None:
|
||||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_comms_history_panel")
|
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_comms_history_panel")
|
||||||
imgui.text_colored(vec4(200, 220, 160), f"Status: {self.ai_status}")
|
st_col = vec4(200, 220, 160)
|
||||||
|
if theme.is_nerv_active():
|
||||||
|
st_col = vec4(80, 255, 80) # DATA_GREEN for status in NERV
|
||||||
|
imgui.text_colored(st_col, f"Status: {self.ai_status}")
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
if imgui.button("Clear##comms"):
|
if imgui.button("Clear##comms"):
|
||||||
ai_client.clear_comms_log()
|
ai_client.clear_comms_log()
|
||||||
@@ -2300,8 +2332,11 @@ def hello():
|
|||||||
|
|
||||||
def _render_mma_dashboard(self) -> None:
|
def _render_mma_dashboard(self) -> None:
|
||||||
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")
|
||||||
|
is_nerv = theme.is_nerv_active()
|
||||||
if self.is_viewing_prior_session:
|
if self.is_viewing_prior_session:
|
||||||
imgui.text_colored(vec4(255, 200, 100), "HISTORICAL VIEW - READ ONLY")
|
c = vec4(255, 200, 100)
|
||||||
|
if is_nerv: c = vec4(255, 152, 48) # NERV_ORANGE
|
||||||
|
imgui.text_colored(c, "HISTORICAL VIEW - READ ONLY")
|
||||||
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")
|
||||||
return
|
return
|
||||||
# Task 5.3: Dense Summary Line
|
# Task 5.3: Dense Summary Line
|
||||||
@@ -2324,7 +2359,9 @@ def hello():
|
|||||||
imgui.text(" | Status:")
|
imgui.text(" | Status:")
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
if self.mma_status == "paused":
|
if self.mma_status == "paused":
|
||||||
imgui.text_colored(imgui.ImVec4(1, 0.5, 0, 1), "PIPELINE PAUSED")
|
c = imgui.ImVec4(1, 0.5, 0, 1)
|
||||||
|
if is_nerv: c = vec4(255, 152, 48)
|
||||||
|
imgui.text_colored(c, "PIPELINE PAUSED")
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
status_col = imgui.ImVec4(1, 1, 1, 1)
|
status_col = imgui.ImVec4(1, 1, 1, 1)
|
||||||
if self.mma_status == "idle": status_col = imgui.ImVec4(0.7, 0.7, 0.7, 1)
|
if self.mma_status == "idle": status_col = imgui.ImVec4(0.7, 0.7, 0.7, 1)
|
||||||
@@ -2332,6 +2369,11 @@ def hello():
|
|||||||
elif self.mma_status == "done": status_col = imgui.ImVec4(0, 1, 0, 1)
|
elif self.mma_status == "done": status_col = imgui.ImVec4(0, 1, 0, 1)
|
||||||
elif self.mma_status == "error": status_col = imgui.ImVec4(1, 0, 0, 1)
|
elif self.mma_status == "error": status_col = imgui.ImVec4(1, 0, 0, 1)
|
||||||
elif self.mma_status == "paused": status_col = imgui.ImVec4(1, 0.5, 0, 1)
|
elif self.mma_status == "paused": status_col = imgui.ImVec4(1, 0.5, 0, 1)
|
||||||
|
|
||||||
|
if is_nerv:
|
||||||
|
if self.mma_status == "running": status_col = vec4(80, 255, 80) # DATA_GREEN
|
||||||
|
elif self.mma_status == "error": status_col = vec4(255, 72, 64) # ALERT_RED
|
||||||
|
|
||||||
imgui.text_colored(status_col, self.mma_status.upper())
|
imgui.text_colored(status_col, self.mma_status.upper())
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
imgui.text(" | Cost:")
|
imgui.text(" | Cost:")
|
||||||
@@ -2412,7 +2454,9 @@ def hello():
|
|||||||
if status == "new":
|
if status == "new":
|
||||||
imgui.text_colored(imgui.ImVec4(0.7, 0.7, 0.7, 1.0), "NEW")
|
imgui.text_colored(imgui.ImVec4(0.7, 0.7, 0.7, 1.0), "NEW")
|
||||||
elif status == "active":
|
elif status == "active":
|
||||||
imgui.text_colored(imgui.ImVec4(1.0, 1.0, 0.0, 1.0), "ACTIVE")
|
c = imgui.ImVec4(1.0, 1.0, 0.0, 1.0)
|
||||||
|
if is_nerv: c = vec4(80, 255, 80)
|
||||||
|
imgui.text_colored(c, "ACTIVE")
|
||||||
elif status == "done":
|
elif status == "done":
|
||||||
imgui.text_colored(imgui.ImVec4(0.0, 1.0, 0.0, 1.0), "DONE")
|
imgui.text_colored(imgui.ImVec4(0.0, 1.0, 0.0, 1.0), "DONE")
|
||||||
elif status == "blocked":
|
elif status == "blocked":
|
||||||
@@ -2478,7 +2522,9 @@ def hello():
|
|||||||
if any_pending:
|
if any_pending:
|
||||||
alpha = abs(math.sin(time.time() * 5))
|
alpha = abs(math.sin(time.time() * 5))
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
imgui.text_colored(imgui.ImVec4(1.0, 0.3, 0.3, alpha), " APPROVAL PENDING")
|
c = imgui.ImVec4(1.0, 0.3, 0.3, alpha)
|
||||||
|
if is_nerv: c = vec4(255, 72, 64, alpha) # ALERT_RED
|
||||||
|
imgui.text_colored(c, " APPROVAL PENDING")
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
if imgui.button("Go to Approval"):
|
if imgui.button("Go to Approval"):
|
||||||
pass # scroll/focus handled by existing dialog rendering
|
pass # scroll/focus handled by existing dialog rendering
|
||||||
@@ -2850,10 +2896,6 @@ def hello():
|
|||||||
ch_ct, ctrans = imgui.slider_float("##ctrans", theme.get_child_transparency(), 0.1, 1.0, "%.2f")
|
ch_ct, ctrans = imgui.slider_float("##ctrans", theme.get_child_transparency(), 0.1, 1.0, "%.2f")
|
||||||
if ch_ct:
|
if ch_ct:
|
||||||
theme.set_child_transparency(ctrans)
|
theme.set_child_transparency(ctrans)
|
||||||
self._flush_to_config()
|
|
||||||
models.save_config(self.config)
|
|
||||||
|
|
||||||
imgui.separator()
|
|
||||||
bg = bg_shader.get_bg()
|
bg = bg_shader.get_bg()
|
||||||
ch_bg, bg.enabled = imgui.checkbox("Animated Background Shader", bg.enabled)
|
ch_bg, bg.enabled = imgui.checkbox("Animated Background Shader", bg.enabled)
|
||||||
if ch_bg:
|
if ch_bg:
|
||||||
@@ -2861,6 +2903,15 @@ def hello():
|
|||||||
gui_cfg["bg_shader_enabled"] = bg.enabled
|
gui_cfg["bg_shader_enabled"] = bg.enabled
|
||||||
self._flush_to_config()
|
self._flush_to_config()
|
||||||
models.save_config(self.config)
|
models.save_config(self.config)
|
||||||
|
|
||||||
|
ch_crt, self.ui_crt_filter = imgui.checkbox("CRT Filter", self.ui_crt_filter)
|
||||||
|
if ch_crt:
|
||||||
|
gui_cfg = self.config.setdefault("gui", {})
|
||||||
|
gui_cfg["crt_filter_enabled"] = self.ui_crt_filter
|
||||||
|
self._flush_to_config()
|
||||||
|
models.save_config(self.config)
|
||||||
|
self._flush_to_config()
|
||||||
|
models.save_config(self.config)
|
||||||
imgui.end()
|
imgui.end()
|
||||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_theme_panel")
|
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_theme_panel")
|
||||||
|
|
||||||
@@ -2878,6 +2929,14 @@ def hello():
|
|||||||
font_path, font_size = theme.get_font_loading_params()
|
font_path, font_size = theme.get_font_loading_params()
|
||||||
|
|
||||||
if font_path:
|
if font_path:
|
||||||
|
p = Path(font_path)
|
||||||
|
if p.is_absolute():
|
||||||
|
try:
|
||||||
|
if p.is_relative_to(assets_dir):
|
||||||
|
font_path = str(p.relative_to(assets_dir)).replace("\\", "/")
|
||||||
|
except (ValueError, AttributeError):
|
||||||
|
pass # Fallback to original font_path if relative_to fails or on old Python
|
||||||
|
|
||||||
# Just try loading it directly; hello_imgui will look in the assets folder
|
# Just try loading it directly; hello_imgui will look in the assets folder
|
||||||
try:
|
try:
|
||||||
self.main_font = hello_imgui.load_font_ttf_with_font_awesome_icons(font_path, font_size, config)
|
self.main_font = hello_imgui.load_font_ttf_with_font_awesome_icons(font_path, font_size, config)
|
||||||
|
|||||||
+9
-9
@@ -12,7 +12,7 @@ STEEL = _c(224, 224, 216)
|
|||||||
BLACK = _c(0, 0, 0)
|
BLACK = _c(0, 0, 0)
|
||||||
|
|
||||||
NERV_PALETTE = {
|
NERV_PALETTE = {
|
||||||
imgui.Col_.text: NERV_ORANGE,
|
imgui.Col_.text: STEEL,
|
||||||
imgui.Col_.window_bg: BLACK,
|
imgui.Col_.window_bg: BLACK,
|
||||||
imgui.Col_.child_bg: BLACK,
|
imgui.Col_.child_bg: BLACK,
|
||||||
imgui.Col_.popup_bg: BLACK,
|
imgui.Col_.popup_bg: BLACK,
|
||||||
@@ -22,7 +22,7 @@ NERV_PALETTE = {
|
|||||||
imgui.Col_.frame_bg_hovered: _c(255, 152, 48, 40),
|
imgui.Col_.frame_bg_hovered: _c(255, 152, 48, 40),
|
||||||
imgui.Col_.frame_bg_active: _c(255, 152, 48, 80),
|
imgui.Col_.frame_bg_active: _c(255, 152, 48, 80),
|
||||||
imgui.Col_.title_bg: BLACK,
|
imgui.Col_.title_bg: BLACK,
|
||||||
imgui.Col_.title_bg_active: NERV_ORANGE,
|
imgui.Col_.title_bg_active: BLACK,
|
||||||
imgui.Col_.title_bg_collapsed: BLACK,
|
imgui.Col_.title_bg_collapsed: BLACK,
|
||||||
imgui.Col_.menu_bar_bg: BLACK,
|
imgui.Col_.menu_bar_bg: BLACK,
|
||||||
imgui.Col_.scrollbar_bg: BLACK,
|
imgui.Col_.scrollbar_bg: BLACK,
|
||||||
@@ -33,11 +33,11 @@ NERV_PALETTE = {
|
|||||||
imgui.Col_.slider_grab: WIRE_CYAN,
|
imgui.Col_.slider_grab: WIRE_CYAN,
|
||||||
imgui.Col_.slider_grab_active: DATA_GREEN,
|
imgui.Col_.slider_grab_active: DATA_GREEN,
|
||||||
imgui.Col_.button: BLACK,
|
imgui.Col_.button: BLACK,
|
||||||
imgui.Col_.button_hovered: NERV_ORANGE,
|
imgui.Col_.button_hovered: _c(255, 152, 48, 80),
|
||||||
imgui.Col_.button_active: ALERT_RED,
|
imgui.Col_.button_active: _c(255, 152, 48, 120),
|
||||||
imgui.Col_.header: _c(255, 152, 48, 120),
|
imgui.Col_.header: _c(255, 152, 48, 60),
|
||||||
imgui.Col_.header_hovered: NERV_ORANGE,
|
imgui.Col_.header_hovered: _c(255, 152, 48, 100),
|
||||||
imgui.Col_.header_active: ALERT_RED,
|
imgui.Col_.header_active: _c(255, 152, 48, 140),
|
||||||
imgui.Col_.separator: STEEL,
|
imgui.Col_.separator: STEEL,
|
||||||
imgui.Col_.separator_hovered: WIRE_CYAN,
|
imgui.Col_.separator_hovered: WIRE_CYAN,
|
||||||
imgui.Col_.separator_active: DATA_GREEN,
|
imgui.Col_.separator_active: DATA_GREEN,
|
||||||
@@ -45,8 +45,8 @@ NERV_PALETTE = {
|
|||||||
imgui.Col_.resize_grip_hovered: STEEL,
|
imgui.Col_.resize_grip_hovered: STEEL,
|
||||||
imgui.Col_.resize_grip_active: WIRE_CYAN,
|
imgui.Col_.resize_grip_active: WIRE_CYAN,
|
||||||
imgui.Col_.tab: BLACK,
|
imgui.Col_.tab: BLACK,
|
||||||
imgui.Col_.tab_hovered: NERV_ORANGE,
|
imgui.Col_.tab_hovered: _c(255, 152, 48, 100),
|
||||||
imgui.Col_.tab_selected: NERV_ORANGE,
|
imgui.Col_.tab_selected: _c(255, 152, 48, 120),
|
||||||
imgui.Col_.tab_dimmed: BLACK,
|
imgui.Col_.tab_dimmed: BLACK,
|
||||||
imgui.Col_.tab_dimmed_selected: _c(255, 152, 48, 80),
|
imgui.Col_.tab_dimmed_selected: _c(255, 152, 48, 80),
|
||||||
imgui.Col_.plot_lines: WIRE_CYAN,
|
imgui.Col_.plot_lines: WIRE_CYAN,
|
||||||
|
|||||||
+23
-3
@@ -1,8 +1,9 @@
|
|||||||
import time
|
import time
|
||||||
import math
|
import math
|
||||||
|
import random
|
||||||
from imgui_bundle import imgui
|
from imgui_bundle import imgui
|
||||||
|
|
||||||
class ScanlineOverlay:
|
class CRTFilter:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.enabled = True
|
self.enabled = True
|
||||||
|
|
||||||
@@ -10,9 +11,28 @@ class ScanlineOverlay:
|
|||||||
if not self.enabled:
|
if not self.enabled:
|
||||||
return
|
return
|
||||||
draw_list = imgui.get_foreground_draw_list()
|
draw_list = imgui.get_foreground_draw_list()
|
||||||
color = imgui.get_color_u32((0.0, 0.0, 0.0, 0.06))
|
|
||||||
|
# Scanlines
|
||||||
|
scanline_color = imgui.get_color_u32((0.0, 0.0, 0.0, 0.06))
|
||||||
for y in range(0, int(height), 2):
|
for y in range(0, int(height), 2):
|
||||||
draw_list.add_line((0.0, float(y)), (float(width), float(y)), color, 1.0)
|
draw_list.add_line((0.0, float(y)), (float(width), float(y)), scanline_color, 1.0)
|
||||||
|
|
||||||
|
# Subtle Vignette
|
||||||
|
v_steps = 15
|
||||||
|
for i in range(v_steps):
|
||||||
|
alpha = (i / v_steps) ** 2.0 * 0.15
|
||||||
|
v_color = imgui.get_color_u32((0.0, 0.0, 0.0, alpha))
|
||||||
|
inset = (v_steps - i) * 6.0
|
||||||
|
if width > inset * 2.0 and height > inset * 2.0:
|
||||||
|
draw_list.add_rect((inset, inset), (width - inset, height - inset), v_color, 40.0, 0, 10.0)
|
||||||
|
|
||||||
|
# Subtle Random Noise
|
||||||
|
for _ in range(30):
|
||||||
|
nx = random.uniform(0.0, width)
|
||||||
|
ny = random.uniform(0.0, height)
|
||||||
|
n_alpha = random.uniform(0.01, 0.03)
|
||||||
|
n_color = imgui.get_color_u32((1.0, 1.0, 1.0, n_alpha))
|
||||||
|
draw_list.add_rect_filled((nx, ny), (nx + 1.0, ny + 1.0), n_color)
|
||||||
|
|
||||||
class StatusFlicker:
|
class StatusFlicker:
|
||||||
def get_alpha(self) -> float:
|
def get_alpha(self) -> float:
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ def test_apply_nerv_sets_rounding_and_colors(monkeypatch):
|
|||||||
|
|
||||||
# Verify key colors
|
# Verify key colors
|
||||||
# window_bg should be BLACK (0, 0, 0, 1.0)
|
# window_bg should be BLACK (0, 0, 0, 1.0)
|
||||||
# text should be NERV_ORANGE (255/255.0, 152/255.0, 48/255.0, 1.0)
|
# text should be STEEL (224/255.0, 224/255.0, 216/255.0, 1.0)
|
||||||
|
# title_bg_active should be BLACK (0, 0, 0, 1.0)
|
||||||
|
|
||||||
# Extract calls to set_color_
|
# Extract calls to set_color_
|
||||||
# Using real imgui.Col_ values for keys because they are bound in NERV_PALETTE at import time
|
# Using real imgui.Col_ values for keys because they are bound in NERV_PALETTE at import time
|
||||||
@@ -44,4 +45,7 @@ def test_apply_nerv_sets_rounding_and_colors(monkeypatch):
|
|||||||
assert color_calls[imgui.Col_.window_bg] == (0.0, 0.0, 0.0, 1.0)
|
assert color_calls[imgui.Col_.window_bg] == (0.0, 0.0, 0.0, 1.0)
|
||||||
|
|
||||||
assert imgui.Col_.text in color_calls
|
assert imgui.Col_.text in color_calls
|
||||||
assert color_calls[imgui.Col_.text] == (1.0, 152/255.0, 48/255.0, 1.0)
|
assert color_calls[imgui.Col_.text] == (224/255.0, 224/255.0, 216/255.0, 1.0)
|
||||||
|
|
||||||
|
assert imgui.Col_.title_bg_active in color_calls
|
||||||
|
assert color_calls[imgui.Col_.title_bg_active] == (0.0, 0.0, 0.0, 1.0)
|
||||||
|
|||||||
+52
-16
@@ -1,30 +1,45 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
import math
|
import math
|
||||||
from src.theme_nerv_fx import ScanlineOverlay, StatusFlicker
|
from src.theme_nerv_fx import CRTFilter, StatusFlicker, AlertPulsing
|
||||||
|
|
||||||
class TestThemeNervFx(unittest.TestCase):
|
class TestThemeNervFx(unittest.TestCase):
|
||||||
@patch("src.theme_nerv_fx.imgui")
|
@patch("src.theme_nerv_fx.imgui")
|
||||||
def test_scanline_overlay_render(self, mock_imgui):
|
def test_crt_filter_render(self, mock_imgui):
|
||||||
# Setup
|
# Setup
|
||||||
mock_draw_list = MagicMock()
|
mock_draw_list = MagicMock()
|
||||||
mock_imgui.get_foreground_draw_list.return_value = mock_draw_list
|
mock_imgui.get_foreground_draw_list.return_value = mock_draw_list
|
||||||
mock_imgui.get_color_u32.return_value = 0x12345678
|
mock_imgui.get_color_u32.return_value = 0x12345678
|
||||||
|
|
||||||
overlay = ScanlineOverlay()
|
overlay = CRTFilter()
|
||||||
width, height = 100.0, 10.0
|
width, height = 800.0, 600.0
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
overlay.render(width, height)
|
overlay.render(width, height)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
mock_imgui.get_foreground_draw_list.assert_called_once()
|
mock_imgui.get_foreground_draw_list.assert_called_once()
|
||||||
# height is 10, range(0, 10, 2) is [0, 2, 4, 6, 8] -> 5 calls
|
# height is 600, range(0, 600, 2) is 300 calls
|
||||||
self.assertEqual(mock_draw_list.add_line.call_count, 5)
|
self.assertEqual(mock_draw_list.add_line.call_count, 300)
|
||||||
|
# Vignette: v_steps = 15. height=600 is plenty for all insets.
|
||||||
|
self.assertEqual(mock_draw_list.add_rect.call_count, 15)
|
||||||
|
# Noise: 30 calls to add_rect_filled
|
||||||
|
self.assertEqual(mock_draw_list.add_rect_filled.call_count, 30)
|
||||||
|
|
||||||
# Verify some calls
|
# Verify some calls
|
||||||
mock_draw_list.add_line.assert_any_call((0.0, 0.0), (100.0, 0.0), 0x12345678, 1.0)
|
mock_draw_list.add_line.assert_any_call((0.0, 0.0), (800.0, 0.0), 0x12345678, 1.0)
|
||||||
mock_draw_list.add_line.assert_any_call((0.0, 8.0), (100.0, 8.0), 0x12345678, 1.0)
|
mock_draw_list.add_line.assert_any_call((0.0, 598.0), (800.0, 598.0), 0x12345678, 1.0)
|
||||||
|
|
||||||
|
@patch("src.theme_nerv_fx.imgui")
|
||||||
|
def test_crt_filter_disabled(self, mock_imgui):
|
||||||
|
mock_draw_list = MagicMock()
|
||||||
|
mock_imgui.get_foreground_draw_list.return_value = mock_draw_list
|
||||||
|
|
||||||
|
overlay = CRTFilter()
|
||||||
|
overlay.enabled = False
|
||||||
|
overlay.render(800.0, 600.0)
|
||||||
|
|
||||||
|
mock_imgui.get_foreground_draw_list.assert_not_called()
|
||||||
|
|
||||||
@patch("src.theme_nerv_fx.time")
|
@patch("src.theme_nerv_fx.time")
|
||||||
def test_status_flicker_get_alpha(self, mock_time):
|
def test_status_flicker_get_alpha(self, mock_time):
|
||||||
@@ -36,21 +51,42 @@ class TestThemeNervFx(unittest.TestCase):
|
|||||||
self.assertAlmostEqual(flicker.get_alpha(), 0.85)
|
self.assertAlmostEqual(flicker.get_alpha(), 0.85)
|
||||||
|
|
||||||
# sin(pi/2) = 1 -> 1.0
|
# sin(pi/2) = 1 -> 1.0
|
||||||
# time * 20.0 = pi/2 -> time = pi/40
|
|
||||||
mock_time.time.return_value = math.pi / 40.0
|
mock_time.time.return_value = math.pi / 40.0
|
||||||
self.assertAlmostEqual(flicker.get_alpha(), 1.0)
|
self.assertAlmostEqual(flicker.get_alpha(), 1.0)
|
||||||
|
|
||||||
# sin(3pi/2) = -1 -> 0.7
|
# sin(3pi/2) = -1 -> 0.7
|
||||||
# time * 20.0 = 3pi/2 -> time = 3pi/40
|
|
||||||
mock_time.time.return_value = 3 * math.pi / 40.0
|
mock_time.time.return_value = 3 * math.pi / 40.0
|
||||||
self.assertAlmostEqual(flicker.get_alpha(), 0.7)
|
self.assertAlmostEqual(flicker.get_alpha(), 0.7)
|
||||||
|
|
||||||
|
def test_alert_pulsing_update(self):
|
||||||
|
pulse = AlertPulsing()
|
||||||
|
self.assertFalse(pulse.active)
|
||||||
|
|
||||||
# Verify range for many samples
|
pulse.update("error: something failed")
|
||||||
for i in range(100):
|
self.assertTrue(pulse.active)
|
||||||
mock_time.time.return_value = i * 0.1
|
|
||||||
alpha = flicker.get_alpha()
|
pulse.update("Error: alert")
|
||||||
self.assertGreaterEqual(alpha, 0.7)
|
self.assertTrue(pulse.active)
|
||||||
self.assertLessEqual(alpha, 1.0)
|
|
||||||
|
pulse.update("idle")
|
||||||
|
self.assertFalse(pulse.active)
|
||||||
|
|
||||||
|
@patch("src.theme_nerv_fx.imgui")
|
||||||
|
@patch("src.theme_nerv_fx.time")
|
||||||
|
def test_alert_pulsing_render(self, mock_time, mock_imgui):
|
||||||
|
mock_draw_list = MagicMock()
|
||||||
|
mock_imgui.get_foreground_draw_list.return_value = mock_draw_list
|
||||||
|
mock_imgui.get_color_u32.return_value = 0xFF0000FF
|
||||||
|
|
||||||
|
pulse = AlertPulsing()
|
||||||
|
pulse.active = True
|
||||||
|
|
||||||
|
# sin(0) = 0 -> alpha = 0.05 + 0.15 * (0+1)/2 = 0.125
|
||||||
|
mock_time.time.return_value = 0.0
|
||||||
|
pulse.render(800.0, 600.0)
|
||||||
|
|
||||||
|
mock_imgui.get_foreground_draw_list.assert_called()
|
||||||
|
mock_draw_list.add_rect.assert_called_with((0.0, 0.0), (800.0, 600.0), 0xFF0000FF, 0.0, 0, 10.0)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user