kollabor 0.4.9__py3-none-any.whl → 0.4.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agents/__init__.py +2 -0
- agents/coder/__init__.py +0 -0
- agents/coder/agent.json +4 -0
- agents/coder/api-integration.md +2150 -0
- agents/coder/cli-pretty.md +765 -0
- agents/coder/code-review.md +1092 -0
- agents/coder/database-design.md +1525 -0
- agents/coder/debugging.md +1102 -0
- agents/coder/dependency-management.md +1397 -0
- agents/coder/git-workflow.md +1099 -0
- agents/coder/refactoring.md +1454 -0
- agents/coder/security-hardening.md +1732 -0
- agents/coder/system_prompt.md +1448 -0
- agents/coder/tdd.md +1367 -0
- agents/creative-writer/__init__.py +0 -0
- agents/creative-writer/agent.json +4 -0
- agents/creative-writer/character-development.md +1852 -0
- agents/creative-writer/dialogue-craft.md +1122 -0
- agents/creative-writer/plot-structure.md +1073 -0
- agents/creative-writer/revision-editing.md +1484 -0
- agents/creative-writer/system_prompt.md +690 -0
- agents/creative-writer/worldbuilding.md +2049 -0
- agents/data-analyst/__init__.py +30 -0
- agents/data-analyst/agent.json +4 -0
- agents/data-analyst/data-visualization.md +992 -0
- agents/data-analyst/exploratory-data-analysis.md +1110 -0
- agents/data-analyst/pandas-data-manipulation.md +1081 -0
- agents/data-analyst/sql-query-optimization.md +881 -0
- agents/data-analyst/statistical-analysis.md +1118 -0
- agents/data-analyst/system_prompt.md +928 -0
- agents/default/__init__.py +0 -0
- agents/default/agent.json +4 -0
- agents/default/dead-code.md +794 -0
- agents/default/explore-agent-system.md +585 -0
- agents/default/system_prompt.md +1448 -0
- agents/kollabor/__init__.py +0 -0
- agents/kollabor/analyze-plugin-lifecycle.md +175 -0
- agents/kollabor/analyze-terminal-rendering.md +388 -0
- agents/kollabor/code-review.md +1092 -0
- agents/kollabor/debug-mcp-integration.md +521 -0
- agents/kollabor/debug-plugin-hooks.md +547 -0
- agents/kollabor/debugging.md +1102 -0
- agents/kollabor/dependency-management.md +1397 -0
- agents/kollabor/git-workflow.md +1099 -0
- agents/kollabor/inspect-llm-conversation.md +148 -0
- agents/kollabor/monitor-event-bus.md +558 -0
- agents/kollabor/profile-performance.md +576 -0
- agents/kollabor/refactoring.md +1454 -0
- agents/kollabor/system_prompt copy.md +1448 -0
- agents/kollabor/system_prompt.md +757 -0
- agents/kollabor/trace-command-execution.md +178 -0
- agents/kollabor/validate-config.md +879 -0
- agents/research/__init__.py +0 -0
- agents/research/agent.json +4 -0
- agents/research/architecture-mapping.md +1099 -0
- agents/research/codebase-analysis.md +1077 -0
- agents/research/dependency-audit.md +1027 -0
- agents/research/performance-profiling.md +1047 -0
- agents/research/security-review.md +1359 -0
- agents/research/system_prompt.md +492 -0
- agents/technical-writer/__init__.py +0 -0
- agents/technical-writer/agent.json +4 -0
- agents/technical-writer/api-documentation.md +2328 -0
- agents/technical-writer/changelog-management.md +1181 -0
- agents/technical-writer/readme-writing.md +1360 -0
- agents/technical-writer/style-guide.md +1410 -0
- agents/technical-writer/system_prompt.md +653 -0
- agents/technical-writer/tutorial-creation.md +1448 -0
- core/__init__.py +0 -2
- core/application.py +343 -88
- core/cli.py +229 -10
- core/commands/menu_renderer.py +463 -59
- core/commands/registry.py +14 -9
- core/commands/system_commands.py +2461 -14
- core/config/loader.py +151 -37
- core/config/service.py +18 -6
- core/events/bus.py +29 -9
- core/events/executor.py +205 -75
- core/events/models.py +27 -8
- core/fullscreen/command_integration.py +20 -24
- core/fullscreen/components/__init__.py +10 -1
- core/fullscreen/components/matrix_components.py +1 -2
- core/fullscreen/components/space_shooter_components.py +654 -0
- core/fullscreen/plugin.py +5 -0
- core/fullscreen/renderer.py +52 -13
- core/fullscreen/session.py +52 -15
- core/io/__init__.py +29 -5
- core/io/buffer_manager.py +6 -1
- core/io/config_status_view.py +7 -29
- core/io/core_status_views.py +267 -347
- core/io/input/__init__.py +25 -0
- core/io/input/command_mode_handler.py +711 -0
- core/io/input/display_controller.py +128 -0
- core/io/input/hook_registrar.py +286 -0
- core/io/input/input_loop_manager.py +421 -0
- core/io/input/key_press_handler.py +502 -0
- core/io/input/modal_controller.py +1011 -0
- core/io/input/paste_processor.py +339 -0
- core/io/input/status_modal_renderer.py +184 -0
- core/io/input_errors.py +5 -1
- core/io/input_handler.py +211 -2452
- core/io/key_parser.py +7 -0
- core/io/layout.py +15 -3
- core/io/message_coordinator.py +111 -2
- core/io/message_renderer.py +129 -4
- core/io/status_renderer.py +147 -607
- core/io/terminal_renderer.py +97 -51
- core/io/terminal_state.py +21 -4
- core/io/visual_effects.py +816 -165
- core/llm/agent_manager.py +1063 -0
- core/llm/api_adapters/__init__.py +44 -0
- core/llm/api_adapters/anthropic_adapter.py +432 -0
- core/llm/api_adapters/base.py +241 -0
- core/llm/api_adapters/openai_adapter.py +326 -0
- core/llm/api_communication_service.py +167 -113
- core/llm/conversation_logger.py +322 -16
- core/llm/conversation_manager.py +556 -30
- core/llm/file_operations_executor.py +84 -32
- core/llm/llm_service.py +934 -103
- core/llm/mcp_integration.py +541 -57
- core/llm/message_display_service.py +135 -18
- core/llm/plugin_sdk.py +1 -2
- core/llm/profile_manager.py +1183 -0
- core/llm/response_parser.py +274 -56
- core/llm/response_processor.py +16 -3
- core/llm/tool_executor.py +6 -1
- core/logging/__init__.py +2 -0
- core/logging/setup.py +34 -6
- core/models/resume.py +54 -0
- core/plugins/__init__.py +4 -2
- core/plugins/base.py +127 -0
- core/plugins/collector.py +23 -161
- core/plugins/discovery.py +37 -3
- core/plugins/factory.py +6 -12
- core/plugins/registry.py +5 -17
- core/ui/config_widgets.py +128 -28
- core/ui/live_modal_renderer.py +2 -1
- core/ui/modal_actions.py +5 -0
- core/ui/modal_overlay_renderer.py +0 -60
- core/ui/modal_renderer.py +268 -7
- core/ui/modal_state_manager.py +29 -4
- core/ui/widgets/base_widget.py +7 -0
- core/updates/__init__.py +10 -0
- core/updates/version_check_service.py +348 -0
- core/updates/version_comparator.py +103 -0
- core/utils/config_utils.py +685 -526
- core/utils/plugin_utils.py +1 -1
- core/utils/session_naming.py +111 -0
- fonts/LICENSE +21 -0
- fonts/README.md +46 -0
- fonts/SymbolsNerdFont-Regular.ttf +0 -0
- fonts/SymbolsNerdFontMono-Regular.ttf +0 -0
- fonts/__init__.py +44 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/METADATA +54 -4
- kollabor-0.4.15.dist-info/RECORD +228 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/top_level.txt +2 -0
- plugins/agent_orchestrator/__init__.py +39 -0
- plugins/agent_orchestrator/activity_monitor.py +181 -0
- plugins/agent_orchestrator/file_attacher.py +77 -0
- plugins/agent_orchestrator/message_injector.py +135 -0
- plugins/agent_orchestrator/models.py +48 -0
- plugins/agent_orchestrator/orchestrator.py +403 -0
- plugins/agent_orchestrator/plugin.py +976 -0
- plugins/agent_orchestrator/xml_parser.py +191 -0
- plugins/agent_orchestrator_plugin.py +9 -0
- plugins/enhanced_input/box_styles.py +1 -0
- plugins/enhanced_input/color_engine.py +19 -4
- plugins/enhanced_input/config.py +2 -2
- plugins/enhanced_input_plugin.py +61 -11
- plugins/fullscreen/__init__.py +6 -2
- plugins/fullscreen/example_plugin.py +1035 -222
- plugins/fullscreen/setup_wizard_plugin.py +592 -0
- plugins/fullscreen/space_shooter_plugin.py +131 -0
- plugins/hook_monitoring_plugin.py +436 -78
- plugins/query_enhancer_plugin.py +66 -30
- plugins/resume_conversation_plugin.py +1494 -0
- plugins/save_conversation_plugin.py +98 -32
- plugins/system_commands_plugin.py +70 -56
- plugins/tmux_plugin.py +154 -78
- plugins/workflow_enforcement_plugin.py +94 -92
- system_prompt/default.md +952 -886
- core/io/input_mode_manager.py +0 -402
- core/io/modal_interaction_handler.py +0 -315
- core/io/raw_input_processor.py +0 -946
- core/storage/__init__.py +0 -5
- core/storage/state_manager.py +0 -84
- core/ui/widget_integration.py +0 -222
- core/utils/key_reader.py +0 -171
- kollabor-0.4.9.dist-info/RECORD +0 -128
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/WHEEL +0 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/entry_points.txt +0 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,327 +1,1140 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Enhanced example full-screen plugin demonstrating all framework capabilities.
|
|
2
|
+
|
|
3
|
+
This comprehensive plugin showcases:
|
|
4
|
+
- Table of contents navigation
|
|
5
|
+
- Framework statistics and info
|
|
6
|
+
- Drawing primitives (borders, shapes, progress, spinners)
|
|
7
|
+
- Color showcase (256 colors, gradients)
|
|
8
|
+
- Advanced animations (easing, particles, physics)
|
|
9
|
+
- Interactive components (text input, checkboxes, sliders)
|
|
10
|
+
- Calculator mini-app
|
|
11
|
+
- Todo list mini-app
|
|
12
|
+
- Conway's Game of Life
|
|
13
|
+
- Performance comparison
|
|
14
|
+
- Code examples with syntax highlighting
|
|
15
|
+
- Resources and documentation
|
|
16
|
+
"""
|
|
2
17
|
|
|
3
18
|
import asyncio
|
|
4
19
|
import math
|
|
20
|
+
import random
|
|
21
|
+
import time
|
|
22
|
+
from typing import List, Tuple, Dict, Optional
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
|
|
5
25
|
from core.fullscreen import FullScreenPlugin
|
|
6
26
|
from core.fullscreen.plugin import PluginMetadata
|
|
7
27
|
from core.fullscreen.components.drawing import DrawingPrimitives
|
|
8
28
|
from core.fullscreen.components.animation import AnimationFramework, EasingFunctions
|
|
9
|
-
from core.io.visual_effects import ColorPalette
|
|
29
|
+
from core.io.visual_effects import ColorPalette, GradientRenderer
|
|
10
30
|
from core.io.key_parser import KeyPress
|
|
11
31
|
|
|
12
32
|
|
|
13
|
-
|
|
14
|
-
|
|
33
|
+
@dataclass
|
|
34
|
+
class TodoItem:
|
|
35
|
+
"""Todo list item."""
|
|
36
|
+
text: str
|
|
37
|
+
completed: bool = False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class PageInfo:
|
|
42
|
+
"""Page metadata for navigation."""
|
|
43
|
+
title: str
|
|
44
|
+
description: str
|
|
45
|
+
icon: str
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class SimpleTextInput:
|
|
49
|
+
"""Lightweight text input handler for fullscreen plugins."""
|
|
50
|
+
|
|
51
|
+
def __init__(self, initial_value: str = "", max_length: int = 50):
|
|
52
|
+
"""Initialize text input.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
initial_value: Starting text value
|
|
56
|
+
max_length: Maximum input length
|
|
57
|
+
"""
|
|
58
|
+
self.value = initial_value
|
|
59
|
+
self.cursor_pos = len(initial_value)
|
|
60
|
+
self.max_length = max_length
|
|
61
|
+
|
|
62
|
+
def handle_key(self, key_press: KeyPress) -> bool:
|
|
63
|
+
"""Handle keyboard input for text editing.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
key_press: Key press event
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
True if key was handled
|
|
70
|
+
"""
|
|
71
|
+
# Backspace
|
|
72
|
+
if key_press.name == "Backspace" or key_press.char in ['\x7f', '\x08']:
|
|
73
|
+
if self.cursor_pos > 0:
|
|
74
|
+
self.value = self.value[:self.cursor_pos - 1] + self.value[self.cursor_pos:]
|
|
75
|
+
self.cursor_pos -= 1
|
|
76
|
+
return True
|
|
77
|
+
|
|
78
|
+
# Delete
|
|
79
|
+
elif key_press.name == "Delete":
|
|
80
|
+
if self.cursor_pos < len(self.value):
|
|
81
|
+
self.value = self.value[:self.cursor_pos] + self.value[self.cursor_pos + 1:]
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
# Arrow left
|
|
85
|
+
elif key_press.name == "ArrowLeft":
|
|
86
|
+
self.cursor_pos = max(0, self.cursor_pos - 1)
|
|
87
|
+
return True
|
|
88
|
+
|
|
89
|
+
# Arrow right
|
|
90
|
+
elif key_press.name == "ArrowRight":
|
|
91
|
+
self.cursor_pos = min(len(self.value), self.cursor_pos + 1)
|
|
92
|
+
return True
|
|
93
|
+
|
|
94
|
+
# Home
|
|
95
|
+
elif key_press.name == "Home":
|
|
96
|
+
self.cursor_pos = 0
|
|
97
|
+
return True
|
|
98
|
+
|
|
99
|
+
# End
|
|
100
|
+
elif key_press.name == "End":
|
|
101
|
+
self.cursor_pos = len(self.value)
|
|
102
|
+
return True
|
|
103
|
+
|
|
104
|
+
# Printable character
|
|
105
|
+
elif key_press.char and key_press.char.isprintable() and len(self.value) < self.max_length:
|
|
106
|
+
self.value = self.value[:self.cursor_pos] + key_press.char + self.value[self.cursor_pos:]
|
|
107
|
+
self.cursor_pos += 1
|
|
108
|
+
return True
|
|
109
|
+
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
def render_with_cursor(self) -> str:
|
|
113
|
+
"""Render text with cursor indicator.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Text with cursor shown as underscore
|
|
117
|
+
"""
|
|
118
|
+
if self.cursor_pos < len(self.value):
|
|
119
|
+
return self.value[:self.cursor_pos] + "▌" + self.value[self.cursor_pos:]
|
|
120
|
+
else:
|
|
121
|
+
return self.value + "▌"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class EnhancedExamplePlugin(FullScreenPlugin):
|
|
125
|
+
"""Comprehensive example plugin showcasing all framework features.
|
|
15
126
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
127
|
+
Pages:
|
|
128
|
+
0. Table of Contents
|
|
129
|
+
1. Framework Stats & Info
|
|
130
|
+
2. Drawing Primitives
|
|
131
|
+
3. Color Showcase
|
|
132
|
+
4. Animations
|
|
133
|
+
5. Interactive Components
|
|
134
|
+
6. Calculator
|
|
135
|
+
7. Todo List
|
|
136
|
+
8. Game of Life
|
|
137
|
+
9. Performance Demo
|
|
138
|
+
10. Code Examples
|
|
139
|
+
11. Resources & Help
|
|
21
140
|
"""
|
|
22
141
|
|
|
23
142
|
def __init__(self):
|
|
24
|
-
"""Initialize the example plugin."""
|
|
143
|
+
"""Initialize the enhanced example plugin."""
|
|
25
144
|
metadata = PluginMetadata(
|
|
26
145
|
name="example",
|
|
27
|
-
description="
|
|
28
|
-
version="
|
|
29
|
-
author="Framework",
|
|
146
|
+
description="Comprehensive framework showcase with 12 interactive pages",
|
|
147
|
+
version="2.0.0",
|
|
148
|
+
author="Kollabor Framework",
|
|
30
149
|
category="demo",
|
|
31
150
|
icon="🎯",
|
|
32
|
-
aliases=[]
|
|
151
|
+
aliases=["demo", "showcase"]
|
|
33
152
|
)
|
|
34
153
|
super().__init__(metadata)
|
|
35
154
|
|
|
36
|
-
#
|
|
155
|
+
# Moderate FPS for smooth animations without excessive CPU
|
|
156
|
+
self.target_fps = 30.0
|
|
157
|
+
|
|
158
|
+
# Navigation
|
|
37
159
|
self.current_page = 0
|
|
38
|
-
self.total_pages =
|
|
160
|
+
self.total_pages = 12
|
|
161
|
+
self.frame_count = 0
|
|
162
|
+
|
|
163
|
+
# Page definitions
|
|
164
|
+
self.pages = [
|
|
165
|
+
PageInfo("Table of Contents", "Navigate to any page", "📋"),
|
|
166
|
+
PageInfo("Framework Stats", "Live performance metrics", "📊"),
|
|
167
|
+
PageInfo("Drawing Primitives", "Borders, shapes, progress bars", "🎨"),
|
|
168
|
+
PageInfo("Color Showcase", "256 colors and gradients", "🌈"),
|
|
169
|
+
PageInfo("Animations", "Easing functions and particle effects", "✨"),
|
|
170
|
+
PageInfo("Interactive Components", "Text input, checkboxes, sliders", "🎮"),
|
|
171
|
+
PageInfo("Calculator", "Functional number pad calculator", "🔢"),
|
|
172
|
+
PageInfo("Todo List", "Task management with add/remove", "📝"),
|
|
173
|
+
PageInfo("Game of Life", "Conway's cellular automaton", "🧬"),
|
|
174
|
+
PageInfo("Performance Demo", "FPS comparison and optimization", "⚡"),
|
|
175
|
+
PageInfo("Code Examples", "Copy-ready plugin templates", "💻"),
|
|
176
|
+
PageInfo("Resources & Help", "Documentation and key bindings", "📚"),
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
# Animation framework
|
|
39
180
|
self.animation_framework = AnimationFramework()
|
|
40
|
-
self.page_transition_id = None
|
|
41
181
|
self.demo_animations = {}
|
|
42
|
-
|
|
182
|
+
|
|
183
|
+
# Calculator state
|
|
184
|
+
self.calc_display = "0"
|
|
185
|
+
self.calc_operator = None
|
|
186
|
+
self.calc_operand = None
|
|
187
|
+
self.calc_new_number = True
|
|
188
|
+
|
|
189
|
+
# Todo list state
|
|
190
|
+
self.todos: List[TodoItem] = [
|
|
191
|
+
TodoItem("Try the calculator (page 6)", False),
|
|
192
|
+
TodoItem("Explore Game of Life (page 8)", False),
|
|
193
|
+
TodoItem("View code examples (page 10)", False),
|
|
194
|
+
]
|
|
195
|
+
self.todo_input = SimpleTextInput()
|
|
196
|
+
self.todo_editing = False
|
|
197
|
+
|
|
198
|
+
# Game of Life state
|
|
199
|
+
self.life_grid: List[List[bool]] = []
|
|
200
|
+
self.life_running = False
|
|
201
|
+
self.life_generation = 0
|
|
202
|
+
self.life_last_update = 0.0
|
|
203
|
+
self.life_speed = 0.2 # seconds per generation
|
|
204
|
+
|
|
205
|
+
# Interactive components state
|
|
206
|
+
self.text_input = SimpleTextInput("Hello, World!")
|
|
207
|
+
self.checkbox_states = [True, False, True]
|
|
208
|
+
self.slider_value = 50
|
|
209
|
+
self.active_input = 0 # Which component is focused
|
|
210
|
+
|
|
211
|
+
# Performance demo state
|
|
212
|
+
self.perf_mode = 0 # 0=buffered, 1=unbuffered simulation
|
|
213
|
+
self.perf_fps_history: List[float] = []
|
|
43
214
|
|
|
44
215
|
async def initialize(self, renderer) -> bool:
|
|
45
216
|
"""Initialize the example plugin."""
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if not await super().initialize(renderer):
|
|
49
|
-
print("❌ CRITICAL: super().initialize() failed")
|
|
50
|
-
return False
|
|
217
|
+
if not await super().initialize(renderer):
|
|
218
|
+
return False
|
|
51
219
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
220
|
+
# Setup initial animations
|
|
221
|
+
current_time = asyncio.get_event_loop().time()
|
|
222
|
+
self.demo_animations['title_fade'] = self.animation_framework.fade_in(1.5, current_time)
|
|
223
|
+
self.demo_animations['bounce'] = self.animation_framework.bounce_in(1.0, current_time + 0.3)
|
|
56
224
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
import traceback
|
|
62
|
-
traceback.print_exc()
|
|
63
|
-
return False
|
|
225
|
+
# Initialize Game of Life grid
|
|
226
|
+
self._init_life_grid()
|
|
227
|
+
|
|
228
|
+
return True
|
|
64
229
|
|
|
65
230
|
async def on_start(self):
|
|
66
|
-
"""Called when
|
|
231
|
+
"""Called when plugin starts."""
|
|
67
232
|
await super().on_start()
|
|
233
|
+
self.perf_fps_history = []
|
|
68
234
|
|
|
69
235
|
async def render_frame(self, delta_time: float) -> bool:
|
|
70
|
-
"""Render the
|
|
236
|
+
"""Render the current page."""
|
|
71
237
|
if not self.renderer:
|
|
72
238
|
return False
|
|
73
239
|
|
|
74
|
-
# Increment frame counter for animations
|
|
75
240
|
self.frame_count += 1
|
|
76
241
|
|
|
77
|
-
#
|
|
242
|
+
# Update FPS history for performance page
|
|
243
|
+
if len(self.perf_fps_history) > 100:
|
|
244
|
+
self.perf_fps_history.pop(0)
|
|
245
|
+
current_fps = 1.0 / delta_time if delta_time > 0 else 0
|
|
246
|
+
self.perf_fps_history.append(current_fps)
|
|
247
|
+
|
|
248
|
+
# Clear screen
|
|
78
249
|
self.renderer.clear_screen()
|
|
79
250
|
width, height = self.renderer.get_terminal_size()
|
|
80
251
|
|
|
81
|
-
# Render
|
|
252
|
+
# Render appropriate page
|
|
82
253
|
if self.current_page == 0:
|
|
83
|
-
self.
|
|
254
|
+
self._render_toc_page(width, height)
|
|
84
255
|
elif self.current_page == 1:
|
|
85
|
-
self.
|
|
256
|
+
self._render_stats_page(width, height)
|
|
86
257
|
elif self.current_page == 2:
|
|
87
|
-
self.
|
|
258
|
+
self._render_drawing_page(width, height)
|
|
88
259
|
elif self.current_page == 3:
|
|
89
|
-
self.
|
|
260
|
+
self._render_color_page(width, height)
|
|
261
|
+
elif self.current_page == 4:
|
|
262
|
+
self._render_animation_page(width, height)
|
|
263
|
+
elif self.current_page == 5:
|
|
264
|
+
self._render_interactive_page(width, height)
|
|
265
|
+
elif self.current_page == 6:
|
|
266
|
+
self._render_calculator_page(width, height)
|
|
267
|
+
elif self.current_page == 7:
|
|
268
|
+
self._render_todo_page(width, height)
|
|
269
|
+
elif self.current_page == 8:
|
|
270
|
+
await self._render_life_page(width, height, delta_time)
|
|
271
|
+
elif self.current_page == 9:
|
|
272
|
+
self._render_performance_page(width, height)
|
|
273
|
+
elif self.current_page == 10:
|
|
274
|
+
self._render_code_page(width, height)
|
|
275
|
+
elif self.current_page == 11:
|
|
276
|
+
self._render_resources_page(width, height)
|
|
90
277
|
|
|
91
|
-
#
|
|
92
|
-
|
|
93
|
-
nav_x = (width - len(nav_text)) // 2
|
|
94
|
-
self.renderer.write_at(nav_x, height - 1, nav_text, "\033[37m")
|
|
278
|
+
# Render common navigation footer
|
|
279
|
+
self._render_navigation(width, height)
|
|
95
280
|
|
|
96
|
-
# Flush output
|
|
97
|
-
self.renderer.flush()
|
|
98
281
|
return True
|
|
99
282
|
|
|
100
|
-
def
|
|
101
|
-
"""Render
|
|
102
|
-
#
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
DrawingPrimitives.draw_text_centered(
|
|
106
|
-
self.renderer, height // 4,
|
|
107
|
-
"🎯 Full-Screen Framework Demo",
|
|
108
|
-
ColorPalette.BRIGHT_CYAN
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
# Bouncing subtitle
|
|
112
|
-
bounce_offset = int(self.animation_framework.get_value(self.demo_animations.get('bounce', 0)) * 3)
|
|
113
|
-
DrawingPrimitives.draw_text_centered(
|
|
114
|
-
self.renderer, height // 2 - bounce_offset,
|
|
115
|
-
"Welcome to the Plugin Framework!",
|
|
116
|
-
ColorPalette.BRIGHT_GREEN
|
|
117
|
-
)
|
|
283
|
+
def _render_toc_page(self, width: int, height: int):
|
|
284
|
+
"""Render table of contents with grid layout."""
|
|
285
|
+
# Title
|
|
286
|
+
title = "🎯 FRAMEWORK SHOWCASE - TABLE OF CONTENTS"
|
|
287
|
+
DrawingPrimitives.draw_text_centered(self.renderer, 2, title, ColorPalette.BRIGHT_CYAN)
|
|
118
288
|
|
|
119
|
-
#
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
289
|
+
# Subtitle
|
|
290
|
+
subtitle = "Press number key (0-9) or arrow keys to navigate"
|
|
291
|
+
DrawingPrimitives.draw_text_centered(self.renderer, 3, subtitle, ColorPalette.DIM_WHITE)
|
|
292
|
+
|
|
293
|
+
# Draw pages in grid (3 columns)
|
|
294
|
+
start_y = 6
|
|
295
|
+
col_width = width // 3
|
|
296
|
+
|
|
297
|
+
for idx, page in enumerate(self.pages):
|
|
298
|
+
row = idx // 3
|
|
299
|
+
col = idx % 3
|
|
300
|
+
|
|
301
|
+
x = col * col_width + 5
|
|
302
|
+
y = start_y + (row * 4)
|
|
303
|
+
|
|
304
|
+
# Page number and icon
|
|
305
|
+
num_str = f"[{idx}]" if idx < 10 else f"[{chr(65 + idx - 10)}]"
|
|
306
|
+
self.renderer.write_at(x, y, num_str, ColorPalette.BRIGHT_YELLOW)
|
|
307
|
+
self.renderer.write_at(x + 5, y, page.icon, ColorPalette.WHITE)
|
|
308
|
+
|
|
309
|
+
# Page title
|
|
310
|
+
self.renderer.write_at(x + 7, y, page.title[:25], ColorPalette.BRIGHT_GREEN)
|
|
311
|
+
|
|
312
|
+
# Page description
|
|
313
|
+
desc = page.description[:30]
|
|
314
|
+
self.renderer.write_at(x + 3, y + 1, desc, ColorPalette.DIM_WHITE)
|
|
315
|
+
|
|
316
|
+
def _render_stats_page(self, width: int, height: int):
|
|
317
|
+
"""Render framework statistics and terminal info."""
|
|
318
|
+
DrawingPrimitives.draw_text_centered(self.renderer, 2, "📊 FRAMEWORK STATISTICS", ColorPalette.BRIGHT_MAGENTA)
|
|
319
|
+
|
|
320
|
+
# Calculate stats
|
|
321
|
+
runtime = asyncio.get_event_loop().time() - self.start_time if self.running else 0
|
|
322
|
+
avg_fps = sum(self.perf_fps_history[-30:]) / len(self.perf_fps_history[-30:]) if self.perf_fps_history else 0
|
|
323
|
+
|
|
324
|
+
# Draw stats in columns
|
|
325
|
+
left_x = 10
|
|
326
|
+
right_x = width // 2 + 5
|
|
327
|
+
y = 5
|
|
328
|
+
|
|
329
|
+
# Left column - Plugin stats
|
|
330
|
+
self.renderer.write_at(left_x, y, "PLUGIN INFORMATION", ColorPalette.BRIGHT_CYAN)
|
|
331
|
+
y += 2
|
|
332
|
+
self.renderer.write_at(left_x, y, f"Name: {self.metadata.name}", ColorPalette.WHITE)
|
|
333
|
+
y += 1
|
|
334
|
+
self.renderer.write_at(left_x, y, f"Version: {self.metadata.version}", ColorPalette.WHITE)
|
|
335
|
+
y += 1
|
|
336
|
+
self.renderer.write_at(left_x, y, f"Category: {self.metadata.category}", ColorPalette.WHITE)
|
|
337
|
+
y += 1
|
|
338
|
+
self.renderer.write_at(left_x, y, f"Target FPS: {self.target_fps:.1f}", ColorPalette.WHITE)
|
|
339
|
+
y += 2
|
|
340
|
+
|
|
341
|
+
self.renderer.write_at(left_x, y, "RUNTIME STATISTICS", ColorPalette.BRIGHT_GREEN)
|
|
342
|
+
y += 2
|
|
343
|
+
self.renderer.write_at(left_x, y, f"Uptime: {runtime:.1f}s", ColorPalette.WHITE)
|
|
344
|
+
y += 1
|
|
345
|
+
self.renderer.write_at(left_x, y, f"Frames: {self.frame_count:,}", ColorPalette.WHITE)
|
|
346
|
+
y += 1
|
|
347
|
+
self.renderer.write_at(left_x, y, f"Current FPS: {avg_fps:.1f}", ColorPalette.WHITE)
|
|
348
|
+
y += 1
|
|
349
|
+
self.renderer.write_at(left_x, y, f"Page: {self.current_page + 1}/{self.total_pages}", ColorPalette.WHITE)
|
|
125
350
|
|
|
351
|
+
# Right column - Terminal info
|
|
352
|
+
y = 5
|
|
353
|
+
self.renderer.write_at(right_x, y, "TERMINAL INFORMATION", ColorPalette.BRIGHT_YELLOW)
|
|
354
|
+
y += 2
|
|
355
|
+
self.renderer.write_at(right_x, y, f"Size: {width} x {height}", ColorPalette.WHITE)
|
|
356
|
+
y += 1
|
|
357
|
+
self.renderer.write_at(right_x, y, f"Colors: 256-color capable", ColorPalette.WHITE)
|
|
358
|
+
y += 1
|
|
359
|
+
self.renderer.write_at(right_x, y, f"Unicode: ✓ Supported", ColorPalette.WHITE)
|
|
360
|
+
y += 1
|
|
361
|
+
self.renderer.write_at(right_x, y, f"Buffering: ✓ Enabled", ColorPalette.WHITE)
|
|
362
|
+
y += 2
|
|
363
|
+
|
|
364
|
+
self.renderer.write_at(right_x, y, "FRAMEWORK FEATURES", ColorPalette.BRIGHT_CYAN)
|
|
365
|
+
y += 2
|
|
126
366
|
features = [
|
|
127
|
-
"
|
|
128
|
-
"
|
|
129
|
-
"
|
|
130
|
-
"
|
|
131
|
-
"
|
|
367
|
+
"✓ Frame buffering (flicker-free)",
|
|
368
|
+
"✓ Adaptive FPS control",
|
|
369
|
+
"✓ Modal system integration",
|
|
370
|
+
"✓ Drawing primitives library",
|
|
371
|
+
"✓ Animation framework",
|
|
372
|
+
"✓ Event-driven architecture",
|
|
132
373
|
]
|
|
374
|
+
for feature in features:
|
|
375
|
+
self.renderer.write_at(right_x, y, feature, ColorPalette.GREEN)
|
|
376
|
+
y += 1
|
|
377
|
+
|
|
378
|
+
# Live FPS graph
|
|
379
|
+
graph_y = height - 10
|
|
380
|
+
self.renderer.write_at(10, graph_y, "FPS GRAPH (last 50 frames)", ColorPalette.DIM_WHITE)
|
|
381
|
+
if len(self.perf_fps_history) > 1:
|
|
382
|
+
self._draw_fps_graph(15, graph_y + 1, 50, 5, self.perf_fps_history[-50:])
|
|
383
|
+
|
|
384
|
+
def _draw_fps_graph(self, x: int, y: int, width: int, height: int, data: List[float]):
|
|
385
|
+
"""Draw a simple bar graph of FPS data."""
|
|
386
|
+
if not data:
|
|
387
|
+
return
|
|
388
|
+
|
|
389
|
+
max_fps = max(data) if data else 60
|
|
390
|
+
min_fps = min(data) if data else 0
|
|
133
391
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
)
|
|
392
|
+
# Draw bars
|
|
393
|
+
for i, fps in enumerate(data[-width:]):
|
|
394
|
+
bar_x = x + i
|
|
395
|
+
normalized = (fps - min_fps) / (max_fps - min_fps) if max_fps > min_fps else 0.5
|
|
396
|
+
bar_height = int(normalized * height)
|
|
140
397
|
|
|
141
|
-
|
|
398
|
+
for j in range(bar_height):
|
|
399
|
+
bar_y = y + height - 1 - j
|
|
400
|
+
if bar_y >= y:
|
|
401
|
+
char = "█" if j < bar_height - 1 else "▀"
|
|
402
|
+
color = ColorPalette.BRIGHT_GREEN if fps >= 25 else ColorPalette.YELLOW
|
|
403
|
+
self.renderer.write_at(bar_x, bar_y, char, color)
|
|
404
|
+
|
|
405
|
+
def _render_drawing_page(self, width: int, height: int):
|
|
142
406
|
"""Render drawing primitives demonstration."""
|
|
143
|
-
DrawingPrimitives.draw_text_centered(
|
|
144
|
-
self.renderer, 2,
|
|
145
|
-
"Drawing Primitives Demo",
|
|
146
|
-
ColorPalette.BRIGHT_MAGENTA
|
|
147
|
-
)
|
|
407
|
+
DrawingPrimitives.draw_text_centered(self.renderer, 2, "🎨 DRAWING PRIMITIVES", ColorPalette.BRIGHT_MAGENTA)
|
|
148
408
|
|
|
149
|
-
# Draw various shapes and elements
|
|
150
409
|
center_x, center_y = width // 2, height // 2
|
|
151
410
|
|
|
152
|
-
# Border
|
|
153
|
-
DrawingPrimitives.draw_border(
|
|
154
|
-
self.renderer, center_x - 20, center_y - 8, 40, 16,
|
|
155
|
-
color=ColorPalette.CYAN
|
|
156
|
-
)
|
|
411
|
+
# Border box
|
|
412
|
+
DrawingPrimitives.draw_border(self.renderer, center_x - 25, center_y - 8, 50, 16, color=ColorPalette.CYAN)
|
|
157
413
|
|
|
158
|
-
# Progress bar
|
|
414
|
+
# Progress bar (animated)
|
|
159
415
|
progress = (self.frame_count % 100) / 100.0
|
|
160
|
-
DrawingPrimitives.draw_progress_bar(
|
|
161
|
-
|
|
162
|
-
color=ColorPalette.GREEN
|
|
163
|
-
)
|
|
164
|
-
DrawingPrimitives.draw_text_centered(
|
|
165
|
-
self.renderer, center_y - 6,
|
|
166
|
-
f"Progress: {progress:.0%}",
|
|
167
|
-
ColorPalette.WHITE
|
|
168
|
-
)
|
|
416
|
+
DrawingPrimitives.draw_progress_bar(self.renderer, center_x - 20, center_y - 5, 40, progress, color=ColorPalette.GREEN)
|
|
417
|
+
self.renderer.write_at(center_x - 10, center_y - 6, f"Progress: {progress:.0%}", ColorPalette.WHITE)
|
|
169
418
|
|
|
170
419
|
# Spinner
|
|
171
|
-
DrawingPrimitives.draw_spinner(
|
|
172
|
-
self.renderer, center_x - 2, center_y - 2, self.frame_count // 5,
|
|
173
|
-
color=ColorPalette.BRIGHT_BLUE
|
|
174
|
-
)
|
|
420
|
+
DrawingPrimitives.draw_spinner(self.renderer, center_x - 2, center_y - 2, self.frame_count // 3, color=ColorPalette.BRIGHT_BLUE)
|
|
175
421
|
self.renderer.write_at(center_x + 2, center_y - 2, "Loading...", ColorPalette.WHITE)
|
|
176
422
|
|
|
177
|
-
# Circle
|
|
178
|
-
|
|
179
|
-
DrawingPrimitives.draw_circle_points(
|
|
180
|
-
self.renderer, center_x, center_y + 3, radius,
|
|
181
|
-
char="●", color=ColorPalette.RED
|
|
182
|
-
)
|
|
423
|
+
# Circle
|
|
424
|
+
DrawingPrimitives.draw_circle_points(self.renderer, center_x, center_y + 3, 6, char="●", color=ColorPalette.RED)
|
|
183
425
|
|
|
184
|
-
# Wave
|
|
426
|
+
# Wave animation
|
|
185
427
|
wave_phase = self.frame_count * 0.1
|
|
186
|
-
DrawingPrimitives.draw_wave(
|
|
187
|
-
self.renderer, height - 5, 2, 0.3, wave_phase,
|
|
188
|
-
char="~", color=ColorPalette.BLUE
|
|
189
|
-
)
|
|
428
|
+
DrawingPrimitives.draw_wave(self.renderer, height - 6, 2, 0.3, wave_phase, char="~", color=ColorPalette.BLUE)
|
|
190
429
|
|
|
191
|
-
|
|
192
|
-
"
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
ColorPalette.BRIGHT_YELLOW
|
|
197
|
-
)
|
|
430
|
+
# Text samples
|
|
431
|
+
self.renderer.write_at(5, 5, "Box drawing: ╔═╗ ║ ╚═╝", ColorPalette.WHITE)
|
|
432
|
+
self.renderer.write_at(5, 6, "Blocks: █ ▓ ▒ ░ ▀ ▄ ▌ ▐", ColorPalette.WHITE)
|
|
433
|
+
self.renderer.write_at(5, 7, "Arrows: ← ↑ → ↓ ↔ ↕", ColorPalette.WHITE)
|
|
434
|
+
self.renderer.write_at(5, 8, "Symbols: ✓ ✗ ★ ● ○ ◆ ◇", ColorPalette.WHITE)
|
|
198
435
|
|
|
199
|
-
|
|
436
|
+
def _render_color_page(self, width: int, height: int):
|
|
437
|
+
"""Render 256-color palette and gradients."""
|
|
438
|
+
DrawingPrimitives.draw_text_centered(self.renderer, 2, "🌈 COLOR SHOWCASE", ColorPalette.BRIGHT_MAGENTA)
|
|
439
|
+
|
|
440
|
+
# 256-color grid (basic 16 + 216 color cube + 24 grayscale)
|
|
441
|
+
y = 5
|
|
442
|
+
self.renderer.write_at(10, y, "256 COLOR PALETTE", ColorPalette.WHITE)
|
|
443
|
+
y += 2
|
|
444
|
+
|
|
445
|
+
# Draw color blocks in grid
|
|
446
|
+
for row in range(16):
|
|
447
|
+
x = 10
|
|
448
|
+
for col in range(16):
|
|
449
|
+
color_num = row * 16 + col
|
|
450
|
+
# ANSI 256-color escape code
|
|
451
|
+
color_code = f"\033[48;5;{color_num}m"
|
|
452
|
+
self.renderer.write_at(x + col * 2, y + row, f"{color_code} \033[0m", "")
|
|
453
|
+
|
|
454
|
+
# Gradient examples
|
|
455
|
+
grad_y = 5
|
|
456
|
+
grad_x = width // 2 + 10
|
|
457
|
+
self.renderer.write_at(grad_x, grad_y, "GRADIENT EXAMPLES", ColorPalette.WHITE)
|
|
458
|
+
grad_y += 2
|
|
459
|
+
|
|
460
|
+
# Horizontal gradient
|
|
461
|
+
gradient_text = "Horizontal Gradient Demo"
|
|
462
|
+
colored = GradientRenderer.apply_dim_scheme_gradient(gradient_text)
|
|
463
|
+
self.renderer.write_at(grad_x, grad_y, colored, "")
|
|
464
|
+
grad_y += 2
|
|
465
|
+
|
|
466
|
+
# Color names
|
|
467
|
+
colors = [
|
|
468
|
+
("RED", ColorPalette.RED),
|
|
469
|
+
("GREEN", ColorPalette.GREEN),
|
|
470
|
+
("BLUE", ColorPalette.BLUE),
|
|
471
|
+
("YELLOW", ColorPalette.YELLOW),
|
|
472
|
+
("MAGENTA", ColorPalette.MAGENTA),
|
|
473
|
+
("CYAN", ColorPalette.CYAN),
|
|
474
|
+
("BRIGHT_WHITE", ColorPalette.BRIGHT_WHITE),
|
|
475
|
+
("DIM_GREY", ColorPalette.DIM_GREY),
|
|
476
|
+
]
|
|
477
|
+
|
|
478
|
+
for name, color in colors:
|
|
479
|
+
self.renderer.write_at(grad_x, grad_y, f"{name:15} ████████", color)
|
|
480
|
+
grad_y += 1
|
|
481
|
+
|
|
482
|
+
def _render_animation_page(self, width: int, height: int):
|
|
483
|
+
"""Render advanced animation demonstrations."""
|
|
484
|
+
DrawingPrimitives.draw_text_centered(self.renderer, 2, "✨ ANIMATION SHOWCASE", ColorPalette.BRIGHT_YELLOW)
|
|
200
485
|
|
|
201
|
-
# Create cycling animations
|
|
202
486
|
current_time = asyncio.get_event_loop().time()
|
|
487
|
+
center_x, center_y = width // 2, height // 2
|
|
203
488
|
|
|
204
|
-
# Pulsing circle
|
|
489
|
+
# Pulsing circle (sine wave)
|
|
205
490
|
pulse_size = 5 + int(3 * math.sin(current_time * 2))
|
|
206
491
|
for r in range(1, pulse_size):
|
|
207
|
-
DrawingPrimitives.draw_circle_points(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
self.renderer, center_y + 3,
|
|
223
|
-
fade_text,
|
|
224
|
-
ColorPalette.MAGENTA
|
|
225
|
-
)
|
|
492
|
+
DrawingPrimitives.draw_circle_points(self.renderer, center_x, center_y, r, char="○", color=ColorPalette.GREEN)
|
|
493
|
+
|
|
494
|
+
# Sliding text with easing
|
|
495
|
+
slide_x = int(20 * math.sin(current_time * 1.5))
|
|
496
|
+
self.renderer.write_at(center_x + slide_x, center_y - 7, "← Sliding →", ColorPalette.CYAN)
|
|
497
|
+
|
|
498
|
+
# Particle effect (rising dots)
|
|
499
|
+
for i in range(20):
|
|
500
|
+
particle_age = (current_time * 2 + i * 0.5) % 3
|
|
501
|
+
particle_y = center_y + 10 - int(particle_age * 5)
|
|
502
|
+
particle_x = center_x - 10 + i * 2
|
|
503
|
+
if particle_y < height - 3 and particle_y > center_y - 10:
|
|
504
|
+
alpha = 1.0 - (particle_age / 3.0)
|
|
505
|
+
char = "●" if alpha > 0.5 else "○"
|
|
506
|
+
self.renderer.write_at(particle_x, particle_y, char, ColorPalette.BRIGHT_BLUE)
|
|
226
507
|
|
|
227
508
|
# Bouncing ball
|
|
228
|
-
bounce_y = center_y +
|
|
229
|
-
self.renderer.write_at(center_x, bounce_y, "●", ColorPalette.RED)
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
"""
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
self.
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
509
|
+
bounce_y = center_y + 5 + int(3 * abs(math.sin(current_time * 3)))
|
|
510
|
+
self.renderer.write_at(center_x + 15, bounce_y, "●", ColorPalette.RED)
|
|
511
|
+
|
|
512
|
+
# Rotating spinner variations
|
|
513
|
+
spinners = ["⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏", "◐◓◑◒", "▁▃▄▅▆▇█▇▆▅▄▃", "←↖↑↗→↘↓↙"]
|
|
514
|
+
y = 5
|
|
515
|
+
for i, spinner in enumerate(spinners):
|
|
516
|
+
idx = (self.frame_count // 2) % len(spinner)
|
|
517
|
+
self.renderer.write_at(10, y + i * 2, f"Spinner {i+1}: {spinner[idx]}", ColorPalette.BRIGHT_GREEN)
|
|
518
|
+
|
|
519
|
+
# Easing function demo
|
|
520
|
+
easing_y = center_y - 10
|
|
521
|
+
easing_x = 10
|
|
522
|
+
self.renderer.write_at(easing_x, easing_y, "EASING FUNCTIONS:", ColorPalette.WHITE)
|
|
523
|
+
easing_y += 1
|
|
524
|
+
|
|
525
|
+
# Linear
|
|
526
|
+
t = (current_time % 2) / 2
|
|
527
|
+
pos = int(t * 30)
|
|
528
|
+
self.renderer.write_at(easing_x, easing_y, "Linear: ", ColorPalette.DIM_WHITE)
|
|
529
|
+
self.renderer.write_at(easing_x + 10 + pos, easing_y, "●", ColorPalette.YELLOW)
|
|
530
|
+
easing_y += 1
|
|
531
|
+
|
|
532
|
+
# Ease in/out
|
|
533
|
+
ease_t = -2 * t * t * t + 3 * t * t # Smooth step
|
|
534
|
+
ease_pos = int(ease_t * 30)
|
|
535
|
+
self.renderer.write_at(easing_x, easing_y, "Smooth: ", ColorPalette.DIM_WHITE)
|
|
536
|
+
self.renderer.write_at(easing_x + 10 + ease_pos, easing_y, "●", ColorPalette.GREEN)
|
|
537
|
+
|
|
538
|
+
def _render_interactive_page(self, width: int, height: int):
|
|
539
|
+
"""Render interactive components demonstration."""
|
|
540
|
+
DrawingPrimitives.draw_text_centered(self.renderer, 2, "🎮 INTERACTIVE COMPONENTS", ColorPalette.BRIGHT_GREEN)
|
|
541
|
+
|
|
542
|
+
y = 6
|
|
543
|
+
x = 10
|
|
544
|
+
|
|
545
|
+
# Instructions
|
|
546
|
+
self.renderer.write_at(x, y, "Use Tab to switch between components, arrow keys to interact", ColorPalette.DIM_WHITE)
|
|
547
|
+
y += 3
|
|
548
|
+
|
|
549
|
+
# Text input
|
|
550
|
+
is_focused = self.active_input == 0
|
|
551
|
+
label_color = ColorPalette.BRIGHT_YELLOW if is_focused else ColorPalette.WHITE
|
|
552
|
+
self.renderer.write_at(x, y, "Text Input:", label_color)
|
|
553
|
+
input_display = self.text_input.render_with_cursor() if is_focused else self.text_input.value
|
|
554
|
+
input_color = ColorPalette.BRIGHT_WHITE if is_focused else ColorPalette.DIM_WHITE
|
|
555
|
+
self.renderer.write_at(x + 15, y, f"[{input_display}]", input_color)
|
|
556
|
+
y += 3
|
|
557
|
+
|
|
558
|
+
# Checkboxes
|
|
559
|
+
is_focused = self.active_input == 1
|
|
560
|
+
label_color = ColorPalette.BRIGHT_YELLOW if is_focused else ColorPalette.WHITE
|
|
561
|
+
self.renderer.write_at(x, y, "Checkboxes:", label_color)
|
|
562
|
+
y += 1
|
|
563
|
+
for i, (label, checked) in enumerate([("Option A", self.checkbox_states[0]),
|
|
564
|
+
("Option B", self.checkbox_states[1]),
|
|
565
|
+
("Option C", self.checkbox_states[2])]):
|
|
566
|
+
box = "[✓]" if checked else "[ ]"
|
|
567
|
+
color = ColorPalette.BRIGHT_GREEN if checked else ColorPalette.DIM_WHITE
|
|
568
|
+
if is_focused and i == 0:
|
|
569
|
+
color = ColorPalette.BRIGHT_YELLOW
|
|
570
|
+
self.renderer.write_at(x + 2, y, f"{box} {label}", color)
|
|
571
|
+
y += 1
|
|
572
|
+
y += 2
|
|
573
|
+
|
|
574
|
+
# Slider
|
|
575
|
+
is_focused = self.active_input == 2
|
|
576
|
+
label_color = ColorPalette.BRIGHT_YELLOW if is_focused else ColorPalette.WHITE
|
|
577
|
+
self.renderer.write_at(x, y, "Slider:", label_color)
|
|
578
|
+
slider_width = 40
|
|
579
|
+
filled = int((self.slider_value / 100.0) * slider_width)
|
|
580
|
+
slider_bar = "█" * filled + "░" * (slider_width - filled)
|
|
581
|
+
self.renderer.write_at(x + 10, y, f"[{slider_bar}] {self.slider_value}%", ColorPalette.CYAN)
|
|
582
|
+
y += 3
|
|
583
|
+
|
|
584
|
+
# Current value display
|
|
585
|
+
self.renderer.write_at(x, y, f"Active: Component {self.active_input + 1}/3", ColorPalette.DIM_WHITE)
|
|
586
|
+
y += 1
|
|
587
|
+
self.renderer.write_at(x, y, f"Text: '{self.text_input.value}'", ColorPalette.DIM_WHITE)
|
|
588
|
+
y += 1
|
|
589
|
+
self.renderer.write_at(x, y, f"Checks: {sum(self.checkbox_states)}/3 selected", ColorPalette.DIM_WHITE)
|
|
590
|
+
y += 1
|
|
591
|
+
self.renderer.write_at(x, y, f"Slider: {self.slider_value}%", ColorPalette.DIM_WHITE)
|
|
592
|
+
|
|
593
|
+
def _render_calculator_page(self, width: int, height: int):
|
|
594
|
+
"""Render functional calculator interface."""
|
|
595
|
+
DrawingPrimitives.draw_text_centered(self.renderer, 2, "🔢 CALCULATOR", ColorPalette.BRIGHT_CYAN)
|
|
596
|
+
|
|
597
|
+
center_x, center_y = width // 2, height // 2 - 3
|
|
598
|
+
|
|
599
|
+
# Display
|
|
600
|
+
display_width = 24
|
|
601
|
+
display_x = center_x - display_width // 2
|
|
602
|
+
display_y = center_y - 8
|
|
603
|
+
|
|
604
|
+
DrawingPrimitives.draw_border(self.renderer, display_x - 2, display_y - 1, display_width + 4, 3, color=ColorPalette.CYAN)
|
|
605
|
+
display_text = self.calc_display[:display_width - 2].rjust(display_width - 2)
|
|
606
|
+
self.renderer.write_at(display_x, display_y, display_text, ColorPalette.BRIGHT_WHITE)
|
|
607
|
+
|
|
608
|
+
# Number pad
|
|
609
|
+
pad_x = center_x - 10
|
|
610
|
+
pad_y = center_y - 4
|
|
611
|
+
|
|
612
|
+
buttons = [
|
|
613
|
+
["7", "8", "9", "/"],
|
|
614
|
+
["4", "5", "6", "*"],
|
|
615
|
+
["1", "2", "3", "-"],
|
|
616
|
+
["C", "0", "=", "+"],
|
|
617
|
+
]
|
|
618
|
+
|
|
619
|
+
for row_idx, row in enumerate(buttons):
|
|
620
|
+
for col_idx, btn in enumerate(row):
|
|
621
|
+
btn_x = pad_x + col_idx * 5
|
|
622
|
+
btn_y = pad_y + row_idx * 2
|
|
623
|
+
|
|
624
|
+
# Button styling
|
|
625
|
+
if btn in "0123456789":
|
|
626
|
+
color = ColorPalette.BRIGHT_WHITE
|
|
627
|
+
elif btn in "+-*/":
|
|
628
|
+
color = ColorPalette.BRIGHT_YELLOW
|
|
629
|
+
elif btn == "=":
|
|
630
|
+
color = ColorPalette.BRIGHT_GREEN
|
|
631
|
+
else:
|
|
632
|
+
color = ColorPalette.BRIGHT_RED
|
|
633
|
+
|
|
634
|
+
self.renderer.write_at(btn_x, btn_y, f"[{btn}]", color)
|
|
635
|
+
|
|
636
|
+
# Instructions
|
|
637
|
+
self.renderer.write_at(center_x - 15, height - 8, "Use number keys and operators: + - * /", ColorPalette.DIM_WHITE)
|
|
638
|
+
self.renderer.write_at(center_x - 15, height - 7, "Press = to calculate, C to clear", ColorPalette.DIM_WHITE)
|
|
639
|
+
|
|
640
|
+
def _render_todo_page(self, width: int, height: int):
|
|
641
|
+
"""Render todo list mini-app."""
|
|
642
|
+
DrawingPrimitives.draw_text_centered(self.renderer, 2, "📝 TODO LIST", ColorPalette.BRIGHT_GREEN)
|
|
643
|
+
|
|
644
|
+
y = 5
|
|
645
|
+
x = 10
|
|
646
|
+
|
|
647
|
+
# Instructions
|
|
648
|
+
if not self.todo_editing:
|
|
649
|
+
self.renderer.write_at(x, y, "Press 'a' to add, Space to toggle, 'd' to delete selected", ColorPalette.DIM_WHITE)
|
|
650
|
+
else:
|
|
651
|
+
self.renderer.write_at(x, y, "Type task and press Enter to add (Esc to cancel)", ColorPalette.BRIGHT_YELLOW)
|
|
652
|
+
y += 3
|
|
653
|
+
|
|
654
|
+
# Add input (if editing)
|
|
655
|
+
if self.todo_editing:
|
|
656
|
+
input_text = self.todo_input.render_with_cursor()
|
|
657
|
+
self.renderer.write_at(x, y, f"New task: [{input_text}]", ColorPalette.BRIGHT_CYAN)
|
|
658
|
+
y += 2
|
|
659
|
+
|
|
660
|
+
# Todo items
|
|
661
|
+
self.renderer.write_at(x, y, "TASKS:", ColorPalette.WHITE)
|
|
662
|
+
y += 1
|
|
663
|
+
|
|
664
|
+
for idx, todo in enumerate(self.todos):
|
|
665
|
+
checkbox = "[✓]" if todo.completed else "[ ]"
|
|
666
|
+
text_color = ColorPalette.DIM_WHITE if todo.completed else ColorPalette.WHITE
|
|
667
|
+
checkbox_color = ColorPalette.GREEN if todo.completed else ColorPalette.YELLOW
|
|
668
|
+
|
|
669
|
+
self.renderer.write_at(x, y, f"{idx + 1}. {checkbox}", checkbox_color)
|
|
670
|
+
|
|
671
|
+
# Strikethrough for completed
|
|
672
|
+
if todo.completed:
|
|
673
|
+
display_text = "".join(c + "\u0336" for c in todo.text) # Strikethrough
|
|
674
|
+
else:
|
|
675
|
+
display_text = todo.text
|
|
676
|
+
|
|
677
|
+
self.renderer.write_at(x + 9, y, display_text, text_color)
|
|
678
|
+
y += 1
|
|
679
|
+
|
|
680
|
+
# Stats
|
|
681
|
+
y += 2
|
|
682
|
+
total = len(self.todos)
|
|
683
|
+
completed = sum(1 for t in self.todos if t.completed)
|
|
684
|
+
remaining = total - completed
|
|
685
|
+
|
|
686
|
+
self.renderer.write_at(x, y, f"Total: {total} | Completed: {completed} | Remaining: {remaining}", ColorPalette.DIM_WHITE)
|
|
687
|
+
|
|
688
|
+
async def _render_life_page(self, width: int, height: int, delta_time: float):
|
|
689
|
+
"""Render Conway's Game of Life."""
|
|
690
|
+
DrawingPrimitives.draw_text_centered(self.renderer, 2, "🧬 GAME OF LIFE", ColorPalette.BRIGHT_MAGENTA)
|
|
691
|
+
|
|
692
|
+
# Update game if running
|
|
693
|
+
if self.life_running:
|
|
694
|
+
current_time = time.time()
|
|
695
|
+
if current_time - self.life_last_update >= self.life_speed:
|
|
696
|
+
self._update_life_generation()
|
|
697
|
+
self.life_last_update = current_time
|
|
698
|
+
|
|
699
|
+
# Instructions
|
|
700
|
+
instr_y = 4
|
|
701
|
+
self.renderer.write_at(10, instr_y, f"Space: Start/Stop | R: Random | C: Clear | +/-: Speed", ColorPalette.DIM_WHITE)
|
|
702
|
+
instr_y += 1
|
|
703
|
+
status = "RUNNING" if self.life_running else "PAUSED"
|
|
704
|
+
status_color = ColorPalette.BRIGHT_GREEN if self.life_running else ColorPalette.YELLOW
|
|
705
|
+
self.renderer.write_at(10, instr_y, f"Status: {status} | Gen: {self.life_generation} | Speed: {self.life_speed:.2f}s", status_color)
|
|
706
|
+
|
|
707
|
+
# Render grid
|
|
708
|
+
grid_start_x = (width - len(self.life_grid[0])) // 2
|
|
709
|
+
grid_start_y = 8
|
|
710
|
+
|
|
711
|
+
for row_idx, row in enumerate(self.life_grid):
|
|
712
|
+
for col_idx, cell in enumerate(row):
|
|
713
|
+
if cell:
|
|
714
|
+
self.renderer.write_at(grid_start_x + col_idx, grid_start_y + row_idx, "█", ColorPalette.BRIGHT_GREEN)
|
|
715
|
+
else:
|
|
716
|
+
self.renderer.write_at(grid_start_x + col_idx, grid_start_y + row_idx, "·", ColorPalette.DIM_GREY)
|
|
717
|
+
|
|
718
|
+
def _render_performance_page(self, width: int, height: int):
|
|
719
|
+
"""Render performance comparison and metrics."""
|
|
720
|
+
DrawingPrimitives.draw_text_centered(self.renderer, 2, "⚡ PERFORMANCE METRICS", ColorPalette.BRIGHT_YELLOW)
|
|
721
|
+
|
|
722
|
+
y = 5
|
|
723
|
+
x = 10
|
|
724
|
+
|
|
725
|
+
# Current FPS
|
|
726
|
+
avg_fps = sum(self.perf_fps_history[-30:]) / len(self.perf_fps_history[-30:]) if self.perf_fps_history else 0
|
|
727
|
+
self.renderer.write_at(x, y, f"Current FPS: {avg_fps:.1f} / {self.target_fps:.1f} target", ColorPalette.BRIGHT_GREEN)
|
|
728
|
+
y += 2
|
|
729
|
+
|
|
730
|
+
# Frame buffer info
|
|
731
|
+
self.renderer.write_at(x, y, "OPTIMIZATIONS ENABLED:", ColorPalette.WHITE)
|
|
732
|
+
y += 1
|
|
733
|
+
self.renderer.write_at(x + 2, y, "✓ Frame buffering (single atomic write)", ColorPalette.GREEN)
|
|
734
|
+
y += 1
|
|
735
|
+
self.renderer.write_at(x + 2, y, "✓ State tracking (skip unchanged frames)", ColorPalette.GREEN)
|
|
736
|
+
y += 1
|
|
737
|
+
self.renderer.write_at(x + 2, y, f"✓ Adaptive FPS ({self.target_fps:.0f} fps for this page)", ColorPalette.GREEN)
|
|
738
|
+
y += 3
|
|
739
|
+
|
|
740
|
+
# Benefits
|
|
741
|
+
self.renderer.write_at(x, y, "BENEFITS:", ColorPalette.YELLOW)
|
|
742
|
+
y += 1
|
|
743
|
+
self.renderer.write_at(x + 2, y, "→ Zero flicker (no visible blank frames)", ColorPalette.WHITE)
|
|
744
|
+
y += 1
|
|
745
|
+
self.renderer.write_at(x + 2, y, "→ 60x fewer syscalls per frame", ColorPalette.WHITE)
|
|
746
|
+
y += 1
|
|
747
|
+
self.renderer.write_at(x + 2, y, "→ Minimal CPU usage when idle", ColorPalette.WHITE)
|
|
748
|
+
y += 1
|
|
749
|
+
self.renderer.write_at(x + 2, y, "→ Smooth animations with consistent timing", ColorPalette.WHITE)
|
|
750
|
+
y += 3
|
|
751
|
+
|
|
752
|
+
# FPS graph
|
|
753
|
+
self.renderer.write_at(x, y, "FPS HISTORY (last 60 frames):", ColorPalette.DIM_WHITE)
|
|
754
|
+
y += 1
|
|
755
|
+
if self.perf_fps_history:
|
|
756
|
+
self._draw_fps_graph(x, y, 60, 6, self.perf_fps_history[-60:])
|
|
757
|
+
|
|
758
|
+
def _render_code_page(self, width: int, height: int):
|
|
759
|
+
"""Render code examples and templates."""
|
|
760
|
+
DrawingPrimitives.draw_text_centered(self.renderer, 2, "💻 CODE EXAMPLES", ColorPalette.BRIGHT_CYAN)
|
|
761
|
+
|
|
762
|
+
y = 5
|
|
763
|
+
x = 5
|
|
764
|
+
|
|
765
|
+
# Example 1: Basic plugin structure
|
|
766
|
+
self.renderer.write_at(x, y, "MINIMAL PLUGIN TEMPLATE:", ColorPalette.BRIGHT_YELLOW)
|
|
767
|
+
y += 2
|
|
239
768
|
|
|
240
|
-
# Plugin template example - more compact positioning
|
|
241
769
|
code_lines = [
|
|
242
|
-
"class MyPlugin(FullScreenPlugin):",
|
|
243
|
-
" async def render_frame(self, delta_time):",
|
|
244
|
-
" self.renderer.clear_screen()",
|
|
245
|
-
"
|
|
246
|
-
" return True",
|
|
247
|
-
"",
|
|
248
|
-
" async def handle_input(self, key_press):",
|
|
249
|
-
" return key_press.char == 'q'"
|
|
770
|
+
("class", "MyPlugin", "(", "FullScreenPlugin", "):"),
|
|
771
|
+
(" ", "async def", " ", "render_frame", "(", "self", ", ", "delta_time", "):"),
|
|
772
|
+
(" ", "self", ".", "renderer", ".", "clear_screen", "()"),
|
|
773
|
+
(" ", "self", ".", "renderer", ".", "write_at", "(", "10", ", ", "5", ", ", '"Hello!"', ")"),
|
|
774
|
+
(" ", "return", " ", "True"),
|
|
775
|
+
("", "", "", "", ""),
|
|
776
|
+
(" ", "async def", " ", "handle_input", "(", "self", ", ", "key_press", "):"),
|
|
777
|
+
(" ", "return", " ", "key_press", ".", "char", " == ", "'q'"),
|
|
250
778
|
]
|
|
251
779
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
780
|
+
for line_parts in code_lines:
|
|
781
|
+
line_x = x + 2
|
|
782
|
+
for part in line_parts:
|
|
783
|
+
if part in ["class", "async def", "return"]:
|
|
784
|
+
color = ColorPalette.BRIGHT_MAGENTA # Keywords
|
|
785
|
+
elif part in ["MyPlugin", "FullScreenPlugin", "render_frame", "handle_input"]:
|
|
786
|
+
color = ColorPalette.BRIGHT_CYAN # Classes/functions
|
|
787
|
+
elif part in ["self"]:
|
|
788
|
+
color = ColorPalette.BRIGHT_BLUE # Self
|
|
789
|
+
elif part.startswith('"'):
|
|
790
|
+
color = ColorPalette.BRIGHT_GREEN # Strings
|
|
791
|
+
elif part.isdigit():
|
|
792
|
+
color = ColorPalette.BRIGHT_YELLOW # Numbers
|
|
793
|
+
else:
|
|
794
|
+
color = ColorPalette.WHITE
|
|
265
795
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
796
|
+
self.renderer.write_at(line_x, y, part, color)
|
|
797
|
+
line_x += len(part)
|
|
798
|
+
y += 1
|
|
799
|
+
|
|
800
|
+
y += 2
|
|
801
|
+
|
|
802
|
+
# Key concepts
|
|
803
|
+
self.renderer.write_at(x, y, "KEY CONCEPTS:", ColorPalette.BRIGHT_GREEN)
|
|
804
|
+
y += 1
|
|
805
|
+
concepts = [
|
|
806
|
+
"→ render_frame(): Called every frame, return False to exit",
|
|
807
|
+
"→ handle_input(): Process key presses, return True to exit",
|
|
808
|
+
"→ target_fps: Set frame rate (15-60 fps recommended)",
|
|
809
|
+
"→ Use renderer.begin_frame() / end_frame() for buffering",
|
|
810
|
+
"→ Access width, height via renderer.get_terminal_size()",
|
|
811
|
+
]
|
|
812
|
+
for concept in concepts:
|
|
813
|
+
self.renderer.write_at(x + 2, y, concept, ColorPalette.DIM_WHITE)
|
|
814
|
+
y += 1
|
|
815
|
+
|
|
816
|
+
def _render_resources_page(self, width: int, height: int):
|
|
817
|
+
"""Render resources, documentation, and help."""
|
|
818
|
+
DrawingPrimitives.draw_text_centered(self.renderer, 2, "📚 RESOURCES & HELP", ColorPalette.BRIGHT_GREEN)
|
|
819
|
+
|
|
820
|
+
y = 5
|
|
821
|
+
x = 10
|
|
822
|
+
|
|
823
|
+
# Key bindings
|
|
824
|
+
self.renderer.write_at(x, y, "NAVIGATION:", ColorPalette.BRIGHT_YELLOW)
|
|
825
|
+
y += 1
|
|
826
|
+
bindings = [
|
|
827
|
+
("←/→ or h/l", "Previous/Next page"),
|
|
828
|
+
("0-9", "Jump to page by number"),
|
|
829
|
+
("Home", "First page (Table of Contents)"),
|
|
830
|
+
("End", "Last page (Resources)"),
|
|
831
|
+
("q or ESC", "Exit plugin"),
|
|
832
|
+
]
|
|
833
|
+
for key, desc in bindings:
|
|
834
|
+
self.renderer.write_at(x + 2, y, f"{key:12} - {desc}", ColorPalette.WHITE)
|
|
835
|
+
y += 1
|
|
836
|
+
|
|
837
|
+
y += 2
|
|
838
|
+
|
|
839
|
+
# Page-specific controls
|
|
840
|
+
self.renderer.write_at(x, y, "PAGE-SPECIFIC CONTROLS:", ColorPalette.BRIGHT_CYAN)
|
|
841
|
+
y += 1
|
|
842
|
+
controls = [
|
|
843
|
+
("Calculator", "Number keys, +, -, *, /, =, C"),
|
|
844
|
+
("Todo List", "a=add, Space=toggle, d=delete"),
|
|
845
|
+
("Game of Life", "Space=start/stop, r=random, c=clear"),
|
|
846
|
+
("Interactive", "Tab=switch component, arrows=adjust"),
|
|
272
847
|
]
|
|
848
|
+
for page, keys in controls:
|
|
849
|
+
self.renderer.write_at(x + 2, y, f"{page:15} → {keys}", ColorPalette.DIM_WHITE)
|
|
850
|
+
y += 1
|
|
273
851
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
852
|
+
y += 2
|
|
853
|
+
|
|
854
|
+
# Documentation
|
|
855
|
+
self.renderer.write_at(x, y, "DOCUMENTATION:", ColorPalette.BRIGHT_MAGENTA)
|
|
856
|
+
y += 1
|
|
857
|
+
docs = [
|
|
858
|
+
"→ Plugin development guide in docs/",
|
|
859
|
+
"→ API reference in core/fullscreen/",
|
|
860
|
+
"→ More examples in plugins/fullscreen/",
|
|
861
|
+
"→ Framework source code is extensively commented",
|
|
862
|
+
]
|
|
863
|
+
for doc in docs:
|
|
864
|
+
self.renderer.write_at(x + 2, y, doc, ColorPalette.WHITE)
|
|
865
|
+
y += 1
|
|
866
|
+
|
|
867
|
+
y += 2
|
|
868
|
+
|
|
869
|
+
# Credits
|
|
870
|
+
self.renderer.write_at(x, y, "Built with Kollabor Framework 🎯", ColorPalette.BRIGHT_WHITE)
|
|
280
871
|
|
|
281
872
|
def _render_navigation(self, width: int, height: int):
|
|
282
|
-
"""Render navigation
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
873
|
+
"""Render navigation footer."""
|
|
874
|
+
footer_y = height - 1
|
|
875
|
+
|
|
876
|
+
# Page info
|
|
877
|
+
page_info = f"Page {self.current_page + 1}/{self.total_pages}: {self.pages[self.current_page].title}"
|
|
878
|
+
DrawingPrimitives.draw_text_centered(self.renderer, footer_y, page_info, ColorPalette.DIM_WHITE)
|
|
879
|
+
|
|
880
|
+
# Navigation hints (left side)
|
|
881
|
+
nav_hint = "←→/h/l: Navigate | 0-9: Jump | q/ESC: Exit"
|
|
882
|
+
self.renderer.write_at(2, footer_y, nav_hint, ColorPalette.DIM_GREY)
|
|
883
|
+
|
|
884
|
+
def _init_life_grid(self):
|
|
885
|
+
"""Initialize Game of Life grid."""
|
|
886
|
+
grid_width = 60
|
|
887
|
+
grid_height = 20
|
|
888
|
+
|
|
889
|
+
self.life_grid = [[False for _ in range(grid_width)] for _ in range(grid_height)]
|
|
890
|
+
|
|
891
|
+
# Add a glider
|
|
892
|
+
self.life_grid[5][5] = True
|
|
893
|
+
self.life_grid[6][6] = True
|
|
894
|
+
self.life_grid[7][4] = True
|
|
895
|
+
self.life_grid[7][5] = True
|
|
896
|
+
self.life_grid[7][6] = True
|
|
897
|
+
|
|
898
|
+
def _randomize_life_grid(self):
|
|
899
|
+
"""Randomize Game of Life grid."""
|
|
900
|
+
for row in range(len(self.life_grid)):
|
|
901
|
+
for col in range(len(self.life_grid[0])):
|
|
902
|
+
self.life_grid[row][col] = random.random() < 0.3
|
|
903
|
+
self.life_generation = 0
|
|
904
|
+
|
|
905
|
+
def _clear_life_grid(self):
|
|
906
|
+
"""Clear Game of Life grid."""
|
|
907
|
+
for row in range(len(self.life_grid)):
|
|
908
|
+
for col in range(len(self.life_grid[0])):
|
|
909
|
+
self.life_grid[row][col] = False
|
|
910
|
+
self.life_generation = 0
|
|
911
|
+
|
|
912
|
+
def _update_life_generation(self):
|
|
913
|
+
"""Update Game of Life to next generation."""
|
|
914
|
+
rows = len(self.life_grid)
|
|
915
|
+
cols = len(self.life_grid[0])
|
|
916
|
+
new_grid = [[False for _ in range(cols)] for _ in range(rows)]
|
|
917
|
+
|
|
918
|
+
for row in range(rows):
|
|
919
|
+
for col in range(cols):
|
|
920
|
+
# Count neighbors
|
|
921
|
+
neighbors = 0
|
|
922
|
+
for dr in [-1, 0, 1]:
|
|
923
|
+
for dc in [-1, 0, 1]:
|
|
924
|
+
if dr == 0 and dc == 0:
|
|
925
|
+
continue
|
|
926
|
+
nr, nc = row + dr, col + dc
|
|
927
|
+
if 0 <= nr < rows and 0 <= nc < cols and self.life_grid[nr][nc]:
|
|
928
|
+
neighbors += 1
|
|
929
|
+
|
|
930
|
+
# Apply rules
|
|
931
|
+
if self.life_grid[row][col]:
|
|
932
|
+
# Live cell
|
|
933
|
+
new_grid[row][col] = neighbors in [2, 3]
|
|
934
|
+
else:
|
|
935
|
+
# Dead cell
|
|
936
|
+
new_grid[row][col] = neighbors == 3
|
|
937
|
+
|
|
938
|
+
self.life_grid = new_grid
|
|
939
|
+
self.life_generation += 1
|
|
940
|
+
|
|
941
|
+
def _handle_calculator_input(self, key_press: KeyPress) -> bool:
|
|
942
|
+
"""Handle calculator key presses."""
|
|
943
|
+
if key_press.char and key_press.char in "0123456789":
|
|
944
|
+
# Number input
|
|
945
|
+
if self.calc_new_number:
|
|
946
|
+
self.calc_display = key_press.char
|
|
947
|
+
self.calc_new_number = False
|
|
948
|
+
else:
|
|
949
|
+
if len(self.calc_display) < 10:
|
|
950
|
+
self.calc_display += key_press.char
|
|
951
|
+
return False
|
|
952
|
+
|
|
953
|
+
elif key_press.char and key_press.char in "+-*/":
|
|
954
|
+
# Operator
|
|
955
|
+
if self.calc_operator and not self.calc_new_number:
|
|
956
|
+
# Calculate previous operation
|
|
957
|
+
self._calculate()
|
|
958
|
+
self.calc_operand = float(self.calc_display)
|
|
959
|
+
self.calc_operator = key_press.char
|
|
960
|
+
self.calc_new_number = True
|
|
961
|
+
return False
|
|
962
|
+
|
|
963
|
+
elif key_press.char and key_press.char in "=\r\n":
|
|
964
|
+
# Equals
|
|
965
|
+
self._calculate()
|
|
966
|
+
return False
|
|
967
|
+
|
|
968
|
+
elif key_press.char and key_press.char.lower() == 'c':
|
|
969
|
+
# Clear
|
|
970
|
+
self.calc_display = "0"
|
|
971
|
+
self.calc_operator = None
|
|
972
|
+
self.calc_operand = None
|
|
973
|
+
self.calc_new_number = True
|
|
974
|
+
return False
|
|
975
|
+
|
|
976
|
+
elif key_press.char == '.':
|
|
977
|
+
# Decimal point
|
|
978
|
+
if '.' not in self.calc_display:
|
|
979
|
+
self.calc_display += '.'
|
|
980
|
+
return False
|
|
981
|
+
|
|
982
|
+
return False
|
|
983
|
+
|
|
984
|
+
def _calculate(self):
|
|
985
|
+
"""Perform calculator calculation."""
|
|
986
|
+
if self.calc_operator and self.calc_operand is not None:
|
|
987
|
+
try:
|
|
988
|
+
current = float(self.calc_display)
|
|
989
|
+
|
|
990
|
+
if self.calc_operator == '+':
|
|
991
|
+
result = self.calc_operand + current
|
|
992
|
+
elif self.calc_operator == '-':
|
|
993
|
+
result = self.calc_operand - current
|
|
994
|
+
elif self.calc_operator == '*':
|
|
995
|
+
result = self.calc_operand * current
|
|
996
|
+
elif self.calc_operator == '/':
|
|
997
|
+
if current != 0:
|
|
998
|
+
result = self.calc_operand / current
|
|
999
|
+
else:
|
|
1000
|
+
self.calc_display = "Error"
|
|
1001
|
+
self.calc_operator = None
|
|
1002
|
+
self.calc_operand = None
|
|
1003
|
+
self.calc_new_number = True
|
|
1004
|
+
return
|
|
1005
|
+
else:
|
|
1006
|
+
return
|
|
1007
|
+
|
|
1008
|
+
# Format result
|
|
1009
|
+
if result == int(result):
|
|
1010
|
+
self.calc_display = str(int(result))
|
|
1011
|
+
else:
|
|
1012
|
+
self.calc_display = f"{result:.6f}".rstrip('0').rstrip('.')
|
|
1013
|
+
|
|
1014
|
+
self.calc_operator = None
|
|
1015
|
+
self.calc_operand = None
|
|
1016
|
+
self.calc_new_number = True
|
|
1017
|
+
except:
|
|
1018
|
+
self.calc_display = "Error"
|
|
1019
|
+
self.calc_operator = None
|
|
1020
|
+
self.calc_operand = None
|
|
1021
|
+
self.calc_new_number = True
|
|
289
1022
|
|
|
290
1023
|
async def handle_input(self, key_press: KeyPress) -> bool:
|
|
291
|
-
"""Handle input for
|
|
292
|
-
# Exit
|
|
1024
|
+
"""Handle user input for navigation and interactions."""
|
|
1025
|
+
# Global: Exit
|
|
293
1026
|
if key_press.char in ['q', '\x1b'] or key_press.name == "Escape":
|
|
1027
|
+
# Cancel editing mode if active
|
|
1028
|
+
if self.current_page == 7 and self.todo_editing:
|
|
1029
|
+
self.todo_editing = False
|
|
1030
|
+
self.todo_input = SimpleTextInput()
|
|
1031
|
+
return False
|
|
294
1032
|
return True
|
|
295
1033
|
|
|
296
|
-
#
|
|
1034
|
+
# Page-specific input handling
|
|
1035
|
+
if self.current_page == 5: # Interactive components
|
|
1036
|
+
# Tab to switch components
|
|
1037
|
+
if key_press.name == "Tab" or key_press.char == '\t':
|
|
1038
|
+
self.active_input = (self.active_input + 1) % 3
|
|
1039
|
+
return False
|
|
1040
|
+
|
|
1041
|
+
# Component-specific input
|
|
1042
|
+
if self.active_input == 0: # Text input
|
|
1043
|
+
self.text_input.handle_key(key_press)
|
|
1044
|
+
return False
|
|
1045
|
+
elif self.active_input == 1: # Checkboxes
|
|
1046
|
+
if key_press.char == ' ':
|
|
1047
|
+
self.checkbox_states[0] = not self.checkbox_states[0]
|
|
1048
|
+
elif key_press.name == "ArrowDown":
|
|
1049
|
+
# Rotate through checkboxes
|
|
1050
|
+
self.checkbox_states.append(self.checkbox_states.pop(0))
|
|
1051
|
+
return False
|
|
1052
|
+
elif self.active_input == 2: # Slider
|
|
1053
|
+
if key_press.name == "ArrowLeft":
|
|
1054
|
+
self.slider_value = max(0, self.slider_value - 5)
|
|
1055
|
+
elif key_press.name == "ArrowRight":
|
|
1056
|
+
self.slider_value = min(100, self.slider_value + 5)
|
|
1057
|
+
return False
|
|
1058
|
+
|
|
1059
|
+
elif self.current_page == 6: # Calculator
|
|
1060
|
+
return self._handle_calculator_input(key_press)
|
|
1061
|
+
|
|
1062
|
+
elif self.current_page == 7: # Todo list
|
|
1063
|
+
if self.todo_editing:
|
|
1064
|
+
# Handle todo input
|
|
1065
|
+
if key_press.name == "Enter" or key_press.char in ['\r', '\n']:
|
|
1066
|
+
if self.todo_input.value.strip():
|
|
1067
|
+
self.todos.append(TodoItem(self.todo_input.value.strip(), False))
|
|
1068
|
+
self.todo_editing = False
|
|
1069
|
+
self.todo_input = SimpleTextInput()
|
|
1070
|
+
return False
|
|
1071
|
+
else:
|
|
1072
|
+
self.todo_input.handle_key(key_press)
|
|
1073
|
+
return False
|
|
1074
|
+
else:
|
|
1075
|
+
# Todo list commands
|
|
1076
|
+
if key_press.char and key_press.char.lower() == 'a':
|
|
1077
|
+
self.todo_editing = True
|
|
1078
|
+
return False
|
|
1079
|
+
elif key_press.char == ' ' and self.todos:
|
|
1080
|
+
# Toggle first uncompleted todo
|
|
1081
|
+
for todo in self.todos:
|
|
1082
|
+
if not todo.completed:
|
|
1083
|
+
todo.completed = True
|
|
1084
|
+
break
|
|
1085
|
+
return False
|
|
1086
|
+
elif key_press.char and key_press.char.lower() == 'd' and self.todos:
|
|
1087
|
+
# Delete first completed todo
|
|
1088
|
+
for i, todo in enumerate(self.todos):
|
|
1089
|
+
if todo.completed:
|
|
1090
|
+
self.todos.pop(i)
|
|
1091
|
+
break
|
|
1092
|
+
return False
|
|
1093
|
+
|
|
1094
|
+
elif self.current_page == 8: # Game of Life
|
|
1095
|
+
if key_press.char == ' ':
|
|
1096
|
+
self.life_running = not self.life_running
|
|
1097
|
+
if self.life_running:
|
|
1098
|
+
self.life_last_update = time.time()
|
|
1099
|
+
return False
|
|
1100
|
+
elif key_press.char and key_press.char.lower() == 'r':
|
|
1101
|
+
self._randomize_life_grid()
|
|
1102
|
+
return False
|
|
1103
|
+
elif key_press.char and key_press.char.lower() == 'c':
|
|
1104
|
+
self._clear_life_grid()
|
|
1105
|
+
self.life_running = False
|
|
1106
|
+
return False
|
|
1107
|
+
elif key_press.char == '+':
|
|
1108
|
+
self.life_speed = max(0.05, self.life_speed - 0.05)
|
|
1109
|
+
return False
|
|
1110
|
+
elif key_press.char == '-':
|
|
1111
|
+
self.life_speed = min(2.0, self.life_speed + 0.05)
|
|
1112
|
+
return False
|
|
1113
|
+
|
|
1114
|
+
# Global navigation
|
|
297
1115
|
if key_press.char == 'h' or key_press.char == 'a' or key_press.name == "ArrowLeft":
|
|
298
1116
|
if self.current_page > 0:
|
|
299
1117
|
self.current_page -= 1
|
|
300
|
-
await self._start_page_transition()
|
|
301
1118
|
elif key_press.char == 'l' or key_press.char == 'd' or key_press.name == "ArrowRight":
|
|
302
1119
|
if self.current_page < self.total_pages - 1:
|
|
303
1120
|
self.current_page += 1
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
1121
|
+
elif key_press.name == "Home":
|
|
1122
|
+
self.current_page = 0
|
|
1123
|
+
elif key_press.name == "End":
|
|
1124
|
+
self.current_page = self.total_pages - 1
|
|
1125
|
+
elif key_press.char and key_press.char.isdigit():
|
|
1126
|
+
# Jump to page by number
|
|
1127
|
+
page_num = int(key_press.char)
|
|
1128
|
+
if 0 <= page_num < self.total_pages:
|
|
1129
|
+
self.current_page = page_num
|
|
310
1130
|
|
|
311
1131
|
return False
|
|
312
1132
|
|
|
313
|
-
async def _start_page_transition(self):
|
|
314
|
-
"""Start page transition animation."""
|
|
315
|
-
current_time = asyncio.get_event_loop().time()
|
|
316
|
-
# Could add page transition animations here
|
|
317
|
-
# For now, just reset some demo animations
|
|
318
|
-
self.demo_animations['bounce'] = self.animation_framework.bounce_in(0.5, current_time)
|
|
319
|
-
|
|
320
1133
|
async def on_stop(self):
|
|
321
|
-
"""Called when
|
|
1134
|
+
"""Called when plugin stops."""
|
|
322
1135
|
await super().on_stop()
|
|
323
1136
|
|
|
324
1137
|
async def cleanup(self):
|
|
325
|
-
"""Clean up
|
|
1138
|
+
"""Clean up plugin resources."""
|
|
326
1139
|
self.animation_framework.clear_all()
|
|
327
|
-
await super().cleanup()
|
|
1140
|
+
await super().cleanup()
|