Squashed commit of the following: [alpha_presentable]
Author: John McCardle <mccardle.john@gmail.com>
Co-Authored-By: Claude <noreply@anthropic.com>
commit dc47f2474c7b2642d368f9772894aed857527807
the UIEntity rant
commit 673ca8e1b089ea670257fc04ae1a676ed95a40ed
I forget when these tests were written, but I want them in the squash merge
commit 70c71565c684fa96e222179271ecb13a156d80ad
Fix UI object segfault by switching from managed to manual weakref management
The UI types (Frame, Caption, Sprite, Grid, Entity) were using
Py_TPFLAGS_MANAGED_WEAKREF while also trying to manually create weakrefs
for the PythonObjectCache. This is fundamentally incompatible - when
Python manages weakrefs internally, PyWeakref_NewRef() cannot access the
weakref list properly, causing segfaults.
Changed all UI types to use manual weakref management (like PyTimer):
- Restored weakreflist field in all UI type structures
- Removed Py_TPFLAGS_MANAGED_WEAKREF from all UI type flags
- Added tp_weaklistoffset for all UI types in module initialization
- Initialize weakreflist=NULL in tp_new and init methods
- Call PyObject_ClearWeakRefs() in dealloc functions
This allows the PythonObjectCache to continue working correctly,
maintaining Python object identity for C++ objects across the boundary.
Fixes segfault when creating UI objects (e.g., Caption, Grid) that was
preventing tutorial scripts from running.
This is the bulk of the required behavior for Issue #126.
that issure isn't ready for closure yet; several other sub-issues left.
closes #110
mention issue #109 - resolves some __init__ related nuisances
commit 3dce3ec539ae99e32d869007bf3f49d03e4e2f89
Refactor timer system for cleaner architecture and enhanced functionality
Major improvements to the timer system:
- Unified all timer logic in the Timer class (C++)
- Removed PyTimerCallable subclass, now using PyCallable directly
- Timer objects are now passed to callbacks as first argument
- Added 'once' parameter for one-shot timers that auto-stop
- Implemented proper PythonObjectCache integration with weakref support
API enhancements:
- New callback signature: callback(timer, runtime) instead of just (runtime)
- Timer objects expose: name, interval, remaining, paused, active, once properties
- Methods: pause(), resume(), cancel(), restart()
- Comprehensive documentation with examples
- Enhanced repr showing timer state (active/paused/once/remaining time)
This cleanup follows the UIEntity/PyUIEntity pattern and makes the timer
system more Pythonic while maintaining backward compatibility through
the legacy setTimer/delTimer API.
closes #121
commit 145834cfc31b8dabc4cb3591b9cb4ed99fc8b964
Implement Python object cache to preserve derived types in collections
Add a global cache system that maintains weak references to Python objects,
ensuring that derived Python classes maintain their identity when stored in
and retrieved from C++ collections.
Key changes:
- Add PythonObjectCache singleton with serial number system
- Each cacheable object (UIDrawable, UIEntity, Timer, Animation) gets unique ID
- Cache stores weak references to prevent circular reference memory leaks
- Update all UI type definitions to support weak references (Py_TPFLAGS_MANAGED_WEAKREF)
- Enable subclassing for all UI types (Py_TPFLAGS_BASETYPE)
- Collections check cache before creating new Python wrappers
- Register objects in cache during __init__ methods
- Clean up cache entries in C++ destructors
This ensures that Python code like:
```python
class MyFrame(mcrfpy.Frame):
def __init__(self):
super().__init__()
self.custom_data = "preserved"
frame = MyFrame()
scene.ui.append(frame)
retrieved = scene.ui[0] # Same MyFrame instance with custom_data intact
```
Works correctly, with retrieved maintaining the derived type and custom attributes.
Closes #112
commit c5e7e8e298
Update test demos for new Python API and entity system
- Update all text input demos to use new Entity constructor signature
- Fix pathfinding showcase to work with new entity position handling
- Remove entity_waypoints tracking in favor of simplified movement
- Delete obsolete exhaustive_api_demo.py (superseded by newer demos)
- Adjust entity creation calls to match Entity((x, y), texture, sprite_index) pattern
commit 6d29652ae7
Update animation demo suite with crash fixes and improvements
- Add warnings about AnimationManager segfault bug in sizzle_reel_final.py
- Create sizzle_reel_final_fixed.py that works around the crash by hiding objects instead of removing them
- Increase font sizes for better visibility in demos
- Extend demo durations for better showcase of animations
- Remove debug prints from animation_sizzle_reel_working.py
- Minor cleanup and improvements to all animation demos
commit a010e5fa96
Update game scripts for new Python API
- Convert entity position access from tuple to x/y properties
- Update caption size property to font_size
- Fix grid boundary checks to use grid_size instead of exceptions
- Clean up demo timer on menu exit to prevent callbacks
These changes adapt the game scripts to work with the new standardized
Python API constructors and property names.
commit 9c8d6c4591
Fix click event z-order handling in PyScene
Changed click detection to properly respect z-index by:
- Sorting ui_elements in-place when needed (same as render order)
- Using reverse iterators to check highest z-index elements first
- This ensures top-most elements receive clicks before lower ones
commit dcd1b0ca33
Add roguelike tutorial implementation files
Implement Parts 0-2 of the classic roguelike tutorial adapted for McRogueFace:
- Part 0: Basic grid setup and tile rendering
- Part 1: Drawing '@' symbol and basic movement
- Part 1b: Variant with sprite-based player
- Part 2: Entity system and NPC implementation with three movement variants:
- part_2.py: Standard implementation
- part_2-naive.py: Naive movement approach
- part_2-onemovequeued.py: Queued movement system
Includes tutorial assets:
- tutorial2.png: Tileset for dungeon tiles
- tutorial_hero.png: Player sprite sheet
commit 6813fb5129
Standardize Python API constructors and remove PyArgHelpers
- Remove PyArgHelpers.h and all macro-based argument parsing
- Convert all UI class constructors to use PyArg_ParseTupleAndKeywords
- Standardize constructor signatures across UICaption, UIEntity, UIFrame, UIGrid, and UISprite
- Replace PYARGHELPER_SINGLE/MULTI macros with explicit argument parsing
- Improve error messages and argument validation
- Maintain backward compatibility with existing Python code
This change improves code maintainability and consistency across the Python API.
commit 6f67fbb51e
Fix animation callback crashes from iterator invalidation (#119)
Resolved segfaults caused by creating new animations from within
animation callbacks. The issue was iterator invalidation in
AnimationManager::update() when callbacks modified the active
animations vector.
Changes:
- Add deferred animation queue to AnimationManager
- New animations created during update are queued and added after
- Set isUpdating flag to track when in update loop
- Properly handle Animation destructor during callback execution
- Add clearCallback() method for safe cleanup scenarios
This fixes the "free(): invalid pointer" and "malloc(): unaligned
fastbin chunk detected" errors that occurred with rapid animation
creation in callbacks.
commit eb88c7b3aa
Add animation completion callbacks (#119)
Implement callbacks that fire when animations complete, enabling direct
causality between animation end and game state changes. This eliminates
race conditions from parallel timer workarounds.
- Add optional callback parameter to Animation constructor
- Callbacks execute synchronously when animation completes
- Proper Python reference counting with GIL safety
- Callbacks receive (anim, target) parameters (currently None)
- Exception handling prevents crashes from Python errors
Example usage:
```python
def on_complete(anim, target):
player_moving = False
anim = mcrfpy.Animation("x", 300.0, 1.0, "easeOut", callback=on_complete)
anim.start(player)
```
closes #119
commit 9fb428dd01
Update ROADMAP with GitHub issue numbers (#111-#125)
Added issue numbers from GitHub tracker to roadmap items:
- #111: Grid Click Events Broken in Headless
- #112: Object Splitting Bug (Python type preservation)
- #113: Batch Operations for Grid
- #114: CellView API
- #115: SpatialHash Implementation
- #116: Dirty Flag System
- #117: Memory Pool for Entities
- #118: Scene as Drawable
- #119: Animation Completion Callbacks
- #120: Animation Property Locking
- #121: Timer Object System
- #122: Parent-Child UI System
- #123: Grid Subgrid System
- #124: Grid Point Animation
- #125: GitHub Issues Automation
Also updated existing references:
- #101/#110: Constructor standardization
- #109: Vector class indexing
Note: Tutorial-specific items and Python-implementable features
(input queue, collision reservation) are not tracked as engine issues.
commit 062e4dadc4
Fix animation segfaults with RAII weak_ptr implementation
Resolved two critical segmentation faults in AnimationManager:
1. Race condition when creating multiple animations in timer callbacks
2. Exit crash when animations outlive their target objects
Changes:
- Replace raw pointers with std::weak_ptr for automatic target invalidation
- Add Animation::complete() to jump animations to final value
- Add Animation::hasValidTarget() to check if target still exists
- Update AnimationManager to auto-remove invalid animations
- Add AnimationManager::clear() call to GameEngine::cleanup()
- Update Python bindings to pass shared_ptr instead of raw pointers
This ensures animations can never reference destroyed objects, following
proper RAII principles. Tested with sizzle_reel_final.py and stress
tests creating/destroying hundreds of animated objects.
commit 98fc49a978
Directory structure cleanup and organization overhaul
This commit is contained in:
parent
1a143982e1
commit
f4343e1e82
163 changed files with 12812 additions and 5441 deletions
208
tests/demos/animation_demo.py
Normal file
208
tests/demos/animation_demo.py
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Animation Demo: Grid Center & Entity Movement
|
||||
=============================================
|
||||
|
||||
Demonstrates:
|
||||
- Animated grid centering following entity
|
||||
- Smooth entity movement along paths
|
||||
- Perspective shifts with zoom transitions
|
||||
- Field of view updates
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# Setup scene
|
||||
mcrfpy.createScene("anim_demo")
|
||||
|
||||
# Create grid
|
||||
grid = mcrfpy.Grid(grid_x=30, grid_y=20)
|
||||
grid.fill_color = mcrfpy.Color(20, 20, 30)
|
||||
|
||||
# Simple map
|
||||
for y in range(20):
|
||||
for x in range(30):
|
||||
cell = grid.at(x, y)
|
||||
# Create walls around edges and some obstacles
|
||||
if x == 0 or x == 29 or y == 0 or y == 19:
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.color = mcrfpy.Color(40, 30, 30)
|
||||
elif (x == 10 and 5 <= y <= 15) or (y == 10 and 5 <= x <= 25):
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.color = mcrfpy.Color(60, 40, 40)
|
||||
else:
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
cell.color = mcrfpy.Color(80, 80, 100)
|
||||
|
||||
# Create entities
|
||||
player = mcrfpy.Entity(5, 5, grid=grid)
|
||||
player.sprite_index = 64 # @
|
||||
|
||||
enemy = mcrfpy.Entity(25, 15, grid=grid)
|
||||
enemy.sprite_index = 69 # E
|
||||
|
||||
# Update visibility
|
||||
player.update_visibility()
|
||||
enemy.update_visibility()
|
||||
|
||||
# UI setup
|
||||
ui = mcrfpy.sceneUI("anim_demo")
|
||||
ui.append(grid)
|
||||
grid.position = (100, 100)
|
||||
grid.size = (600, 400)
|
||||
|
||||
title = mcrfpy.Caption("Animation Demo - Grid Center & Entity Movement", 200, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
status = mcrfpy.Caption("Press 1: Move Player | 2: Move Enemy | 3: Perspective Shift | Q: Quit", 100, 50)
|
||||
status.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(status)
|
||||
|
||||
info = mcrfpy.Caption("Perspective: Player", 500, 70)
|
||||
info.fill_color = mcrfpy.Color(100, 255, 100)
|
||||
ui.append(info)
|
||||
|
||||
# Movement functions
|
||||
def move_player_demo():
|
||||
"""Demo player movement with camera follow"""
|
||||
# Calculate path to a destination
|
||||
path = player.path_to(20, 10)
|
||||
if not path:
|
||||
status.text = "No path available!"
|
||||
return
|
||||
|
||||
status.text = f"Moving player along {len(path)} steps..."
|
||||
|
||||
# Animate along path
|
||||
for i, (x, y) in enumerate(path[:5]): # First 5 steps
|
||||
delay = i * 500 # 500ms between steps
|
||||
|
||||
# Schedule movement
|
||||
def move_step(dt, px=x, py=y):
|
||||
# Animate entity position
|
||||
anim_x = mcrfpy.Animation("x", float(px), 0.4, "easeInOut")
|
||||
anim_y = mcrfpy.Animation("y", float(py), 0.4, "easeInOut")
|
||||
anim_x.start(player)
|
||||
anim_y.start(player)
|
||||
|
||||
# Update visibility
|
||||
player.update_visibility()
|
||||
|
||||
# Animate camera to follow
|
||||
center_x = px * 16 # Assuming 16x16 tiles
|
||||
center_y = py * 16
|
||||
cam_anim = mcrfpy.Animation("center", (center_x, center_y), 0.4, "easeOut")
|
||||
cam_anim.start(grid)
|
||||
|
||||
mcrfpy.setTimer(f"player_move_{i}", move_step, delay)
|
||||
|
||||
def move_enemy_demo():
|
||||
"""Demo enemy movement"""
|
||||
# Calculate path
|
||||
path = enemy.path_to(10, 5)
|
||||
if not path:
|
||||
status.text = "Enemy has no path!"
|
||||
return
|
||||
|
||||
status.text = f"Moving enemy along {len(path)} steps..."
|
||||
|
||||
# Animate along path
|
||||
for i, (x, y) in enumerate(path[:5]): # First 5 steps
|
||||
delay = i * 500
|
||||
|
||||
def move_step(dt, ex=x, ey=y):
|
||||
anim_x = mcrfpy.Animation("x", float(ex), 0.4, "easeInOut")
|
||||
anim_y = mcrfpy.Animation("y", float(ey), 0.4, "easeInOut")
|
||||
anim_x.start(enemy)
|
||||
anim_y.start(enemy)
|
||||
enemy.update_visibility()
|
||||
|
||||
# If following enemy, update camera
|
||||
if grid.perspective == 1:
|
||||
center_x = ex * 16
|
||||
center_y = ey * 16
|
||||
cam_anim = mcrfpy.Animation("center", (center_x, center_y), 0.4, "easeOut")
|
||||
cam_anim.start(grid)
|
||||
|
||||
mcrfpy.setTimer(f"enemy_move_{i}", move_step, delay)
|
||||
|
||||
def perspective_shift_demo():
|
||||
"""Demo dramatic perspective shift"""
|
||||
status.text = "Perspective shift in progress..."
|
||||
|
||||
# Phase 1: Zoom out
|
||||
zoom_out = mcrfpy.Animation("zoom", 0.5, 1.5, "easeInExpo")
|
||||
zoom_out.start(grid)
|
||||
|
||||
# Phase 2: Switch perspective at peak
|
||||
def switch_perspective(dt):
|
||||
if grid.perspective == 0:
|
||||
grid.perspective = 1
|
||||
info.text = "Perspective: Enemy"
|
||||
info.fill_color = mcrfpy.Color(255, 100, 100)
|
||||
target = enemy
|
||||
else:
|
||||
grid.perspective = 0
|
||||
info.text = "Perspective: Player"
|
||||
info.fill_color = mcrfpy.Color(100, 255, 100)
|
||||
target = player
|
||||
|
||||
# Update camera to new target
|
||||
center_x = target.x * 16
|
||||
center_y = target.y * 16
|
||||
cam_anim = mcrfpy.Animation("center", (center_x, center_y), 0.5, "linear")
|
||||
cam_anim.start(grid)
|
||||
|
||||
mcrfpy.setTimer("switch_persp", switch_perspective, 1600)
|
||||
|
||||
# Phase 3: Zoom back in
|
||||
def zoom_in(dt):
|
||||
zoom_in_anim = mcrfpy.Animation("zoom", 1.0, 1.5, "easeOutExpo")
|
||||
zoom_in_anim.start(grid)
|
||||
status.text = "Perspective shift complete!"
|
||||
|
||||
mcrfpy.setTimer("zoom_in", zoom_in, 2100)
|
||||
|
||||
# Input handler
|
||||
def handle_input(key, state):
|
||||
if state != "start":
|
||||
return
|
||||
|
||||
if key == "q":
|
||||
print("Exiting demo...")
|
||||
sys.exit(0)
|
||||
elif key == "1":
|
||||
move_player_demo()
|
||||
elif key == "2":
|
||||
move_enemy_demo()
|
||||
elif key == "3":
|
||||
perspective_shift_demo()
|
||||
|
||||
# Set scene
|
||||
mcrfpy.setScene("anim_demo")
|
||||
mcrfpy.keypressScene(handle_input)
|
||||
|
||||
# Initial setup
|
||||
grid.perspective = 0
|
||||
grid.zoom = 1.0
|
||||
|
||||
# Center on player initially
|
||||
center_x = player.x * 16
|
||||
center_y = player.y * 16
|
||||
initial_cam = mcrfpy.Animation("center", (center_x, center_y), 0.5, "easeOut")
|
||||
initial_cam.start(grid)
|
||||
|
||||
print("Animation Demo Started!")
|
||||
print("======================")
|
||||
print("Press 1: Animate player movement with camera follow")
|
||||
print("Press 2: Animate enemy movement")
|
||||
print("Press 3: Dramatic perspective shift with zoom")
|
||||
print("Press Q: Quit")
|
||||
print()
|
||||
print("Watch how the grid center smoothly follows entities")
|
||||
print("and how perspective shifts create cinematic effects!")
|
||||
146
tests/demos/animation_demo_safe.py
Normal file
146
tests/demos/animation_demo_safe.py
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
McRogueFace Animation Demo - Safe Version
|
||||
=========================================
|
||||
|
||||
A safer, simpler version that demonstrates animations without crashes.
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# Configuration
|
||||
DEMO_DURATION = 4.0
|
||||
|
||||
# Track state
|
||||
current_demo = 0
|
||||
subtitle = None
|
||||
demo_items = []
|
||||
|
||||
def create_scene():
|
||||
"""Create the demo scene"""
|
||||
mcrfpy.createScene("demo")
|
||||
mcrfpy.setScene("demo")
|
||||
|
||||
ui = mcrfpy.sceneUI("demo")
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption("Animation Demo", 500, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 0)
|
||||
title.outline = 2
|
||||
ui.append(title)
|
||||
|
||||
# Subtitle
|
||||
global subtitle
|
||||
subtitle = mcrfpy.Caption("Starting...", 450, 60)
|
||||
subtitle.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(subtitle)
|
||||
|
||||
def clear_demo_items():
|
||||
"""Clear demo items from scene"""
|
||||
global demo_items
|
||||
ui = mcrfpy.sceneUI("demo")
|
||||
|
||||
# Remove demo items by tracking what we added
|
||||
for item in demo_items:
|
||||
try:
|
||||
# Find index of item
|
||||
for i in range(len(ui)):
|
||||
if i >= 2: # Skip title and subtitle
|
||||
ui.remove(i)
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
demo_items = []
|
||||
|
||||
def demo1_basic():
|
||||
"""Basic frame animations"""
|
||||
global demo_items
|
||||
clear_demo_items()
|
||||
|
||||
ui = mcrfpy.sceneUI("demo")
|
||||
subtitle.text = "Demo 1: Basic Frame Animations"
|
||||
|
||||
# Create frame
|
||||
f = mcrfpy.Frame(100, 150, 200, 100)
|
||||
f.fill_color = mcrfpy.Color(50, 50, 150)
|
||||
f.outline = 3
|
||||
ui.append(f)
|
||||
demo_items.append(f)
|
||||
|
||||
# Simple animations
|
||||
mcrfpy.Animation("x", 600.0, 2.0, "easeInOut").start(f)
|
||||
mcrfpy.Animation("w", 300.0, 2.0, "easeInOut").start(f)
|
||||
mcrfpy.Animation("fill_color", (255, 100, 50, 200), 3.0, "linear").start(f)
|
||||
|
||||
def demo2_caption():
|
||||
"""Caption animations"""
|
||||
global demo_items
|
||||
clear_demo_items()
|
||||
|
||||
ui = mcrfpy.sceneUI("demo")
|
||||
subtitle.text = "Demo 2: Caption Animations"
|
||||
|
||||
# Moving caption
|
||||
c1 = mcrfpy.Caption("Moving Text!", 100, 200)
|
||||
c1.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(c1)
|
||||
demo_items.append(c1)
|
||||
|
||||
mcrfpy.Animation("x", 700.0, 3.0, "easeOutBounce").start(c1)
|
||||
|
||||
# Typewriter
|
||||
c2 = mcrfpy.Caption("", 100, 300)
|
||||
c2.fill_color = mcrfpy.Color(0, 255, 255)
|
||||
ui.append(c2)
|
||||
demo_items.append(c2)
|
||||
|
||||
mcrfpy.Animation("text", "Typewriter effect...", 3.0, "linear").start(c2)
|
||||
|
||||
def demo3_multiple():
|
||||
"""Multiple animations"""
|
||||
global demo_items
|
||||
clear_demo_items()
|
||||
|
||||
ui = mcrfpy.sceneUI("demo")
|
||||
subtitle.text = "Demo 3: Multiple Animations"
|
||||
|
||||
# Create several frames
|
||||
for i in range(5):
|
||||
f = mcrfpy.Frame(100 + i * 120, 200, 80, 80)
|
||||
f.fill_color = mcrfpy.Color(50 + i * 40, 100, 200 - i * 30)
|
||||
ui.append(f)
|
||||
demo_items.append(f)
|
||||
|
||||
# Animate each differently
|
||||
target_y = 350 + i * 20
|
||||
mcrfpy.Animation("y", float(target_y), 2.0, "easeInOut").start(f)
|
||||
mcrfpy.Animation("opacity", 0.5, 3.0, "easeInOut").start(f)
|
||||
|
||||
def run_next_demo(runtime):
|
||||
"""Run the next demo"""
|
||||
global current_demo
|
||||
|
||||
demos = [demo1_basic, demo2_caption, demo3_multiple]
|
||||
|
||||
if current_demo < len(demos):
|
||||
demos[current_demo]()
|
||||
current_demo += 1
|
||||
|
||||
if current_demo < len(demos):
|
||||
mcrfpy.setTimer("next", run_next_demo, int(DEMO_DURATION * 1000))
|
||||
else:
|
||||
subtitle.text = "Demo Complete!"
|
||||
# Exit after a delay
|
||||
def exit_program(rt):
|
||||
print("Demo finished successfully!")
|
||||
sys.exit(0)
|
||||
mcrfpy.setTimer("exit", exit_program, 2000)
|
||||
|
||||
# Initialize
|
||||
print("Starting Safe Animation Demo...")
|
||||
create_scene()
|
||||
|
||||
# Start demos
|
||||
mcrfpy.setTimer("start", run_next_demo, 500)
|
||||
616
tests/demos/animation_sizzle_reel.py
Normal file
616
tests/demos/animation_sizzle_reel.py
Normal file
|
|
@ -0,0 +1,616 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
McRogueFace Animation Sizzle Reel
|
||||
=================================
|
||||
|
||||
This script demonstrates EVERY animation type on EVERY UI object type.
|
||||
It showcases all 30 easing functions, all animatable properties, and
|
||||
special animation modes (delta, sprite sequences, text effects).
|
||||
|
||||
The script creates a comprehensive visual demonstration of the animation
|
||||
system's capabilities, cycling through different objects and effects.
|
||||
|
||||
Author: Claude
|
||||
Purpose: Complete animation system demonstration
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
from mcrfpy import Color, Frame, Caption, Sprite, Grid, Entity, Texture, Animation
|
||||
import sys
|
||||
import math
|
||||
|
||||
# Configuration
|
||||
SCENE_WIDTH = 1280
|
||||
SCENE_HEIGHT = 720
|
||||
DEMO_DURATION = 5.0 # Duration for each demo section
|
||||
|
||||
# All available easing functions
|
||||
EASING_FUNCTIONS = [
|
||||
"linear", "easeIn", "easeOut", "easeInOut",
|
||||
"easeInQuad", "easeOutQuad", "easeInOutQuad",
|
||||
"easeInCubic", "easeOutCubic", "easeInOutCubic",
|
||||
"easeInQuart", "easeOutQuart", "easeInOutQuart",
|
||||
"easeInSine", "easeOutSine", "easeInOutSine",
|
||||
"easeInExpo", "easeOutExpo", "easeInOutExpo",
|
||||
"easeInCirc", "easeOutCirc", "easeInOutCirc",
|
||||
"easeInElastic", "easeOutElastic", "easeInOutElastic",
|
||||
"easeInBack", "easeOutBack", "easeInOutBack",
|
||||
"easeInBounce", "easeOutBounce", "easeInOutBounce"
|
||||
]
|
||||
|
||||
# Track current demo state
|
||||
current_demo = 0
|
||||
demo_start_time = 0
|
||||
demos = []
|
||||
|
||||
# Handle ESC key to exit
|
||||
def handle_keypress(scene_name, keycode):
|
||||
if keycode == 256: # ESC key
|
||||
print("Exiting animation sizzle reel...")
|
||||
sys.exit(0)
|
||||
|
||||
def create_demo_scene():
|
||||
"""Create the main demo scene with title"""
|
||||
mcrfpy.createScene("sizzle_reel")
|
||||
mcrfpy.setScene("sizzle_reel")
|
||||
mcrfpy.keypressScene(handle_keypress)
|
||||
|
||||
ui = mcrfpy.sceneUI("sizzle_reel")
|
||||
|
||||
# Title caption
|
||||
title = Caption("McRogueFace Animation Sizzle Reel",
|
||||
SCENE_WIDTH/2 - 200, 20)
|
||||
title.fill_color = Color(255, 255, 0)
|
||||
title.outline = 2
|
||||
title.outline_color = Color(0, 0, 0)
|
||||
ui.append(title)
|
||||
|
||||
# Subtitle showing current demo
|
||||
global subtitle
|
||||
subtitle = Caption("Initializing...",
|
||||
SCENE_WIDTH/2 - 150, 60)
|
||||
subtitle.fill_color = Color(200, 200, 200)
|
||||
ui.append(subtitle)
|
||||
|
||||
return ui
|
||||
|
||||
def demo_frame_basic_animations(ui):
|
||||
"""Demo 1: Basic frame animations - position, size, colors"""
|
||||
subtitle.text = "Demo 1: Frame Basic Animations (Position, Size, Colors)"
|
||||
|
||||
# Create test frame
|
||||
frame = Frame(100, 150, 200, 100)
|
||||
frame.fill_color = Color(50, 50, 150)
|
||||
frame.outline = 3
|
||||
frame.outline_color = Color(255, 255, 255)
|
||||
ui.append(frame)
|
||||
|
||||
# Position animations with different easings
|
||||
x_anim = Animation("x", 800.0, 2.0, "easeInOutBack")
|
||||
y_anim = Animation("y", 400.0, 2.0, "easeInOutElastic")
|
||||
x_anim.start(frame)
|
||||
y_anim.start(frame)
|
||||
|
||||
# Size animations
|
||||
w_anim = Animation("w", 400.0, 3.0, "easeInOutCubic")
|
||||
h_anim = Animation("h", 200.0, 3.0, "easeInOutCubic")
|
||||
w_anim.start(frame)
|
||||
h_anim.start(frame)
|
||||
|
||||
# Color animations - use tuples instead of Color objects
|
||||
fill_anim = Animation("fill_color", (255, 100, 50, 200), 4.0, "easeInOutSine")
|
||||
outline_anim = Animation("outline_color", (0, 255, 255, 255), 4.0, "easeOutBounce")
|
||||
fill_anim.start(frame)
|
||||
outline_anim.start(frame)
|
||||
|
||||
# Outline thickness animation
|
||||
thickness_anim = Animation("outline", 10.0, 4.5, "easeInOutQuad")
|
||||
thickness_anim.start(frame)
|
||||
|
||||
return frame
|
||||
|
||||
def demo_frame_opacity_zindex(ui):
|
||||
"""Demo 2: Frame opacity and z-index animations"""
|
||||
subtitle.text = "Demo 2: Frame Opacity & Z-Index Animations"
|
||||
|
||||
frames = []
|
||||
colors = [
|
||||
Color(255, 0, 0, 200),
|
||||
Color(0, 255, 0, 200),
|
||||
Color(0, 0, 255, 200),
|
||||
Color(255, 255, 0, 200)
|
||||
]
|
||||
|
||||
# Create overlapping frames
|
||||
for i in range(4):
|
||||
frame = Frame(200 + i*80, 200 + i*40, 200, 150)
|
||||
frame.fill_color = colors[i]
|
||||
frame.outline = 2
|
||||
frame.z_index = i
|
||||
ui.append(frame)
|
||||
frames.append(frame)
|
||||
|
||||
# Animate opacity in waves
|
||||
opacity_anim = Animation("opacity", 0.3, 2.0, "easeInOutSine")
|
||||
opacity_anim.start(frame)
|
||||
|
||||
# Reverse opacity animation
|
||||
opacity_back = Animation("opacity", 1.0, 2.0, "easeInOutSine", delta=False)
|
||||
mcrfpy.setTimer(f"opacity_back_{i}", lambda t, f=frame, a=opacity_back: a.start(f), 2000)
|
||||
|
||||
# Z-index shuffle animation
|
||||
z_anim = Animation("z_index", (i + 2) % 4, 3.0, "linear")
|
||||
z_anim.start(frame)
|
||||
|
||||
return frames
|
||||
|
||||
def demo_caption_animations(ui):
|
||||
"""Demo 3: Caption text animations and effects"""
|
||||
subtitle.text = "Demo 3: Caption Animations (Text, Color, Position)"
|
||||
|
||||
# Basic caption with position animation
|
||||
caption1 = Caption("Moving Text!", 100, 200)
|
||||
caption1.fill_color = Color(255, 255, 255)
|
||||
caption1.outline = 1
|
||||
ui.append(caption1)
|
||||
|
||||
# Animate across screen with bounce
|
||||
x_anim = Animation("x", 900.0, 3.0, "easeOutBounce")
|
||||
x_anim.start(caption1)
|
||||
|
||||
# Color cycling caption
|
||||
caption2 = Caption("Rainbow Colors", 400, 300)
|
||||
caption2.outline = 2
|
||||
ui.append(caption2)
|
||||
|
||||
# Cycle through colors - use tuples
|
||||
color_anim1 = Animation("fill_color", (255, 0, 0, 255), 1.0, "linear")
|
||||
color_anim2 = Animation("fill_color", (0, 255, 0, 255), 1.0, "linear")
|
||||
color_anim3 = Animation("fill_color", (0, 0, 255, 255), 1.0, "linear")
|
||||
color_anim4 = Animation("fill_color", (255, 255, 255, 255), 1.0, "linear")
|
||||
|
||||
color_anim1.start(caption2)
|
||||
mcrfpy.setTimer("color2", lambda t: color_anim2.start(caption2), 1000)
|
||||
mcrfpy.setTimer("color3", lambda t: color_anim3.start(caption2), 2000)
|
||||
mcrfpy.setTimer("color4", lambda t: color_anim4.start(caption2), 3000)
|
||||
|
||||
# Typewriter effect caption
|
||||
caption3 = Caption("", 100, 400)
|
||||
caption3.fill_color = Color(0, 255, 255)
|
||||
ui.append(caption3)
|
||||
|
||||
typewriter = Animation("text", "This text appears one character at a time...", 3.0, "linear")
|
||||
typewriter.start(caption3)
|
||||
|
||||
# Size animation caption
|
||||
caption4 = Caption("Growing Text", 400, 500)
|
||||
caption4.fill_color = Color(255, 200, 0)
|
||||
ui.append(caption4)
|
||||
|
||||
# Note: size animation would require font size property support
|
||||
# For now, animate position to simulate growth
|
||||
scale_sim = Animation("y", 480.0, 2.0, "easeInOutElastic")
|
||||
scale_sim.start(caption4)
|
||||
|
||||
return [caption1, caption2, caption3, caption4]
|
||||
|
||||
def demo_sprite_animations(ui):
|
||||
"""Demo 4: Sprite animations including sprite sequences"""
|
||||
subtitle.text = "Demo 4: Sprite Animations (Position, Scale, Sprite Sequences)"
|
||||
|
||||
# Load a test texture (you'll need to adjust path)
|
||||
try:
|
||||
texture = Texture("assets/sprites/player.png", grid_size=(32, 32))
|
||||
except:
|
||||
# Fallback if texture not found
|
||||
texture = None
|
||||
|
||||
if texture:
|
||||
# Basic sprite with position animation
|
||||
sprite1 = Sprite(100, 200, texture, sprite_index=0)
|
||||
sprite1.scale = 2.0
|
||||
ui.append(sprite1)
|
||||
|
||||
# Circular motion using sin/cos animations
|
||||
# We'll use delta mode to create circular motion
|
||||
x_circle = Animation("x", 300.0, 4.0, "easeInOutSine")
|
||||
y_circle = Animation("y", 300.0, 4.0, "easeInOutCubic")
|
||||
x_circle.start(sprite1)
|
||||
y_circle.start(sprite1)
|
||||
|
||||
# Sprite sequence animation (walking cycle)
|
||||
sprite2 = Sprite(500, 300, texture, sprite_index=0)
|
||||
sprite2.scale = 3.0
|
||||
ui.append(sprite2)
|
||||
|
||||
# Animate through sprite indices for animation
|
||||
walk_cycle = Animation("sprite_index", [0, 1, 2, 3, 2, 1], 2.0, "linear")
|
||||
walk_cycle.start(sprite2)
|
||||
|
||||
# Scale pulsing sprite
|
||||
sprite3 = Sprite(800, 400, texture, sprite_index=4)
|
||||
ui.append(sprite3)
|
||||
|
||||
# Note: scale animation would need to be supported
|
||||
# For now use position to simulate
|
||||
pulse_y = Animation("y", 380.0, 0.5, "easeInOutSine")
|
||||
pulse_y.start(sprite3)
|
||||
|
||||
# Z-index animation for layering
|
||||
sprite3_z = Animation("z_index", 10, 2.0, "linear")
|
||||
sprite3_z.start(sprite3)
|
||||
|
||||
return [sprite1, sprite2, sprite3]
|
||||
else:
|
||||
# Create placeholder caption if no texture
|
||||
no_texture = Caption("(Sprite demo requires texture file)", 400, 350)
|
||||
no_texture.fill_color = Color(255, 100, 100)
|
||||
ui.append(no_texture)
|
||||
return [no_texture]
|
||||
|
||||
def demo_grid_animations(ui):
|
||||
"""Demo 5: Grid animations (position, camera, zoom)"""
|
||||
subtitle.text = "Demo 5: Grid Animations (Position, Camera Effects)"
|
||||
|
||||
# Create a grid
|
||||
try:
|
||||
texture = Texture("assets/sprites/tiles.png", grid_size=(16, 16))
|
||||
except:
|
||||
texture = None
|
||||
|
||||
# Grid constructor: Grid(grid_x, grid_y, texture, position, size)
|
||||
# Note: tile dimensions are determined by texture's grid_size
|
||||
grid = Grid(20, 15, texture, (100, 150), (480, 360)) # 20x24, 15x24
|
||||
grid.fill_color = Color(20, 20, 40)
|
||||
ui.append(grid)
|
||||
|
||||
# Fill with some test pattern
|
||||
for y in range(15):
|
||||
for x in range(20):
|
||||
point = grid.at(x, y)
|
||||
point.tilesprite = (x + y) % 4
|
||||
point.walkable = ((x + y) % 3) != 0
|
||||
if not point.walkable:
|
||||
point.color = Color(100, 50, 50, 128)
|
||||
|
||||
# Animate grid position
|
||||
grid_x = Animation("x", 400.0, 3.0, "easeInOutBack")
|
||||
grid_x.start(grid)
|
||||
|
||||
# Camera pan animation (if supported)
|
||||
# center_x = Animation("center", (10.0, 7.5), 4.0, "easeInOutCubic")
|
||||
# center_x.start(grid)
|
||||
|
||||
# Create entities in the grid
|
||||
if texture:
|
||||
entity1 = Entity((5.0, 5.0), texture, 8) # position tuple, texture, sprite_index
|
||||
entity1.scale = 1.5
|
||||
grid.entities.append(entity1)
|
||||
|
||||
# Animate entity movement
|
||||
entity_pos = Animation("position", (15.0, 10.0), 3.0, "easeInOutQuad")
|
||||
entity_pos.start(entity1)
|
||||
|
||||
# Create patrolling entity
|
||||
entity2 = Entity((10.0, 2.0), texture, 12) # position tuple, texture, sprite_index
|
||||
grid.entities.append(entity2)
|
||||
|
||||
# Animate sprite changes
|
||||
entity2_sprite = Animation("sprite_index", [12, 13, 14, 15, 14, 13], 2.0, "linear")
|
||||
entity2_sprite.start(entity2)
|
||||
|
||||
return grid
|
||||
|
||||
def demo_complex_combinations(ui):
|
||||
"""Demo 6: Complex multi-property animations"""
|
||||
subtitle.text = "Demo 6: Complex Multi-Property Animations"
|
||||
|
||||
# Create a complex UI composition
|
||||
main_frame = Frame(200, 200, 400, 300)
|
||||
main_frame.fill_color = Color(30, 30, 60, 200)
|
||||
main_frame.outline = 2
|
||||
ui.append(main_frame)
|
||||
|
||||
# Child elements
|
||||
title = Caption("Multi-Animation Demo", 20, 20)
|
||||
title.fill_color = Color(255, 255, 255)
|
||||
main_frame.children.append(title)
|
||||
|
||||
# Animate everything at once
|
||||
# Frame animations
|
||||
frame_x = Animation("x", 600.0, 3.0, "easeInOutElastic")
|
||||
frame_w = Animation("w", 300.0, 2.5, "easeOutBack")
|
||||
frame_fill = Animation("fill_color", (60, 30, 90, 220), 4.0, "easeInOutSine")
|
||||
frame_outline = Animation("outline", 8.0, 3.0, "easeInOutQuad")
|
||||
|
||||
frame_x.start(main_frame)
|
||||
frame_w.start(main_frame)
|
||||
frame_fill.start(main_frame)
|
||||
frame_outline.start(main_frame)
|
||||
|
||||
# Title animations
|
||||
title_color = Animation("fill_color", (255, 200, 0, 255), 2.0, "easeOutBounce")
|
||||
title_color.start(title)
|
||||
|
||||
# Add animated sub-frames
|
||||
for i in range(3):
|
||||
sub_frame = Frame(50 + i * 100, 100, 80, 80)
|
||||
sub_frame.fill_color = Color(100 + i*50, 50, 200 - i*50, 180)
|
||||
main_frame.children.append(sub_frame)
|
||||
|
||||
# Rotate positions using delta animations
|
||||
sub_y = Animation("y", 50.0, 2.0, "easeInOutSine", delta=True)
|
||||
sub_y.start(sub_frame)
|
||||
|
||||
return main_frame
|
||||
|
||||
def demo_easing_showcase(ui):
|
||||
"""Demo 7: Showcase all 30 easing functions"""
|
||||
subtitle.text = "Demo 7: All 30 Easing Functions Showcase"
|
||||
|
||||
# Create small frames for each easing function
|
||||
frames_per_row = 6
|
||||
frame_size = 180
|
||||
spacing = 10
|
||||
|
||||
for i, easing in enumerate(EASING_FUNCTIONS[:12]): # First 12 easings
|
||||
row = i // frames_per_row
|
||||
col = i % frames_per_row
|
||||
|
||||
x = 50 + col * (frame_size + spacing)
|
||||
y = 150 + row * (60 + spacing)
|
||||
|
||||
# Create indicator frame
|
||||
frame = Frame(x, y, 20, 20)
|
||||
frame.fill_color = Color(100, 200, 255)
|
||||
frame.outline = 1
|
||||
ui.append(frame)
|
||||
|
||||
# Label
|
||||
label = Caption(easing, x, y - 20)
|
||||
label.fill_color = Color(200, 200, 200)
|
||||
ui.append(label)
|
||||
|
||||
# Animate using this easing
|
||||
move_anim = Animation("x", x + frame_size - 20, 3.0, easing)
|
||||
move_anim.start(frame)
|
||||
|
||||
# Continue with remaining easings after a delay
|
||||
def show_more_easings(runtime):
|
||||
for j, easing in enumerate(EASING_FUNCTIONS[12:24]): # Next 12
|
||||
row = j // frames_per_row + 2
|
||||
col = j % frames_per_row
|
||||
|
||||
x = 50 + col * (frame_size + spacing)
|
||||
y = 150 + row * (60 + spacing)
|
||||
|
||||
frame2 = Frame(x, y, 20, 20)
|
||||
frame2.fill_color = Color(255, 150, 100)
|
||||
frame2.outline = 1
|
||||
ui.append(frame2)
|
||||
|
||||
label2 = Caption(easing, x, y - 20)
|
||||
label2.fill_color = Color(200, 200, 200)
|
||||
ui.append(label2)
|
||||
|
||||
move_anim2 = Animation("x", x + frame_size - 20, 3.0, easing)
|
||||
move_anim2.start(frame2)
|
||||
|
||||
mcrfpy.setTimer("more_easings", show_more_easings, 1000)
|
||||
|
||||
# Show final easings
|
||||
def show_final_easings(runtime):
|
||||
for k, easing in enumerate(EASING_FUNCTIONS[24:]): # Last 6
|
||||
row = k // frames_per_row + 4
|
||||
col = k % frames_per_row
|
||||
|
||||
x = 50 + col * (frame_size + spacing)
|
||||
y = 150 + row * (60 + spacing)
|
||||
|
||||
frame3 = Frame(x, y, 20, 20)
|
||||
frame3.fill_color = Color(150, 255, 150)
|
||||
frame3.outline = 1
|
||||
ui.append(frame3)
|
||||
|
||||
label3 = Caption(easing, x, y - 20)
|
||||
label3.fill_color = Color(200, 200, 200)
|
||||
ui.append(label3)
|
||||
|
||||
move_anim3 = Animation("x", x + frame_size - 20, 3.0, easing)
|
||||
move_anim3.start(frame3)
|
||||
|
||||
mcrfpy.setTimer("final_easings", show_final_easings, 2000)
|
||||
|
||||
def demo_delta_animations(ui):
|
||||
"""Demo 8: Delta mode animations (relative movements)"""
|
||||
subtitle.text = "Demo 8: Delta Mode Animations (Relative Movements)"
|
||||
|
||||
# Create objects that will move relative to their position
|
||||
frames = []
|
||||
start_positions = [(100, 200), (300, 200), (500, 200), (700, 200)]
|
||||
colors = [Color(255, 100, 100), Color(100, 255, 100),
|
||||
Color(100, 100, 255), Color(255, 255, 100)]
|
||||
|
||||
for i, (x, y) in enumerate(start_positions):
|
||||
frame = Frame(x, y, 80, 80)
|
||||
frame.fill_color = colors[i]
|
||||
frame.outline = 2
|
||||
ui.append(frame)
|
||||
frames.append(frame)
|
||||
|
||||
# Delta animations - move relative to current position
|
||||
# Each frame moves by different amounts
|
||||
dx = (i + 1) * 50
|
||||
dy = math.sin(i) * 100
|
||||
|
||||
x_delta = Animation("x", dx, 2.0, "easeInOutBack", delta=True)
|
||||
y_delta = Animation("y", dy, 2.0, "easeInOutElastic", delta=True)
|
||||
|
||||
x_delta.start(frame)
|
||||
y_delta.start(frame)
|
||||
|
||||
# Create caption showing delta mode
|
||||
delta_label = Caption("Delta mode: Relative animations from current position", 200, 400)
|
||||
delta_label.fill_color = Color(255, 255, 255)
|
||||
ui.append(delta_label)
|
||||
|
||||
# Animate the label with delta mode text append
|
||||
text_delta = Animation("text", " - ANIMATED!", 2.0, "linear", delta=True)
|
||||
text_delta.start(delta_label)
|
||||
|
||||
return frames
|
||||
|
||||
def demo_color_component_animations(ui):
|
||||
"""Demo 9: Individual color channel animations"""
|
||||
subtitle.text = "Demo 9: Color Component Animations (R, G, B, A channels)"
|
||||
|
||||
# Create frames to demonstrate individual color channel animations
|
||||
base_frame = Frame(300, 200, 600, 300)
|
||||
base_frame.fill_color = Color(128, 128, 128, 255)
|
||||
base_frame.outline = 3
|
||||
ui.append(base_frame)
|
||||
|
||||
# Labels for each channel
|
||||
labels = ["Red", "Green", "Blue", "Alpha"]
|
||||
positions = [(50, 50), (200, 50), (350, 50), (500, 50)]
|
||||
|
||||
for i, (label_text, (x, y)) in enumerate(zip(labels, positions)):
|
||||
# Create label
|
||||
label = Caption(label_text, x, y - 30)
|
||||
label.fill_color = Color(255, 255, 255)
|
||||
base_frame.children.append(label)
|
||||
|
||||
# Create demo frame for this channel
|
||||
demo_frame = Frame(x, y, 100, 100)
|
||||
demo_frame.fill_color = Color(100, 100, 100, 200)
|
||||
demo_frame.outline = 2
|
||||
base_frame.children.append(demo_frame)
|
||||
|
||||
# Animate individual color channel
|
||||
if i == 0: # Red
|
||||
r_anim = Animation("fill_color.r", 255, 3.0, "easeInOutSine")
|
||||
r_anim.start(demo_frame)
|
||||
elif i == 1: # Green
|
||||
g_anim = Animation("fill_color.g", 255, 3.0, "easeInOutSine")
|
||||
g_anim.start(demo_frame)
|
||||
elif i == 2: # Blue
|
||||
b_anim = Animation("fill_color.b", 255, 3.0, "easeInOutSine")
|
||||
b_anim.start(demo_frame)
|
||||
else: # Alpha
|
||||
a_anim = Animation("fill_color.a", 50, 3.0, "easeInOutSine")
|
||||
a_anim.start(demo_frame)
|
||||
|
||||
# Animate main frame outline color components in sequence
|
||||
outline_r = Animation("outline_color.r", 255, 1.0, "linear")
|
||||
outline_g = Animation("outline_color.g", 255, 1.0, "linear")
|
||||
outline_b = Animation("outline_color.b", 0, 1.0, "linear")
|
||||
|
||||
outline_r.start(base_frame)
|
||||
mcrfpy.setTimer("outline_g", lambda t: outline_g.start(base_frame), 1000)
|
||||
mcrfpy.setTimer("outline_b", lambda t: outline_b.start(base_frame), 2000)
|
||||
|
||||
return base_frame
|
||||
|
||||
def demo_performance_stress_test(ui):
|
||||
"""Demo 10: Performance test with many simultaneous animations"""
|
||||
subtitle.text = "Demo 10: Performance Stress Test (100+ Simultaneous Animations)"
|
||||
|
||||
# Create many small objects with different animations
|
||||
num_objects = 100
|
||||
|
||||
for i in range(num_objects):
|
||||
# Random starting position
|
||||
x = 100 + (i % 20) * 50
|
||||
y = 150 + (i // 20) * 50
|
||||
|
||||
# Create small frame
|
||||
size = 20 + (i % 3) * 10
|
||||
frame = Frame(x, y, size, size)
|
||||
|
||||
# Random color
|
||||
r = (i * 37) % 256
|
||||
g = (i * 73) % 256
|
||||
b = (i * 113) % 256
|
||||
frame.fill_color = Color(r, g, b, 200)
|
||||
frame.outline = 1
|
||||
ui.append(frame)
|
||||
|
||||
# Random animation properties
|
||||
target_x = 100 + (i % 15) * 70
|
||||
target_y = 150 + (i // 15) * 70
|
||||
duration = 2.0 + (i % 30) * 0.1
|
||||
easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)]
|
||||
|
||||
# Start multiple animations per object
|
||||
x_anim = Animation("x", target_x, duration, easing)
|
||||
y_anim = Animation("y", target_y, duration, easing)
|
||||
opacity_anim = Animation("opacity", 0.3 + (i % 7) * 0.1, duration, "easeInOutSine")
|
||||
|
||||
x_anim.start(frame)
|
||||
y_anim.start(frame)
|
||||
opacity_anim.start(frame)
|
||||
|
||||
# Performance counter
|
||||
perf_caption = Caption(f"Animating {num_objects * 3} properties simultaneously", 400, 600)
|
||||
perf_caption.fill_color = Color(255, 255, 0)
|
||||
ui.append(perf_caption)
|
||||
|
||||
def next_demo(runtime):
|
||||
"""Cycle to the next demo"""
|
||||
global current_demo, demo_start_time
|
||||
|
||||
# Clear the UI except title and subtitle
|
||||
ui = mcrfpy.sceneUI("sizzle_reel")
|
||||
|
||||
# Keep only the first two elements (title and subtitle)
|
||||
while len(ui) > 2:
|
||||
# Remove from the end to avoid index issues
|
||||
ui.remove(len(ui) - 1)
|
||||
|
||||
# Run the next demo
|
||||
if current_demo < len(demos):
|
||||
demos[current_demo](ui)
|
||||
current_demo += 1
|
||||
|
||||
# Schedule next demo
|
||||
if current_demo < len(demos):
|
||||
mcrfpy.setTimer("next_demo", next_demo, int(DEMO_DURATION * 1000))
|
||||
else:
|
||||
# All demos complete
|
||||
subtitle.text = "Animation Showcase Complete! Press ESC to exit."
|
||||
complete = Caption("All animation types demonstrated!", 400, 350)
|
||||
complete.fill_color = Color(0, 255, 0)
|
||||
complete.outline = 2
|
||||
ui.append(complete)
|
||||
|
||||
def run_sizzle_reel(runtime):
|
||||
"""Main entry point - start the demo sequence"""
|
||||
global demos
|
||||
|
||||
# List of all demo functions
|
||||
demos = [
|
||||
demo_frame_basic_animations,
|
||||
demo_frame_opacity_zindex,
|
||||
demo_caption_animations,
|
||||
demo_sprite_animations,
|
||||
demo_grid_animations,
|
||||
demo_complex_combinations,
|
||||
demo_easing_showcase,
|
||||
demo_delta_animations,
|
||||
demo_color_component_animations,
|
||||
demo_performance_stress_test
|
||||
]
|
||||
|
||||
# Start the first demo
|
||||
next_demo(runtime)
|
||||
|
||||
# Initialize scene
|
||||
ui = create_demo_scene()
|
||||
|
||||
|
||||
# Start the sizzle reel after a short delay
|
||||
mcrfpy.setTimer("start_sizzle", run_sizzle_reel, 500)
|
||||
|
||||
print("Starting McRogueFace Animation Sizzle Reel...")
|
||||
print("This will demonstrate ALL animation types on ALL objects.")
|
||||
print("Press ESC at any time to exit.")
|
||||
227
tests/demos/animation_sizzle_reel_fixed.py
Normal file
227
tests/demos/animation_sizzle_reel_fixed.py
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
McRogueFace Animation Sizzle Reel (Fixed)
|
||||
=========================================
|
||||
|
||||
This script demonstrates EVERY animation type on EVERY UI object type.
|
||||
Fixed version that works properly with the game loop.
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
|
||||
# Configuration
|
||||
SCENE_WIDTH = 1280
|
||||
SCENE_HEIGHT = 720
|
||||
DEMO_DURATION = 5.0 # Duration for each demo section
|
||||
|
||||
# All available easing functions
|
||||
EASING_FUNCTIONS = [
|
||||
"linear", "easeIn", "easeOut", "easeInOut",
|
||||
"easeInQuad", "easeOutQuad", "easeInOutQuad",
|
||||
"easeInCubic", "easeOutCubic", "easeInOutCubic",
|
||||
"easeInQuart", "easeOutQuart", "easeInOutQuart",
|
||||
"easeInSine", "easeOutSine", "easeInOutSine",
|
||||
"easeInExpo", "easeOutExpo", "easeInOutExpo",
|
||||
"easeInCirc", "easeOutCirc", "easeInOutCirc",
|
||||
"easeInElastic", "easeOutElastic", "easeInOutElastic",
|
||||
"easeInBack", "easeOutBack", "easeInOutBack",
|
||||
"easeInBounce", "easeOutBounce", "easeInOutBounce"
|
||||
]
|
||||
|
||||
# Track current demo state
|
||||
current_demo = 0
|
||||
subtitle = None
|
||||
|
||||
def create_demo_scene():
|
||||
"""Create the main demo scene with title"""
|
||||
mcrfpy.createScene("sizzle_reel")
|
||||
mcrfpy.setScene("sizzle_reel")
|
||||
|
||||
ui = mcrfpy.sceneUI("sizzle_reel")
|
||||
|
||||
# Title caption
|
||||
title = mcrfpy.Caption("McRogueFace Animation Sizzle Reel",
|
||||
SCENE_WIDTH/2 - 200, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 0)
|
||||
title.outline = 2
|
||||
title.outline_color = mcrfpy.Color(0, 0, 0)
|
||||
ui.append(title)
|
||||
|
||||
# Subtitle showing current demo
|
||||
global subtitle
|
||||
subtitle = mcrfpy.Caption("Initializing...",
|
||||
SCENE_WIDTH/2 - 150, 60)
|
||||
subtitle.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(subtitle)
|
||||
|
||||
return ui
|
||||
|
||||
def demo_frame_basic_animations():
|
||||
"""Demo 1: Basic frame animations - position, size, colors"""
|
||||
ui = mcrfpy.sceneUI("sizzle_reel")
|
||||
subtitle.text = "Demo 1: Frame Basic Animations (Position, Size, Colors)"
|
||||
|
||||
# Create test frame
|
||||
frame = mcrfpy.Frame(100, 150, 200, 100)
|
||||
frame.fill_color = mcrfpy.Color(50, 50, 150)
|
||||
frame.outline = 3
|
||||
frame.outline_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(frame)
|
||||
|
||||
# Position animations with different easings
|
||||
x_anim = mcrfpy.Animation("x", 800.0, 2.0, "easeInOutBack")
|
||||
y_anim = mcrfpy.Animation("y", 400.0, 2.0, "easeInOutElastic")
|
||||
x_anim.start(frame)
|
||||
y_anim.start(frame)
|
||||
|
||||
# Size animations
|
||||
w_anim = mcrfpy.Animation("w", 400.0, 3.0, "easeInOutCubic")
|
||||
h_anim = mcrfpy.Animation("h", 200.0, 3.0, "easeInOutCubic")
|
||||
w_anim.start(frame)
|
||||
h_anim.start(frame)
|
||||
|
||||
# Color animations
|
||||
fill_anim = mcrfpy.Animation("fill_color", mcrfpy.Color(255, 100, 50, 200), 4.0, "easeInOutSine")
|
||||
outline_anim = mcrfpy.Animation("outline_color", mcrfpy.Color(0, 255, 255), 4.0, "easeOutBounce")
|
||||
fill_anim.start(frame)
|
||||
outline_anim.start(frame)
|
||||
|
||||
# Outline thickness animation
|
||||
thickness_anim = mcrfpy.Animation("outline", 10.0, 4.5, "easeInOutQuad")
|
||||
thickness_anim.start(frame)
|
||||
|
||||
def demo_caption_animations():
|
||||
"""Demo 2: Caption text animations and effects"""
|
||||
ui = mcrfpy.sceneUI("sizzle_reel")
|
||||
subtitle.text = "Demo 2: Caption Animations (Text, Color, Position)"
|
||||
|
||||
# Basic caption with position animation
|
||||
caption1 = mcrfpy.Caption("Moving Text!", 100, 200)
|
||||
caption1.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
caption1.outline = 1
|
||||
ui.append(caption1)
|
||||
|
||||
# Animate across screen with bounce
|
||||
x_anim = mcrfpy.Animation("x", 900.0, 3.0, "easeOutBounce")
|
||||
x_anim.start(caption1)
|
||||
|
||||
# Color cycling caption
|
||||
caption2 = mcrfpy.Caption("Rainbow Colors", 400, 300)
|
||||
caption2.outline = 2
|
||||
ui.append(caption2)
|
||||
|
||||
# Cycle through colors
|
||||
color_anim1 = mcrfpy.Animation("fill_color", mcrfpy.Color(255, 0, 0), 1.0, "linear")
|
||||
color_anim1.start(caption2)
|
||||
|
||||
# Typewriter effect caption
|
||||
caption3 = mcrfpy.Caption("", 100, 400)
|
||||
caption3.fill_color = mcrfpy.Color(0, 255, 255)
|
||||
ui.append(caption3)
|
||||
|
||||
typewriter = mcrfpy.Animation("text", "This text appears one character at a time...", 3.0, "linear")
|
||||
typewriter.start(caption3)
|
||||
|
||||
def demo_sprite_animations():
|
||||
"""Demo 3: Sprite animations (if texture available)"""
|
||||
ui = mcrfpy.sceneUI("sizzle_reel")
|
||||
subtitle.text = "Demo 3: Sprite Animations"
|
||||
|
||||
# Create placeholder caption since texture might not exist
|
||||
no_texture = mcrfpy.Caption("(Sprite demo - textures may not be loaded)", 400, 350)
|
||||
no_texture.fill_color = mcrfpy.Color(255, 100, 100)
|
||||
ui.append(no_texture)
|
||||
|
||||
def demo_performance_stress_test():
|
||||
"""Demo 4: Performance test with many simultaneous animations"""
|
||||
ui = mcrfpy.sceneUI("sizzle_reel")
|
||||
subtitle.text = "Demo 4: Performance Test (50+ Simultaneous Animations)"
|
||||
|
||||
# Create many small objects with different animations
|
||||
num_objects = 50
|
||||
|
||||
for i in range(num_objects):
|
||||
# Random starting position
|
||||
x = 100 + (i % 10) * 100
|
||||
y = 150 + (i // 10) * 80
|
||||
|
||||
# Create small frame
|
||||
size = 20 + (i % 3) * 10
|
||||
frame = mcrfpy.Frame(x, y, size, size)
|
||||
|
||||
# Random color
|
||||
r = (i * 37) % 256
|
||||
g = (i * 73) % 256
|
||||
b = (i * 113) % 256
|
||||
frame.fill_color = mcrfpy.Color(r, g, b, 200)
|
||||
frame.outline = 1
|
||||
ui.append(frame)
|
||||
|
||||
# Random animation properties
|
||||
target_x = 100 + (i % 8) * 120
|
||||
target_y = 150 + (i // 8) * 100
|
||||
duration = 2.0 + (i % 30) * 0.1
|
||||
easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)]
|
||||
|
||||
# Start multiple animations per object
|
||||
x_anim = mcrfpy.Animation("x", float(target_x), duration, easing)
|
||||
y_anim = mcrfpy.Animation("y", float(target_y), duration, easing)
|
||||
opacity_anim = mcrfpy.Animation("opacity", 0.3 + (i % 7) * 0.1, duration, "easeInOutSine")
|
||||
|
||||
x_anim.start(frame)
|
||||
y_anim.start(frame)
|
||||
opacity_anim.start(frame)
|
||||
|
||||
# Performance counter
|
||||
perf_caption = mcrfpy.Caption(f"Animating {num_objects * 3} properties simultaneously", 400, 600)
|
||||
perf_caption.fill_color = mcrfpy.Color(255, 255, 0)
|
||||
ui.append(perf_caption)
|
||||
|
||||
def clear_scene():
|
||||
"""Clear the scene except title and subtitle"""
|
||||
ui = mcrfpy.sceneUI("sizzle_reel")
|
||||
|
||||
# Keep only the first two elements (title and subtitle)
|
||||
while len(ui) > 2:
|
||||
ui.remove(2)
|
||||
|
||||
def run_demo_sequence(runtime):
|
||||
"""Run through all demos"""
|
||||
global current_demo
|
||||
|
||||
# Clear previous demo
|
||||
clear_scene()
|
||||
|
||||
# Demo list
|
||||
demos = [
|
||||
demo_frame_basic_animations,
|
||||
demo_caption_animations,
|
||||
demo_sprite_animations,
|
||||
demo_performance_stress_test
|
||||
]
|
||||
|
||||
if current_demo < len(demos):
|
||||
# Run current demo
|
||||
demos[current_demo]()
|
||||
current_demo += 1
|
||||
|
||||
# Schedule next demo
|
||||
if current_demo < len(demos):
|
||||
mcrfpy.setTimer("next_demo", run_demo_sequence, int(DEMO_DURATION * 1000))
|
||||
else:
|
||||
# All demos complete
|
||||
subtitle.text = "Animation Showcase Complete!"
|
||||
complete = mcrfpy.Caption("All animation types demonstrated!", 400, 350)
|
||||
complete.fill_color = mcrfpy.Color(0, 255, 0)
|
||||
complete.outline = 2
|
||||
ui = mcrfpy.sceneUI("sizzle_reel")
|
||||
ui.append(complete)
|
||||
|
||||
# Initialize scene
|
||||
print("Starting McRogueFace Animation Sizzle Reel...")
|
||||
print("This will demonstrate animation types on various objects.")
|
||||
|
||||
ui = create_demo_scene()
|
||||
|
||||
# Start the demo sequence after a short delay
|
||||
mcrfpy.setTimer("start_demos", run_demo_sequence, 500)
|
||||
307
tests/demos/animation_sizzle_reel_v2.py
Normal file
307
tests/demos/animation_sizzle_reel_v2.py
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
McRogueFace Animation Sizzle Reel v2
|
||||
====================================
|
||||
|
||||
Fixed version with proper API usage for animations and collections.
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
|
||||
# Configuration
|
||||
SCENE_WIDTH = 1280
|
||||
SCENE_HEIGHT = 720
|
||||
DEMO_DURATION = 5.0 # Duration for each demo section
|
||||
|
||||
# All available easing functions
|
||||
EASING_FUNCTIONS = [
|
||||
"linear", "easeIn", "easeOut", "easeInOut",
|
||||
"easeInQuad", "easeOutQuad", "easeInOutQuad",
|
||||
"easeInCubic", "easeOutCubic", "easeInOutCubic",
|
||||
"easeInQuart", "easeOutQuart", "easeInOutQuart",
|
||||
"easeInSine", "easeOutSine", "easeInOutSine",
|
||||
"easeInExpo", "easeOutExpo", "easeInOutExpo",
|
||||
"easeInCirc", "easeOutCirc", "easeInOutCirc",
|
||||
"easeInElastic", "easeOutElastic", "easeInOutElastic",
|
||||
"easeInBack", "easeOutBack", "easeInOutBack",
|
||||
"easeInBounce", "easeOutBounce", "easeInOutBounce"
|
||||
]
|
||||
|
||||
# Track current demo state
|
||||
current_demo = 0
|
||||
subtitle = None
|
||||
demo_objects = [] # Track objects from current demo
|
||||
|
||||
def create_demo_scene():
|
||||
"""Create the main demo scene with title"""
|
||||
mcrfpy.createScene("sizzle_reel")
|
||||
mcrfpy.setScene("sizzle_reel")
|
||||
|
||||
ui = mcrfpy.sceneUI("sizzle_reel")
|
||||
|
||||
# Title caption
|
||||
title = mcrfpy.Caption("McRogueFace Animation Sizzle Reel",
|
||||
SCENE_WIDTH/2 - 200, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 0)
|
||||
title.outline = 2
|
||||
title.outline_color = mcrfpy.Color(0, 0, 0)
|
||||
ui.append(title)
|
||||
|
||||
# Subtitle showing current demo
|
||||
global subtitle
|
||||
subtitle = mcrfpy.Caption("Initializing...",
|
||||
SCENE_WIDTH/2 - 150, 60)
|
||||
subtitle.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(subtitle)
|
||||
|
||||
return ui
|
||||
|
||||
def demo_frame_basic_animations():
|
||||
"""Demo 1: Basic frame animations - position, size, colors"""
|
||||
global demo_objects
|
||||
demo_objects = []
|
||||
|
||||
ui = mcrfpy.sceneUI("sizzle_reel")
|
||||
subtitle.text = "Demo 1: Frame Basic Animations (Position, Size, Colors)"
|
||||
|
||||
# Create test frame
|
||||
frame = mcrfpy.Frame(100, 150, 200, 100)
|
||||
frame.fill_color = mcrfpy.Color(50, 50, 150)
|
||||
frame.outline = 3
|
||||
frame.outline_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(frame)
|
||||
demo_objects.append(frame)
|
||||
|
||||
# Position animations with different easings
|
||||
x_anim = mcrfpy.Animation("x", 800.0, 2.0, "easeInOutBack")
|
||||
y_anim = mcrfpy.Animation("y", 400.0, 2.0, "easeInOutElastic")
|
||||
x_anim.start(frame)
|
||||
y_anim.start(frame)
|
||||
|
||||
# Size animations
|
||||
w_anim = mcrfpy.Animation("w", 400.0, 3.0, "easeInOutCubic")
|
||||
h_anim = mcrfpy.Animation("h", 200.0, 3.0, "easeInOutCubic")
|
||||
w_anim.start(frame)
|
||||
h_anim.start(frame)
|
||||
|
||||
# Color animations - use tuples instead of Color objects
|
||||
fill_anim = mcrfpy.Animation("fill_color", (255, 100, 50, 200), 4.0, "easeInOutSine")
|
||||
outline_anim = mcrfpy.Animation("outline_color", (0, 255, 255, 255), 4.0, "easeOutBounce")
|
||||
fill_anim.start(frame)
|
||||
outline_anim.start(frame)
|
||||
|
||||
# Outline thickness animation
|
||||
thickness_anim = mcrfpy.Animation("outline", 10.0, 4.5, "easeInOutQuad")
|
||||
thickness_anim.start(frame)
|
||||
|
||||
def demo_caption_animations():
|
||||
"""Demo 2: Caption text animations and effects"""
|
||||
global demo_objects
|
||||
demo_objects = []
|
||||
|
||||
ui = mcrfpy.sceneUI("sizzle_reel")
|
||||
subtitle.text = "Demo 2: Caption Animations (Text, Color, Position)"
|
||||
|
||||
# Basic caption with position animation
|
||||
caption1 = mcrfpy.Caption("Moving Text!", 100, 200)
|
||||
caption1.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
caption1.outline = 1
|
||||
ui.append(caption1)
|
||||
demo_objects.append(caption1)
|
||||
|
||||
# Animate across screen with bounce
|
||||
x_anim = mcrfpy.Animation("x", 900.0, 3.0, "easeOutBounce")
|
||||
x_anim.start(caption1)
|
||||
|
||||
# Color cycling caption
|
||||
caption2 = mcrfpy.Caption("Rainbow Colors", 400, 300)
|
||||
caption2.outline = 2
|
||||
ui.append(caption2)
|
||||
demo_objects.append(caption2)
|
||||
|
||||
# Cycle through colors using tuples
|
||||
color_anim1 = mcrfpy.Animation("fill_color", (255, 0, 0, 255), 1.0, "linear")
|
||||
color_anim1.start(caption2)
|
||||
|
||||
# Schedule color changes
|
||||
def change_to_green(rt):
|
||||
color_anim2 = mcrfpy.Animation("fill_color", (0, 255, 0, 255), 1.0, "linear")
|
||||
color_anim2.start(caption2)
|
||||
|
||||
def change_to_blue(rt):
|
||||
color_anim3 = mcrfpy.Animation("fill_color", (0, 0, 255, 255), 1.0, "linear")
|
||||
color_anim3.start(caption2)
|
||||
|
||||
def change_to_white(rt):
|
||||
color_anim4 = mcrfpy.Animation("fill_color", (255, 255, 255, 255), 1.0, "linear")
|
||||
color_anim4.start(caption2)
|
||||
|
||||
mcrfpy.setTimer("color2", change_to_green, 1000)
|
||||
mcrfpy.setTimer("color3", change_to_blue, 2000)
|
||||
mcrfpy.setTimer("color4", change_to_white, 3000)
|
||||
|
||||
# Typewriter effect caption
|
||||
caption3 = mcrfpy.Caption("", 100, 400)
|
||||
caption3.fill_color = mcrfpy.Color(0, 255, 255)
|
||||
ui.append(caption3)
|
||||
demo_objects.append(caption3)
|
||||
|
||||
typewriter = mcrfpy.Animation("text", "This text appears one character at a time...", 3.0, "linear")
|
||||
typewriter.start(caption3)
|
||||
|
||||
def demo_easing_showcase():
|
||||
"""Demo 3: Showcase different easing functions"""
|
||||
global demo_objects
|
||||
demo_objects = []
|
||||
|
||||
ui = mcrfpy.sceneUI("sizzle_reel")
|
||||
subtitle.text = "Demo 3: Easing Functions Showcase"
|
||||
|
||||
# Create small frames for each easing function
|
||||
frames_per_row = 6
|
||||
frame_width = 180
|
||||
spacing = 10
|
||||
|
||||
# Show first 12 easings
|
||||
for i, easing in enumerate(EASING_FUNCTIONS[:12]):
|
||||
row = i // frames_per_row
|
||||
col = i % frames_per_row
|
||||
|
||||
x = 50 + col * (frame_width + spacing)
|
||||
y = 150 + row * (80 + spacing)
|
||||
|
||||
# Create indicator frame
|
||||
frame = mcrfpy.Frame(x, y, 20, 20)
|
||||
frame.fill_color = mcrfpy.Color(100, 200, 255)
|
||||
frame.outline = 1
|
||||
ui.append(frame)
|
||||
demo_objects.append(frame)
|
||||
|
||||
# Label
|
||||
label = mcrfpy.Caption(easing[:8], x, y - 20) # Truncate long names
|
||||
label.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(label)
|
||||
demo_objects.append(label)
|
||||
|
||||
# Animate using this easing
|
||||
move_anim = mcrfpy.Animation("x", float(x + frame_width - 20), 3.0, easing)
|
||||
move_anim.start(frame)
|
||||
|
||||
def demo_performance_stress_test():
|
||||
"""Demo 4: Performance test with many simultaneous animations"""
|
||||
global demo_objects
|
||||
demo_objects = []
|
||||
|
||||
ui = mcrfpy.sceneUI("sizzle_reel")
|
||||
subtitle.text = "Demo 4: Performance Test (50+ Simultaneous Animations)"
|
||||
|
||||
# Create many small objects with different animations
|
||||
num_objects = 50
|
||||
|
||||
for i in range(num_objects):
|
||||
# Starting position
|
||||
x = 100 + (i % 10) * 100
|
||||
y = 150 + (i // 10) * 80
|
||||
|
||||
# Create small frame
|
||||
size = 20 + (i % 3) * 10
|
||||
frame = mcrfpy.Frame(x, y, size, size)
|
||||
|
||||
# Random color
|
||||
r = (i * 37) % 256
|
||||
g = (i * 73) % 256
|
||||
b = (i * 113) % 256
|
||||
frame.fill_color = mcrfpy.Color(r, g, b, 200)
|
||||
frame.outline = 1
|
||||
ui.append(frame)
|
||||
demo_objects.append(frame)
|
||||
|
||||
# Random animation properties
|
||||
target_x = 100 + (i % 8) * 120
|
||||
target_y = 150 + (i // 8) * 100
|
||||
duration = 2.0 + (i % 30) * 0.1
|
||||
easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)]
|
||||
|
||||
# Start multiple animations per object
|
||||
x_anim = mcrfpy.Animation("x", float(target_x), duration, easing)
|
||||
y_anim = mcrfpy.Animation("y", float(target_y), duration, easing)
|
||||
opacity_anim = mcrfpy.Animation("opacity", 0.3 + (i % 7) * 0.1, duration, "easeInOutSine")
|
||||
|
||||
x_anim.start(frame)
|
||||
y_anim.start(frame)
|
||||
opacity_anim.start(frame)
|
||||
|
||||
# Performance counter
|
||||
perf_caption = mcrfpy.Caption(f"Animating {num_objects * 3} properties simultaneously", 350, 600)
|
||||
perf_caption.fill_color = mcrfpy.Color(255, 255, 0)
|
||||
ui.append(perf_caption)
|
||||
demo_objects.append(perf_caption)
|
||||
|
||||
def clear_scene():
|
||||
"""Clear the scene except title and subtitle"""
|
||||
global demo_objects
|
||||
ui = mcrfpy.sceneUI("sizzle_reel")
|
||||
|
||||
# Remove all demo objects
|
||||
for obj in demo_objects:
|
||||
try:
|
||||
# Find index of object
|
||||
for i in range(len(ui)):
|
||||
if ui[i] is obj:
|
||||
ui.remove(ui[i])
|
||||
break
|
||||
except:
|
||||
pass # Object might already be removed
|
||||
|
||||
demo_objects = []
|
||||
|
||||
# Clean up any timers
|
||||
for timer_name in ["color2", "color3", "color4"]:
|
||||
try:
|
||||
mcrfpy.delTimer(timer_name)
|
||||
except:
|
||||
pass
|
||||
|
||||
def run_demo_sequence(runtime):
|
||||
"""Run through all demos"""
|
||||
global current_demo
|
||||
|
||||
# Clear previous demo
|
||||
clear_scene()
|
||||
|
||||
# Demo list
|
||||
demos = [
|
||||
demo_frame_basic_animations,
|
||||
demo_caption_animations,
|
||||
demo_easing_showcase,
|
||||
demo_performance_stress_test
|
||||
]
|
||||
|
||||
if current_demo < len(demos):
|
||||
# Run current demo
|
||||
demos[current_demo]()
|
||||
current_demo += 1
|
||||
|
||||
# Schedule next demo
|
||||
if current_demo < len(demos):
|
||||
mcrfpy.setTimer("next_demo", run_demo_sequence, int(DEMO_DURATION * 1000))
|
||||
else:
|
||||
# Final demo completed
|
||||
def show_complete(rt):
|
||||
subtitle.text = "Animation Showcase Complete!"
|
||||
complete = mcrfpy.Caption("All animation types demonstrated!", 400, 350)
|
||||
complete.fill_color = mcrfpy.Color(0, 255, 0)
|
||||
complete.outline = 2
|
||||
ui = mcrfpy.sceneUI("sizzle_reel")
|
||||
ui.append(complete)
|
||||
|
||||
mcrfpy.setTimer("complete", show_complete, 3000)
|
||||
|
||||
# Initialize scene
|
||||
print("Starting McRogueFace Animation Sizzle Reel v2...")
|
||||
print("This will demonstrate animation types on various objects.")
|
||||
|
||||
ui = create_demo_scene()
|
||||
|
||||
# Start the demo sequence after a short delay
|
||||
mcrfpy.setTimer("start_demos", run_demo_sequence, 500)
|
||||
316
tests/demos/animation_sizzle_reel_working.py
Normal file
316
tests/demos/animation_sizzle_reel_working.py
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
McRogueFace Animation Sizzle Reel - Working Version
|
||||
===================================================
|
||||
|
||||
Complete demonstration of all animation capabilities.
|
||||
Fixed to work properly with the API.
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
import math
|
||||
|
||||
# Configuration
|
||||
DEMO_DURATION = 7.0 # Duration for each demo
|
||||
|
||||
# All available easing functions
|
||||
EASING_FUNCTIONS = [
|
||||
"linear", "easeIn", "easeOut", "easeInOut",
|
||||
"easeInQuad", "easeOutQuad", "easeInOutQuad",
|
||||
"easeInCubic", "easeOutCubic", "easeInOutCubic",
|
||||
"easeInQuart", "easeOutQuart", "easeInOutQuart",
|
||||
"easeInSine", "easeOutSine", "easeInOutSine",
|
||||
"easeInExpo", "easeOutExpo", "easeInOutExpo",
|
||||
"easeInCirc", "easeOutCirc", "easeInOutCirc",
|
||||
"easeInElastic", "easeOutElastic", "easeInOutElastic",
|
||||
"easeInBack", "easeOutBack", "easeInOutBack",
|
||||
"easeInBounce", "easeOutBounce", "easeInOutBounce"
|
||||
]
|
||||
|
||||
# Track state
|
||||
current_demo = 0
|
||||
subtitle = None
|
||||
demo_objects = []
|
||||
|
||||
def create_scene():
|
||||
"""Create the demo scene with title"""
|
||||
mcrfpy.createScene("sizzle")
|
||||
mcrfpy.setScene("sizzle")
|
||||
|
||||
ui = mcrfpy.sceneUI("sizzle")
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption("McRogueFace Animation Sizzle Reel", 340, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 0)
|
||||
title.outline = 2
|
||||
title.outline_color = mcrfpy.Color(0, 0, 0)
|
||||
ui.append(title)
|
||||
|
||||
# Subtitle
|
||||
global subtitle
|
||||
subtitle = mcrfpy.Caption("Initializing...", 400, 60)
|
||||
subtitle.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(subtitle)
|
||||
|
||||
def clear_demo():
|
||||
"""Clear demo objects"""
|
||||
global demo_objects
|
||||
ui = mcrfpy.sceneUI("sizzle")
|
||||
|
||||
# Remove items starting from the end
|
||||
# Skip first 2 (title and subtitle)
|
||||
while len(ui) > 2:
|
||||
ui.remove(len(ui) - 1)
|
||||
|
||||
demo_objects = []
|
||||
|
||||
def demo1_frame_basics():
|
||||
"""Demo 1: Basic frame animations"""
|
||||
clear_demo()
|
||||
print("demo1")
|
||||
subtitle.text = "Demo 1: Frame Animations (Position, Size, Color)"
|
||||
|
||||
ui = mcrfpy.sceneUI("sizzle")
|
||||
|
||||
# Create frame
|
||||
frame = mcrfpy.Frame(100, 150, 200, 100)
|
||||
frame.fill_color = mcrfpy.Color(50, 50, 150)
|
||||
frame.outline = 3
|
||||
frame.outline_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(frame)
|
||||
|
||||
# Animate properties
|
||||
mcrfpy.Animation("x", 700.0, 2.5, "easeInOutBack").start(frame)
|
||||
mcrfpy.Animation("y", 350.0, 2.5, "easeInOutElastic").start(frame)
|
||||
mcrfpy.Animation("w", 350.0, 3.0, "easeInOutCubic").start(frame)
|
||||
mcrfpy.Animation("h", 180.0, 3.0, "easeInOutCubic").start(frame)
|
||||
mcrfpy.Animation("fill_color", (255, 100, 50, 200), 4.0, "easeInOutSine").start(frame)
|
||||
mcrfpy.Animation("outline_color", (0, 255, 255, 255), 4.0, "easeOutBounce").start(frame)
|
||||
mcrfpy.Animation("outline", 8.0, 4.0, "easeInOutQuad").start(frame)
|
||||
|
||||
def demo2_opacity_zindex():
|
||||
"""Demo 2: Opacity and z-index animations"""
|
||||
clear_demo()
|
||||
print("demo2")
|
||||
subtitle.text = "Demo 2: Opacity & Z-Index Animations"
|
||||
|
||||
ui = mcrfpy.sceneUI("sizzle")
|
||||
|
||||
# Create overlapping frames
|
||||
colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0)]
|
||||
|
||||
for i in range(4):
|
||||
frame = mcrfpy.Frame(200 + i*80, 200 + i*40, 200, 150)
|
||||
frame.fill_color = mcrfpy.Color(colors[i][0], colors[i][1], colors[i][2], 200)
|
||||
frame.outline = 2
|
||||
frame.z_index = i
|
||||
ui.append(frame)
|
||||
|
||||
# Animate opacity
|
||||
mcrfpy.Animation("opacity", 0.3, 2.0, "easeInOutSine").start(frame)
|
||||
|
||||
# Schedule opacity return
|
||||
def return_opacity(rt):
|
||||
for i in range(4):
|
||||
mcrfpy.Animation("opacity", 1.0, 2.0, "easeInOutSine").start(ui[i])
|
||||
mcrfpy.setTimer(f"opacity_{i}", return_opacity, 2100)
|
||||
|
||||
def demo3_captions():
|
||||
"""Demo 3: Caption animations"""
|
||||
clear_demo()
|
||||
print("demo3")
|
||||
subtitle.text = "Demo 3: Caption Animations"
|
||||
|
||||
ui = mcrfpy.sceneUI("sizzle")
|
||||
|
||||
# Moving caption
|
||||
c1 = mcrfpy.Caption("Bouncing Text!", 100, 200)
|
||||
c1.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
c1.outline = 1
|
||||
ui.append(c1)
|
||||
mcrfpy.Animation("x", 800.0, 3.0, "easeOutBounce").start(c1)
|
||||
|
||||
# Color cycling caption
|
||||
c2 = mcrfpy.Caption("Color Cycle", 400, 300)
|
||||
c2.outline = 2
|
||||
ui.append(c2)
|
||||
|
||||
# Animate through colors
|
||||
def cycle_colors():
|
||||
anim = mcrfpy.Animation("fill_color", (255, 0, 0, 255), 0.5, "linear")
|
||||
anim.start(c2)
|
||||
|
||||
def to_green(rt):
|
||||
mcrfpy.Animation("fill_color", (0, 255, 0, 255), 0.5, "linear").start(c2)
|
||||
def to_blue(rt):
|
||||
mcrfpy.Animation("fill_color", (0, 0, 255, 255), 0.5, "linear").start(c2)
|
||||
def to_white(rt):
|
||||
mcrfpy.Animation("fill_color", (255, 255, 255, 255), 0.5, "linear").start(c2)
|
||||
|
||||
mcrfpy.setTimer("c_green", to_green, 600)
|
||||
mcrfpy.setTimer("c_blue", to_blue, 1200)
|
||||
mcrfpy.setTimer("c_white", to_white, 1800)
|
||||
|
||||
cycle_colors()
|
||||
|
||||
# Typewriter effect
|
||||
c3 = mcrfpy.Caption("", 100, 400)
|
||||
c3.fill_color = mcrfpy.Color(0, 255, 255)
|
||||
ui.append(c3)
|
||||
mcrfpy.Animation("text", "This text appears one character at a time...", 3.0, "linear").start(c3)
|
||||
|
||||
def demo4_easing_showcase():
|
||||
"""Demo 4: Showcase easing functions"""
|
||||
clear_demo()
|
||||
print("demo4")
|
||||
subtitle.text = "Demo 4: 30 Easing Functions"
|
||||
|
||||
ui = mcrfpy.sceneUI("sizzle")
|
||||
|
||||
# Show first 15 easings
|
||||
for i in range(15):
|
||||
row = i // 5
|
||||
col = i % 5
|
||||
x = 80 + col * 180
|
||||
y = 150 + row * 120
|
||||
|
||||
# Create frame
|
||||
f = mcrfpy.Frame(x, y, 20, 20)
|
||||
f.fill_color = mcrfpy.Color(100, 150, 255)
|
||||
f.outline = 1
|
||||
ui.append(f)
|
||||
|
||||
# Label
|
||||
label = mcrfpy.Caption(EASING_FUNCTIONS[i][:10], x, y - 20)
|
||||
label.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(label)
|
||||
|
||||
# Animate with this easing
|
||||
mcrfpy.Animation("x", float(x + 140), 3.0, EASING_FUNCTIONS[i]).start(f)
|
||||
|
||||
def demo5_performance():
|
||||
"""Demo 5: Many simultaneous animations"""
|
||||
clear_demo()
|
||||
print("demo5")
|
||||
subtitle.text = "Demo 5: 50+ Simultaneous Animations"
|
||||
|
||||
ui = mcrfpy.sceneUI("sizzle")
|
||||
|
||||
# Create many animated objects
|
||||
for i in range(50):
|
||||
print(f"{i}...",end='',flush=True)
|
||||
x = 100 + (i % 10) * 90
|
||||
y = 120 + (i // 10) * 80
|
||||
|
||||
f = mcrfpy.Frame(x, y, 25, 25)
|
||||
r = (i * 37) % 256
|
||||
g = (i * 73) % 256
|
||||
b = (i * 113) % 256
|
||||
f.fill_color = (r, g, b, 200) #mcrfpy.Color(r, g, b, 200)
|
||||
f.outline = 1
|
||||
ui.append(f)
|
||||
|
||||
# Random animations
|
||||
target_x = 150 + (i % 8) * 100
|
||||
target_y = 150 + (i // 8) * 85
|
||||
duration = 2.0 + (i % 30) * 0.1
|
||||
easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)]
|
||||
|
||||
mcrfpy.Animation("x", float(target_x), duration, easing).start(f)
|
||||
mcrfpy.Animation("y", float(target_y), duration, easing).start(f)
|
||||
mcrfpy.Animation("opacity", 0.3 + (i % 7) * 0.1, 2.5, "easeInOutSine").start(f)
|
||||
|
||||
def demo6_delta_mode():
|
||||
"""Demo 6: Delta mode animations"""
|
||||
clear_demo()
|
||||
print("demo6")
|
||||
subtitle.text = "Demo 6: Delta Mode (Relative Movement)"
|
||||
|
||||
ui = mcrfpy.sceneUI("sizzle")
|
||||
|
||||
# Create frames that move relative to position
|
||||
positions = [(100, 300), (300, 300), (500, 300), (700, 300)]
|
||||
colors = [(255, 100, 100), (100, 255, 100), (100, 100, 255), (255, 255, 100)]
|
||||
|
||||
for i, ((x, y), color) in enumerate(zip(positions, colors)):
|
||||
f = mcrfpy.Frame(x, y, 60, 60)
|
||||
f.fill_color = mcrfpy.Color(color[0], color[1], color[2])
|
||||
f.outline = 2
|
||||
ui.append(f)
|
||||
|
||||
# Delta animations - move by amount, not to position
|
||||
dx = (i + 1) * 30
|
||||
dy = math.sin(i * 0.5) * 50
|
||||
|
||||
mcrfpy.Animation("x", float(dx), 2.0, "easeInOutBack", delta=True).start(f)
|
||||
mcrfpy.Animation("y", float(dy), 2.0, "easeInOutElastic", delta=True).start(f)
|
||||
|
||||
# Caption explaining delta mode
|
||||
info = mcrfpy.Caption("Delta mode: animations move BY amount, not TO position", 200, 450)
|
||||
info.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(info)
|
||||
|
||||
def run_next_demo(runtime):
|
||||
"""Run the next demo in sequence"""
|
||||
global current_demo
|
||||
|
||||
demos = [
|
||||
demo1_frame_basics,
|
||||
demo2_opacity_zindex,
|
||||
demo3_captions,
|
||||
demo4_easing_showcase,
|
||||
demo5_performance,
|
||||
demo6_delta_mode
|
||||
]
|
||||
|
||||
if current_demo < len(demos):
|
||||
# Clean up timers from previous demo
|
||||
for timer in ["opacity_0", "opacity_1", "opacity_2", "opacity_3",
|
||||
"c_green", "c_blue", "c_white"]:
|
||||
try:
|
||||
mcrfpy.delTimer(timer)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Run next demo
|
||||
print(f"Run next: {current_demo}")
|
||||
demos[current_demo]()
|
||||
current_demo += 1
|
||||
|
||||
# Schedule next demo
|
||||
if current_demo < len(demos):
|
||||
#mcrfpy.setTimer("next_demo", run_next_demo, int(DEMO_DURATION * 1000))
|
||||
pass
|
||||
else:
|
||||
current_demo = 0
|
||||
# All done
|
||||
#subtitle.text = "Animation Showcase Complete!"
|
||||
#complete = mcrfpy.Caption("All animations demonstrated successfully!", 350, 350)
|
||||
#complete.fill_color = mcrfpy.Color(0, 255, 0)
|
||||
#complete.outline = 2
|
||||
#ui = mcrfpy.sceneUI("sizzle")
|
||||
#ui.append(complete)
|
||||
#
|
||||
## Exit after delay
|
||||
#def exit_program(rt):
|
||||
# print("\nSizzle reel completed successfully!")
|
||||
# sys.exit(0)
|
||||
#mcrfpy.setTimer("exit", exit_program, 3000)
|
||||
|
||||
# Handle ESC key
|
||||
def handle_keypress(scene_name, keycode):
|
||||
if keycode == 256: # ESC
|
||||
print("\nExiting...")
|
||||
sys.exit(0)
|
||||
|
||||
# Initialize
|
||||
print("Starting McRogueFace Animation Sizzle Reel...")
|
||||
print("This demonstrates all animation capabilities.")
|
||||
print("Press ESC to exit at any time.")
|
||||
|
||||
create_scene()
|
||||
mcrfpy.keypressScene(handle_keypress)
|
||||
|
||||
# Start the show
|
||||
mcrfpy.setTimer("start", run_next_demo, int(DEMO_DURATION * 1000))
|
||||
207
tests/demos/api_demo_final.py
Normal file
207
tests/demos/api_demo_final.py
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
McRogueFace API Demo - Final Version
|
||||
====================================
|
||||
|
||||
Complete API demonstration with proper error handling.
|
||||
Tests all constructors and methods systematically.
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
def print_section(title):
|
||||
"""Print a section header"""
|
||||
print("\n" + "="*60)
|
||||
print(f" {title}")
|
||||
print("="*60)
|
||||
|
||||
def print_test(name, success=True):
|
||||
"""Print test result"""
|
||||
status = "✓" if success else "✗"
|
||||
print(f" {status} {name}")
|
||||
|
||||
def test_colors():
|
||||
"""Test Color API"""
|
||||
print_section("COLOR TESTS")
|
||||
|
||||
try:
|
||||
# Basic constructors
|
||||
c1 = mcrfpy.Color(255, 0, 0) # RGB
|
||||
print_test(f"Color(255,0,0) = ({c1.r},{c1.g},{c1.b},{c1.a})")
|
||||
|
||||
c2 = mcrfpy.Color(100, 150, 200, 128) # RGBA
|
||||
print_test(f"Color(100,150,200,128) = ({c2.r},{c2.g},{c2.b},{c2.a})")
|
||||
|
||||
# Property modification
|
||||
c1.r = 128
|
||||
c1.g = 128
|
||||
c1.b = 128
|
||||
c1.a = 200
|
||||
print_test(f"Modified color = ({c1.r},{c1.g},{c1.b},{c1.a})")
|
||||
|
||||
except Exception as e:
|
||||
print_test(f"Color test failed: {e}", False)
|
||||
|
||||
def test_frames():
|
||||
"""Test Frame API"""
|
||||
print_section("FRAME TESTS")
|
||||
|
||||
# Create scene
|
||||
mcrfpy.createScene("test")
|
||||
mcrfpy.setScene("test")
|
||||
ui = mcrfpy.sceneUI("test")
|
||||
|
||||
try:
|
||||
# Constructors
|
||||
f1 = mcrfpy.Frame()
|
||||
print_test(f"Frame() at ({f1.x},{f1.y}) size ({f1.w},{f1.h})")
|
||||
|
||||
f2 = mcrfpy.Frame(100, 50)
|
||||
print_test(f"Frame(100,50) at ({f2.x},{f2.y})")
|
||||
|
||||
f3 = mcrfpy.Frame(200, 100, 150, 75)
|
||||
print_test(f"Frame(200,100,150,75) size ({f3.w},{f3.h})")
|
||||
|
||||
# Properties
|
||||
f3.fill_color = mcrfpy.Color(100, 100, 200)
|
||||
f3.outline = 3
|
||||
f3.outline_color = mcrfpy.Color(255, 255, 0)
|
||||
f3.opacity = 0.8
|
||||
f3.visible = True
|
||||
f3.z_index = 5
|
||||
print_test(f"Frame properties set")
|
||||
|
||||
# Add to scene
|
||||
ui.append(f3)
|
||||
print_test(f"Frame added to scene")
|
||||
|
||||
# Children
|
||||
child = mcrfpy.Frame(10, 10, 50, 50)
|
||||
f3.children.append(child)
|
||||
print_test(f"Child added, count = {len(f3.children)}")
|
||||
|
||||
except Exception as e:
|
||||
print_test(f"Frame test failed: {e}", False)
|
||||
|
||||
def test_captions():
|
||||
"""Test Caption API"""
|
||||
print_section("CAPTION TESTS")
|
||||
|
||||
ui = mcrfpy.sceneUI("test")
|
||||
|
||||
try:
|
||||
# Constructors
|
||||
c1 = mcrfpy.Caption()
|
||||
print_test(f"Caption() text='{c1.text}'")
|
||||
|
||||
c2 = mcrfpy.Caption("Hello World")
|
||||
print_test(f"Caption('Hello World') at ({c2.x},{c2.y})")
|
||||
|
||||
c3 = mcrfpy.Caption("Test", 300, 200)
|
||||
print_test(f"Caption with position at ({c3.x},{c3.y})")
|
||||
|
||||
# Properties
|
||||
c3.text = "Modified"
|
||||
c3.fill_color = mcrfpy.Color(255, 255, 0)
|
||||
c3.outline = 2
|
||||
c3.outline_color = mcrfpy.Color(0, 0, 0)
|
||||
print_test(f"Caption text='{c3.text}'")
|
||||
|
||||
ui.append(c3)
|
||||
print_test("Caption added to scene")
|
||||
|
||||
except Exception as e:
|
||||
print_test(f"Caption test failed: {e}", False)
|
||||
|
||||
def test_animations():
|
||||
"""Test Animation API"""
|
||||
print_section("ANIMATION TESTS")
|
||||
|
||||
ui = mcrfpy.sceneUI("test")
|
||||
|
||||
try:
|
||||
# Create target
|
||||
frame = mcrfpy.Frame(50, 50, 100, 100)
|
||||
frame.fill_color = mcrfpy.Color(100, 100, 100)
|
||||
ui.append(frame)
|
||||
|
||||
# Basic animations
|
||||
a1 = mcrfpy.Animation("x", 300.0, 2.0)
|
||||
print_test("Animation created (position)")
|
||||
|
||||
a2 = mcrfpy.Animation("opacity", 0.5, 1.5, "easeInOut")
|
||||
print_test("Animation with easing")
|
||||
|
||||
a3 = mcrfpy.Animation("fill_color", (255, 0, 0, 255), 2.0)
|
||||
print_test("Color animation (tuple)")
|
||||
|
||||
# Start animations
|
||||
a1.start(frame)
|
||||
a2.start(frame)
|
||||
a3.start(frame)
|
||||
print_test("Animations started")
|
||||
|
||||
# Check properties
|
||||
print_test(f"Duration = {a1.duration}")
|
||||
print_test(f"Elapsed = {a1.elapsed}")
|
||||
print_test(f"Complete = {a1.is_complete}")
|
||||
|
||||
except Exception as e:
|
||||
print_test(f"Animation test failed: {e}", False)
|
||||
|
||||
def test_collections():
|
||||
"""Test collection operations"""
|
||||
print_section("COLLECTION TESTS")
|
||||
|
||||
ui = mcrfpy.sceneUI("test")
|
||||
|
||||
try:
|
||||
# Clear scene
|
||||
while len(ui) > 0:
|
||||
ui.remove(ui[len(ui)-1])
|
||||
print_test(f"Scene cleared, length = {len(ui)}")
|
||||
|
||||
# Add items
|
||||
for i in range(5):
|
||||
f = mcrfpy.Frame(i*100, 50, 80, 80)
|
||||
ui.append(f)
|
||||
print_test(f"Added 5 frames, length = {len(ui)}")
|
||||
|
||||
# Access
|
||||
first = ui[0]
|
||||
print_test(f"Accessed ui[0] at ({first.x},{first.y})")
|
||||
|
||||
# Iteration
|
||||
count = sum(1 for _ in ui)
|
||||
print_test(f"Iteration count = {count}")
|
||||
|
||||
except Exception as e:
|
||||
print_test(f"Collection test failed: {e}", False)
|
||||
|
||||
def run_tests():
|
||||
"""Run all tests"""
|
||||
print("\n" + "="*60)
|
||||
print(" McRogueFace API Test Suite")
|
||||
print("="*60)
|
||||
|
||||
test_colors()
|
||||
test_frames()
|
||||
test_captions()
|
||||
test_animations()
|
||||
test_collections()
|
||||
|
||||
print("\n" + "="*60)
|
||||
print(" Tests Complete")
|
||||
print("="*60)
|
||||
|
||||
# Exit after delay
|
||||
def exit_program(runtime):
|
||||
print("\nExiting...")
|
||||
sys.exit(0)
|
||||
|
||||
mcrfpy.setTimer("exit", exit_program, 3000)
|
||||
|
||||
# Run tests
|
||||
print("Starting API tests...")
|
||||
run_tests()
|
||||
99
tests/demos/debug_astar_demo.py
Normal file
99
tests/demos/debug_astar_demo.py
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Debug the astar_vs_dijkstra demo issue"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# Same setup as the demo
|
||||
start_pos = (5, 10)
|
||||
end_pos = (25, 10)
|
||||
|
||||
print("Debugging A* vs Dijkstra demo...")
|
||||
print(f"Start: {start_pos}, End: {end_pos}")
|
||||
|
||||
# Create scene and grid
|
||||
mcrfpy.createScene("debug")
|
||||
grid = mcrfpy.Grid(grid_x=30, grid_y=20)
|
||||
|
||||
# Initialize all as floor
|
||||
print("\nInitializing 30x20 grid...")
|
||||
for y in range(20):
|
||||
for x in range(30):
|
||||
grid.at(x, y).walkable = True
|
||||
|
||||
# Test path before obstacles
|
||||
print("\nTest 1: Path with no obstacles")
|
||||
path1 = grid.compute_astar_path(start_pos[0], start_pos[1], end_pos[0], end_pos[1])
|
||||
print(f" Path: {path1[:5]}...{path1[-3:] if len(path1) > 5 else ''}")
|
||||
print(f" Length: {len(path1)}")
|
||||
|
||||
# Add obstacles from the demo
|
||||
obstacles = [
|
||||
# Vertical wall with gaps
|
||||
[(15, y) for y in range(3, 17) if y not in [8, 12]],
|
||||
# Horizontal walls
|
||||
[(x, 5) for x in range(10, 20)],
|
||||
[(x, 15) for x in range(10, 20)],
|
||||
# Maze-like structure
|
||||
[(x, 10) for x in range(20, 25)],
|
||||
[(25, y) for y in range(5, 15)],
|
||||
]
|
||||
|
||||
print("\nAdding obstacles...")
|
||||
wall_count = 0
|
||||
for obstacle_group in obstacles:
|
||||
for x, y in obstacle_group:
|
||||
grid.at(x, y).walkable = False
|
||||
wall_count += 1
|
||||
if wall_count <= 5:
|
||||
print(f" Wall at ({x}, {y})")
|
||||
|
||||
print(f" Total walls added: {wall_count}")
|
||||
|
||||
# Check specific cells
|
||||
print(f"\nChecking key positions:")
|
||||
print(f" Start ({start_pos[0]}, {start_pos[1]}): walkable={grid.at(start_pos[0], start_pos[1]).walkable}")
|
||||
print(f" End ({end_pos[0]}, {end_pos[1]}): walkable={grid.at(end_pos[0], end_pos[1]).walkable}")
|
||||
|
||||
# Check if path is blocked
|
||||
print(f"\nChecking horizontal line at y=10:")
|
||||
blocked_x = []
|
||||
for x in range(30):
|
||||
if not grid.at(x, 10).walkable:
|
||||
blocked_x.append(x)
|
||||
|
||||
print(f" Blocked x positions: {blocked_x}")
|
||||
|
||||
# Test path with obstacles
|
||||
print("\nTest 2: Path with obstacles")
|
||||
path2 = grid.compute_astar_path(start_pos[0], start_pos[1], end_pos[0], end_pos[1])
|
||||
print(f" Path: {path2}")
|
||||
print(f" Length: {len(path2)}")
|
||||
|
||||
# Check if there's any path at all
|
||||
if not path2:
|
||||
print("\n No path found! Checking why...")
|
||||
|
||||
# Check if we can reach the vertical wall gap
|
||||
print("\n Testing path to wall gap at (15, 8):")
|
||||
path_to_gap = grid.compute_astar_path(start_pos[0], start_pos[1], 15, 8)
|
||||
print(f" Path to gap: {path_to_gap}")
|
||||
|
||||
# Check from gap to end
|
||||
print("\n Testing path from gap (15, 8) to end:")
|
||||
path_from_gap = grid.compute_astar_path(15, 8, end_pos[0], end_pos[1])
|
||||
print(f" Path from gap: {path_from_gap}")
|
||||
|
||||
# Check walls more carefully
|
||||
print("\nDetailed wall analysis:")
|
||||
print(" Walls at x=25 (blocking end?):")
|
||||
for y in range(5, 15):
|
||||
print(f" ({25}, {y}): walkable={grid.at(25, y).walkable}")
|
||||
|
||||
def timer_cb(dt):
|
||||
sys.exit(0)
|
||||
|
||||
ui = mcrfpy.sceneUI("debug")
|
||||
ui.append(grid)
|
||||
mcrfpy.setScene("debug")
|
||||
mcrfpy.setTimer("exit", timer_cb, 100)
|
||||
137
tests/demos/dijkstra_demo_working.py
Normal file
137
tests/demos/dijkstra_demo_working.py
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Working Dijkstra Demo with Clear Visual Feedback
|
||||
================================================
|
||||
|
||||
This demo shows pathfinding with high-contrast colors.
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# High contrast colors
|
||||
WALL_COLOR = mcrfpy.Color(40, 20, 20) # Very dark red/brown for walls
|
||||
FLOOR_COLOR = mcrfpy.Color(60, 60, 80) # Dark blue-gray for floors
|
||||
PATH_COLOR = mcrfpy.Color(0, 255, 0) # Pure green for paths
|
||||
START_COLOR = mcrfpy.Color(255, 0, 0) # Red for start
|
||||
END_COLOR = mcrfpy.Color(0, 0, 255) # Blue for end
|
||||
|
||||
print("Dijkstra Demo - High Contrast")
|
||||
print("==============================")
|
||||
|
||||
# Create scene
|
||||
mcrfpy.createScene("dijkstra_demo")
|
||||
|
||||
# Create grid with exact layout from user
|
||||
grid = mcrfpy.Grid(grid_x=14, grid_y=10)
|
||||
grid.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
|
||||
# Map layout
|
||||
map_layout = [
|
||||
"..............", # Row 0
|
||||
"..W.....WWWW..", # Row 1
|
||||
"..W.W...W.EW..", # Row 2
|
||||
"..W.....W..W..", # Row 3
|
||||
"..W...E.WWWW..", # Row 4
|
||||
"E.W...........", # Row 5
|
||||
"..W...........", # Row 6
|
||||
"..W...........", # Row 7
|
||||
"..W.WWW.......", # Row 8
|
||||
"..............", # Row 9
|
||||
]
|
||||
|
||||
# Create the map
|
||||
entity_positions = []
|
||||
for y, row in enumerate(map_layout):
|
||||
for x, char in enumerate(row):
|
||||
cell = grid.at(x, y)
|
||||
|
||||
if char == 'W':
|
||||
cell.walkable = False
|
||||
cell.color = WALL_COLOR
|
||||
else:
|
||||
cell.walkable = True
|
||||
cell.color = FLOOR_COLOR
|
||||
|
||||
if char == 'E':
|
||||
entity_positions.append((x, y))
|
||||
|
||||
print(f"Map created: {grid.grid_x}x{grid.grid_y}")
|
||||
print(f"Entity positions: {entity_positions}")
|
||||
|
||||
# Create entities
|
||||
entities = []
|
||||
for i, (x, y) in enumerate(entity_positions):
|
||||
entity = mcrfpy.Entity(x, y)
|
||||
entity.sprite_index = 49 + i # '1', '2', '3'
|
||||
grid.entities.append(entity)
|
||||
entities.append(entity)
|
||||
print(f"Entity {i+1} at ({x}, {y})")
|
||||
|
||||
# Highlight a path immediately
|
||||
if len(entities) >= 2:
|
||||
e1, e2 = entities[0], entities[1]
|
||||
print(f"\nCalculating path from Entity 1 ({e1.x}, {e1.y}) to Entity 2 ({e2.x}, {e2.y})...")
|
||||
|
||||
path = e1.path_to(int(e2.x), int(e2.y))
|
||||
print(f"Path found: {path}")
|
||||
print(f"Path length: {len(path)} steps")
|
||||
|
||||
if path:
|
||||
print("\nHighlighting path in bright green...")
|
||||
# Color start and end specially
|
||||
grid.at(int(e1.x), int(e1.y)).color = START_COLOR
|
||||
grid.at(int(e2.x), int(e2.y)).color = END_COLOR
|
||||
|
||||
# Color the path
|
||||
for i, (x, y) in enumerate(path):
|
||||
if i > 0 and i < len(path) - 1: # Skip start and end
|
||||
grid.at(x, y).color = PATH_COLOR
|
||||
print(f" Colored ({x}, {y}) green")
|
||||
|
||||
# Keypress handler
|
||||
def handle_keypress(scene_name, keycode):
|
||||
if keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC
|
||||
print("\nExiting...")
|
||||
sys.exit(0)
|
||||
elif keycode == 32: # Space
|
||||
print("\nRefreshing path colors...")
|
||||
# Re-color the path to ensure it's visible
|
||||
if len(entities) >= 2 and path:
|
||||
for x, y in path[1:-1]:
|
||||
grid.at(x, y).color = PATH_COLOR
|
||||
|
||||
# Set up UI
|
||||
ui = mcrfpy.sceneUI("dijkstra_demo")
|
||||
ui.append(grid)
|
||||
|
||||
# Scale grid
|
||||
grid.size = (560, 400) # 14*40, 10*40
|
||||
grid.position = (120, 100)
|
||||
|
||||
# Add title
|
||||
title = mcrfpy.Caption("Dijkstra Pathfinding - High Contrast", 200, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Add legend
|
||||
legend1 = mcrfpy.Caption("Red=Start, Blue=End, Green=Path", 120, 520)
|
||||
legend1.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(legend1)
|
||||
|
||||
legend2 = mcrfpy.Caption("Press Q to quit, SPACE to refresh", 120, 540)
|
||||
legend2.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(legend2)
|
||||
|
||||
# Entity info
|
||||
info = mcrfpy.Caption(f"Path: Entity 1 to 2 = {len(path) if 'path' in locals() else 0} steps", 120, 60)
|
||||
info.fill_color = mcrfpy.Color(255, 255, 100)
|
||||
ui.append(info)
|
||||
|
||||
# Set up input
|
||||
mcrfpy.keypressScene(handle_keypress)
|
||||
mcrfpy.setScene("dijkstra_demo")
|
||||
|
||||
print("\nDemo ready! The path should be clearly visible in bright green.")
|
||||
print("Red = Start, Blue = End, Green = Path")
|
||||
print("Press SPACE to refresh colors if needed.")
|
||||
306
tests/demos/exhaustive_api_demo_fixed.py
Normal file
306
tests/demos/exhaustive_api_demo_fixed.py
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
McRogueFace Exhaustive API Demo (Fixed)
|
||||
=======================================
|
||||
|
||||
Fixed version that properly exits after tests complete.
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# Test configuration
|
||||
VERBOSE = True # Print detailed information about each test
|
||||
|
||||
def print_section(title):
|
||||
"""Print a section header"""
|
||||
print("\n" + "="*60)
|
||||
print(f" {title}")
|
||||
print("="*60)
|
||||
|
||||
def print_test(test_name, success=True):
|
||||
"""Print test result"""
|
||||
status = "✓ PASS" if success else "✗ FAIL"
|
||||
print(f" {status} - {test_name}")
|
||||
|
||||
def test_color_api():
|
||||
"""Test all Color constructors and methods"""
|
||||
print_section("COLOR API TESTS")
|
||||
|
||||
# Constructor variants
|
||||
print("\n Constructors:")
|
||||
|
||||
# Empty constructor (defaults to white)
|
||||
c1 = mcrfpy.Color()
|
||||
print_test(f"Color() = ({c1.r}, {c1.g}, {c1.b}, {c1.a})")
|
||||
|
||||
# Single value (grayscale)
|
||||
c2 = mcrfpy.Color(128)
|
||||
print_test(f"Color(128) = ({c2.r}, {c2.g}, {c2.b}, {c2.a})")
|
||||
|
||||
# RGB only (alpha defaults to 255)
|
||||
c3 = mcrfpy.Color(255, 128, 0)
|
||||
print_test(f"Color(255, 128, 0) = ({c3.r}, {c3.g}, {c3.b}, {c3.a})")
|
||||
|
||||
# Full RGBA
|
||||
c4 = mcrfpy.Color(100, 150, 200, 128)
|
||||
print_test(f"Color(100, 150, 200, 128) = ({c4.r}, {c4.g}, {c4.b}, {c4.a})")
|
||||
|
||||
# Property access
|
||||
print("\n Properties:")
|
||||
c = mcrfpy.Color(10, 20, 30, 40)
|
||||
print_test(f"Initial: r={c.r}, g={c.g}, b={c.b}, a={c.a}")
|
||||
|
||||
c.r = 200
|
||||
c.g = 150
|
||||
c.b = 100
|
||||
c.a = 255
|
||||
print_test(f"After modification: r={c.r}, g={c.g}, b={c.b}, a={c.a}")
|
||||
|
||||
return True
|
||||
|
||||
def test_frame_api():
|
||||
"""Test all Frame constructors and methods"""
|
||||
print_section("FRAME API TESTS")
|
||||
|
||||
# Create a test scene
|
||||
mcrfpy.createScene("api_test")
|
||||
mcrfpy.setScene("api_test")
|
||||
ui = mcrfpy.sceneUI("api_test")
|
||||
|
||||
# Constructor variants
|
||||
print("\n Constructors:")
|
||||
|
||||
# Empty constructor
|
||||
f1 = mcrfpy.Frame()
|
||||
print_test(f"Frame() - pos=({f1.x}, {f1.y}), size=({f1.w}, {f1.h})")
|
||||
ui.append(f1)
|
||||
|
||||
# Position only
|
||||
f2 = mcrfpy.Frame(100, 50)
|
||||
print_test(f"Frame(100, 50) - pos=({f2.x}, {f2.y}), size=({f2.w}, {f2.h})")
|
||||
ui.append(f2)
|
||||
|
||||
# Position and size
|
||||
f3 = mcrfpy.Frame(200, 100, 150, 75)
|
||||
print_test(f"Frame(200, 100, 150, 75) - pos=({f3.x}, {f3.y}), size=({f3.w}, {f3.h})")
|
||||
ui.append(f3)
|
||||
|
||||
# Full constructor
|
||||
f4 = mcrfpy.Frame(300, 200, 200, 100,
|
||||
fill_color=mcrfpy.Color(100, 100, 200),
|
||||
outline_color=mcrfpy.Color(255, 255, 0),
|
||||
outline=3)
|
||||
print_test("Frame with all parameters")
|
||||
ui.append(f4)
|
||||
|
||||
# Properties
|
||||
print("\n Properties:")
|
||||
|
||||
# Position and size
|
||||
f = mcrfpy.Frame(10, 20, 30, 40)
|
||||
print_test(f"Initial: x={f.x}, y={f.y}, w={f.w}, h={f.h}")
|
||||
|
||||
f.x = 50
|
||||
f.y = 60
|
||||
f.w = 70
|
||||
f.h = 80
|
||||
print_test(f"Modified: x={f.x}, y={f.y}, w={f.w}, h={f.h}")
|
||||
|
||||
# Colors
|
||||
f.fill_color = mcrfpy.Color(255, 0, 0, 128)
|
||||
f.outline_color = mcrfpy.Color(0, 255, 0)
|
||||
f.outline = 5.0
|
||||
print_test(f"Colors set, outline={f.outline}")
|
||||
|
||||
# Visibility and opacity
|
||||
f.visible = False
|
||||
f.opacity = 0.5
|
||||
print_test(f"visible={f.visible}, opacity={f.opacity}")
|
||||
f.visible = True # Reset
|
||||
|
||||
# Z-index
|
||||
f.z_index = 10
|
||||
print_test(f"z_index={f.z_index}")
|
||||
|
||||
# Children collection
|
||||
child1 = mcrfpy.Frame(5, 5, 20, 20)
|
||||
child2 = mcrfpy.Frame(30, 5, 20, 20)
|
||||
f.children.append(child1)
|
||||
f.children.append(child2)
|
||||
print_test(f"children.count = {len(f.children)}")
|
||||
|
||||
return True
|
||||
|
||||
def test_caption_api():
|
||||
"""Test all Caption constructors and methods"""
|
||||
print_section("CAPTION API TESTS")
|
||||
|
||||
ui = mcrfpy.sceneUI("api_test")
|
||||
|
||||
# Constructor variants
|
||||
print("\n Constructors:")
|
||||
|
||||
# Empty constructor
|
||||
c1 = mcrfpy.Caption()
|
||||
print_test(f"Caption() - text='{c1.text}', pos=({c1.x}, {c1.y})")
|
||||
ui.append(c1)
|
||||
|
||||
# Text only
|
||||
c2 = mcrfpy.Caption("Hello World")
|
||||
print_test(f"Caption('Hello World') - pos=({c2.x}, {c2.y})")
|
||||
ui.append(c2)
|
||||
|
||||
# Text and position
|
||||
c3 = mcrfpy.Caption("Positioned Text", 100, 50)
|
||||
print_test(f"Caption('Positioned Text', 100, 50)")
|
||||
ui.append(c3)
|
||||
|
||||
# Full constructor
|
||||
c5 = mcrfpy.Caption("Styled Text", 300, 150,
|
||||
fill_color=mcrfpy.Color(255, 255, 0),
|
||||
outline_color=mcrfpy.Color(255, 0, 0),
|
||||
outline=2)
|
||||
print_test("Caption with all style parameters")
|
||||
ui.append(c5)
|
||||
|
||||
# Properties
|
||||
print("\n Properties:")
|
||||
|
||||
c = mcrfpy.Caption("Test Caption", 10, 20)
|
||||
|
||||
# Text
|
||||
c.text = "Modified Text"
|
||||
print_test(f"text = '{c.text}'")
|
||||
|
||||
# Position
|
||||
c.x = 50
|
||||
c.y = 60
|
||||
print_test(f"position = ({c.x}, {c.y})")
|
||||
|
||||
# Colors and style
|
||||
c.fill_color = mcrfpy.Color(0, 255, 255)
|
||||
c.outline_color = mcrfpy.Color(255, 0, 255)
|
||||
c.outline = 3.0
|
||||
print_test("Colors and outline set")
|
||||
|
||||
# Size (read-only, computed from text)
|
||||
print_test(f"size (computed) = ({c.w}, {c.h})")
|
||||
|
||||
return True
|
||||
|
||||
def test_animation_api():
|
||||
"""Test Animation class API"""
|
||||
print_section("ANIMATION API TESTS")
|
||||
|
||||
ui = mcrfpy.sceneUI("api_test")
|
||||
|
||||
print("\n Animation Constructors:")
|
||||
|
||||
# Basic animation
|
||||
anim1 = mcrfpy.Animation("x", 100.0, 2.0)
|
||||
print_test("Animation('x', 100.0, 2.0)")
|
||||
|
||||
# With easing
|
||||
anim2 = mcrfpy.Animation("y", 200.0, 3.0, "easeInOut")
|
||||
print_test("Animation with easing='easeInOut'")
|
||||
|
||||
# Delta mode
|
||||
anim3 = mcrfpy.Animation("w", 50.0, 1.5, "linear", delta=True)
|
||||
print_test("Animation with delta=True")
|
||||
|
||||
# Color animation (as tuple)
|
||||
anim4 = mcrfpy.Animation("fill_color", (255, 0, 0, 255), 2.0)
|
||||
print_test("Animation with Color tuple target")
|
||||
|
||||
# Vector animation
|
||||
anim5 = mcrfpy.Animation("position", (10.0, 20.0), 2.5, "easeOutBounce")
|
||||
print_test("Animation with position tuple")
|
||||
|
||||
# Sprite sequence
|
||||
anim6 = mcrfpy.Animation("sprite_index", [0, 1, 2, 3, 2, 1], 2.0)
|
||||
print_test("Animation with sprite sequence")
|
||||
|
||||
# Properties
|
||||
print("\n Animation Properties:")
|
||||
|
||||
# Check properties
|
||||
print_test(f"property = '{anim1.property}'")
|
||||
print_test(f"duration = {anim1.duration}")
|
||||
print_test(f"elapsed = {anim1.elapsed}")
|
||||
print_test(f"is_complete = {anim1.is_complete}")
|
||||
print_test(f"is_delta = {anim3.is_delta}")
|
||||
|
||||
# Methods
|
||||
print("\n Animation Methods:")
|
||||
|
||||
# Create test frame
|
||||
frame = mcrfpy.Frame(50, 50, 100, 100)
|
||||
frame.fill_color = mcrfpy.Color(100, 100, 100)
|
||||
ui.append(frame)
|
||||
|
||||
# Start animation
|
||||
anim1.start(frame)
|
||||
print_test("start() called on frame")
|
||||
|
||||
# Test some easing functions
|
||||
print("\n Sample Easing Functions:")
|
||||
easings = ["linear", "easeIn", "easeOut", "easeInOut", "easeInBounce", "easeOutElastic"]
|
||||
|
||||
for easing in easings:
|
||||
try:
|
||||
test_anim = mcrfpy.Animation("x", 100.0, 1.0, easing)
|
||||
print_test(f"Easing '{easing}' ✓")
|
||||
except:
|
||||
print_test(f"Easing '{easing}' failed", False)
|
||||
|
||||
return True
|
||||
|
||||
def run_all_tests():
|
||||
"""Run all API tests"""
|
||||
print("\n" + "="*60)
|
||||
print(" McRogueFace Exhaustive API Test Suite (Fixed)")
|
||||
print(" Testing constructors and methods...")
|
||||
print("="*60)
|
||||
|
||||
# Run each test category
|
||||
test_functions = [
|
||||
test_color_api,
|
||||
test_frame_api,
|
||||
test_caption_api,
|
||||
test_animation_api
|
||||
]
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
|
||||
for test_func in test_functions:
|
||||
try:
|
||||
if test_func():
|
||||
passed += 1
|
||||
else:
|
||||
failed += 1
|
||||
except Exception as e:
|
||||
print(f"\n ERROR in {test_func.__name__}: {e}")
|
||||
failed += 1
|
||||
|
||||
# Summary
|
||||
print("\n" + "="*60)
|
||||
print(f" TEST SUMMARY: {passed} passed, {failed} failed")
|
||||
print("="*60)
|
||||
|
||||
print("\n Visual elements are displayed in the 'api_test' scene.")
|
||||
print(" The test is complete.")
|
||||
|
||||
# Exit after a short delay to allow output to be seen
|
||||
def exit_test(runtime):
|
||||
print("\nExiting API test suite...")
|
||||
sys.exit(0)
|
||||
|
||||
mcrfpy.setTimer("exit", exit_test, 2000)
|
||||
|
||||
# Run the tests immediately
|
||||
print("Starting McRogueFace Exhaustive API Demo (Fixed)...")
|
||||
print("This will test constructors and methods.")
|
||||
|
||||
run_all_tests()
|
||||
391
tests/demos/path_vision_sizzle_reel.py
Normal file
391
tests/demos/path_vision_sizzle_reel.py
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Path & Vision Sizzle Reel
|
||||
=========================
|
||||
|
||||
A choreographed demo showing:
|
||||
- Smooth entity movement along paths
|
||||
- Camera following with grid center animation
|
||||
- Field of view updates as entities move
|
||||
- Dramatic perspective transitions with zoom effects
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# Colors
|
||||
WALL_COLOR = mcrfpy.Color(40, 30, 30)
|
||||
FLOOR_COLOR = mcrfpy.Color(80, 80, 100)
|
||||
PATH_COLOR = mcrfpy.Color(120, 120, 180)
|
||||
DARK_FLOOR = mcrfpy.Color(40, 40, 50)
|
||||
|
||||
# Global state
|
||||
grid = None
|
||||
player = None
|
||||
enemy = None
|
||||
sequence_step = 0
|
||||
player_path = []
|
||||
enemy_path = []
|
||||
player_path_index = 0
|
||||
enemy_path_index = 0
|
||||
|
||||
def create_scene():
|
||||
"""Create the demo environment"""
|
||||
global grid, player, enemy
|
||||
|
||||
mcrfpy.createScene("path_vision_demo")
|
||||
|
||||
# Create larger grid for more dramatic movement
|
||||
grid = mcrfpy.Grid(grid_x=40, grid_y=25)
|
||||
grid.fill_color = mcrfpy.Color(20, 20, 30)
|
||||
|
||||
# Map layout - interconnected rooms with corridors
|
||||
map_layout = [
|
||||
"########################################", # 0
|
||||
"#......##########......################", # 1
|
||||
"#......##########......################", # 2
|
||||
"#......##########......################", # 3
|
||||
"#......#.........#.....################", # 4
|
||||
"#......#.........#.....################", # 5
|
||||
"####.###.........####.#################", # 6
|
||||
"####.....................##############", # 7
|
||||
"####.....................##############", # 8
|
||||
"####.###.........####.#################", # 9
|
||||
"#......#.........#.....################", # 10
|
||||
"#......#.........#.....################", # 11
|
||||
"#......#.........#.....################", # 12
|
||||
"#......###.....###.....################", # 13
|
||||
"#......###.....###.....################", # 14
|
||||
"#......###.....###.....#########......#", # 15
|
||||
"#......###.....###.....#########......#", # 16
|
||||
"#......###.....###.....#########......#", # 17
|
||||
"#####.############.#############......#", # 18
|
||||
"#####...........................#.....#", # 19
|
||||
"#####...........................#.....#", # 20
|
||||
"#####.############.#############......#", # 21
|
||||
"#......###########.##########.........#", # 22
|
||||
"#......###########.##########.........#", # 23
|
||||
"########################################", # 24
|
||||
]
|
||||
|
||||
# Build the map
|
||||
for y, row in enumerate(map_layout):
|
||||
for x, char in enumerate(row):
|
||||
cell = grid.at(x, y)
|
||||
if char == '#':
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.color = WALL_COLOR
|
||||
else:
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
cell.color = FLOOR_COLOR
|
||||
|
||||
# Create player in top-left room
|
||||
player = mcrfpy.Entity(3, 3, grid=grid)
|
||||
player.sprite_index = 64 # @
|
||||
|
||||
# Create enemy in bottom-right area
|
||||
enemy = mcrfpy.Entity(35, 20, grid=grid)
|
||||
enemy.sprite_index = 69 # E
|
||||
|
||||
# Initial visibility
|
||||
player.update_visibility()
|
||||
enemy.update_visibility()
|
||||
|
||||
# Set initial perspective to player
|
||||
grid.perspective = 0
|
||||
|
||||
def setup_paths():
|
||||
"""Define the paths for entities"""
|
||||
global player_path, enemy_path
|
||||
|
||||
# Player path: Top-left room → corridor → middle room
|
||||
player_waypoints = [
|
||||
(3, 3), # Start
|
||||
(3, 8), # Move down
|
||||
(7, 8), # Enter corridor
|
||||
(16, 8), # Through corridor
|
||||
(16, 12), # Enter middle room
|
||||
(12, 12), # Move in room
|
||||
(12, 16), # Move down
|
||||
(16, 16), # Move right
|
||||
(16, 19), # Exit room
|
||||
(25, 19), # Move right
|
||||
(30, 19), # Continue
|
||||
(35, 19), # Near enemy start
|
||||
]
|
||||
|
||||
# Enemy path: Bottom-right → around → approach player area
|
||||
enemy_waypoints = [
|
||||
(35, 20), # Start
|
||||
(30, 20), # Move left
|
||||
(25, 20), # Continue
|
||||
(20, 20), # Continue
|
||||
(16, 20), # Corridor junction
|
||||
(16, 16), # Move up (might see player)
|
||||
(16, 12), # Continue up
|
||||
(16, 8), # Top corridor
|
||||
(10, 8), # Move left
|
||||
(7, 8), # Continue
|
||||
(3, 8), # Player's area
|
||||
(3, 12), # Move down
|
||||
]
|
||||
|
||||
# Calculate full paths using pathfinding
|
||||
player_path = []
|
||||
for i in range(len(player_waypoints) - 1):
|
||||
x1, y1 = player_waypoints[i]
|
||||
x2, y2 = player_waypoints[i + 1]
|
||||
|
||||
# Use grid's A* pathfinding
|
||||
segment = grid.compute_astar_path(x1, y1, x2, y2)
|
||||
if segment:
|
||||
# Add segment (avoiding duplicates)
|
||||
if not player_path or segment[0] != player_path[-1]:
|
||||
player_path.extend(segment)
|
||||
else:
|
||||
player_path.extend(segment[1:])
|
||||
|
||||
enemy_path = []
|
||||
for i in range(len(enemy_waypoints) - 1):
|
||||
x1, y1 = enemy_waypoints[i]
|
||||
x2, y2 = enemy_waypoints[i + 1]
|
||||
|
||||
segment = grid.compute_astar_path(x1, y1, x2, y2)
|
||||
if segment:
|
||||
if not enemy_path or segment[0] != enemy_path[-1]:
|
||||
enemy_path.extend(segment)
|
||||
else:
|
||||
enemy_path.extend(segment[1:])
|
||||
|
||||
print(f"Player path: {len(player_path)} steps")
|
||||
print(f"Enemy path: {len(enemy_path)} steps")
|
||||
|
||||
def setup_ui():
|
||||
"""Create UI elements"""
|
||||
ui = mcrfpy.sceneUI("path_vision_demo")
|
||||
ui.append(grid)
|
||||
|
||||
# Position and size grid
|
||||
grid.position = (50, 80)
|
||||
grid.size = (700, 500) # Adjust based on zoom
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption("Path & Vision Sizzle Reel", 300, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Status
|
||||
global status_text, perspective_text
|
||||
status_text = mcrfpy.Caption("Starting demo...", 50, 50)
|
||||
status_text.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(status_text)
|
||||
|
||||
perspective_text = mcrfpy.Caption("Perspective: Player", 550, 50)
|
||||
perspective_text.fill_color = mcrfpy.Color(100, 255, 100)
|
||||
ui.append(perspective_text)
|
||||
|
||||
# Controls
|
||||
controls = mcrfpy.Caption("Space: Pause/Resume | R: Restart | Q: Quit", 250, 600)
|
||||
controls.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(controls)
|
||||
|
||||
# Animation control
|
||||
paused = False
|
||||
move_timer = 0
|
||||
zoom_transition = False
|
||||
|
||||
def move_entity_smooth(entity, target_x, target_y, duration=0.3):
|
||||
"""Smoothly animate entity to position"""
|
||||
# Create position animation
|
||||
anim_x = mcrfpy.Animation("x", float(target_x), duration, "easeInOut")
|
||||
anim_y = mcrfpy.Animation("y", float(target_y), duration, "easeInOut")
|
||||
|
||||
anim_x.start(entity)
|
||||
anim_y.start(entity)
|
||||
|
||||
def update_camera_smooth(center_x, center_y, duration=0.3):
|
||||
"""Smoothly move camera center"""
|
||||
# Convert grid coords to pixel coords (assuming 16x16 tiles)
|
||||
pixel_x = center_x * 16
|
||||
pixel_y = center_y * 16
|
||||
|
||||
anim = mcrfpy.Animation("center", (pixel_x, pixel_y), duration, "easeOut")
|
||||
anim.start(grid)
|
||||
|
||||
def start_perspective_transition():
|
||||
"""Begin the dramatic perspective shift"""
|
||||
global zoom_transition, sequence_step
|
||||
zoom_transition = True
|
||||
sequence_step = 100 # Special sequence number
|
||||
|
||||
status_text.text = "Perspective shift: Zooming out..."
|
||||
|
||||
# Zoom out with elastic easing
|
||||
zoom_out = mcrfpy.Animation("zoom", 0.5, 2.0, "easeInExpo")
|
||||
zoom_out.start(grid)
|
||||
|
||||
# Schedule the perspective switch
|
||||
mcrfpy.setTimer("switch_perspective", switch_perspective, 2100)
|
||||
|
||||
def switch_perspective(dt):
|
||||
"""Switch perspective at the peak of zoom"""
|
||||
global sequence_step
|
||||
|
||||
# Switch to enemy perspective
|
||||
grid.perspective = 1
|
||||
perspective_text.text = "Perspective: Enemy"
|
||||
perspective_text.fill_color = mcrfpy.Color(255, 100, 100)
|
||||
|
||||
status_text.text = "Perspective shift: Following enemy..."
|
||||
|
||||
# Update camera to enemy position
|
||||
update_camera_smooth(enemy.x, enemy.y, 0.1)
|
||||
|
||||
# Zoom back in
|
||||
zoom_in = mcrfpy.Animation("zoom", 1.2, 2.0, "easeOutExpo")
|
||||
zoom_in.start(grid)
|
||||
|
||||
# Resume sequence
|
||||
mcrfpy.setTimer("resume_enemy", resume_enemy_sequence, 2100)
|
||||
|
||||
# Cancel this timer
|
||||
mcrfpy.delTimer("switch_perspective")
|
||||
|
||||
def resume_enemy_sequence(dt):
|
||||
"""Resume following enemy after perspective shift"""
|
||||
global sequence_step, zoom_transition
|
||||
zoom_transition = False
|
||||
sequence_step = 101 # Continue with enemy movement
|
||||
mcrfpy.delTimer("resume_enemy")
|
||||
|
||||
def sequence_tick(dt):
|
||||
"""Main sequence controller"""
|
||||
global sequence_step, player_path_index, enemy_path_index, move_timer
|
||||
|
||||
if paused or zoom_transition:
|
||||
return
|
||||
|
||||
move_timer += dt
|
||||
if move_timer < 400: # Move every 400ms
|
||||
return
|
||||
move_timer = 0
|
||||
|
||||
if sequence_step < 50:
|
||||
# Phase 1: Follow player movement
|
||||
if player_path_index < len(player_path):
|
||||
x, y = player_path[player_path_index]
|
||||
move_entity_smooth(player, x, y)
|
||||
player.update_visibility()
|
||||
|
||||
# Camera follows player
|
||||
if grid.perspective == 0:
|
||||
update_camera_smooth(player.x, player.y)
|
||||
|
||||
player_path_index += 1
|
||||
status_text.text = f"Player moving... Step {player_path_index}/{len(player_path)}"
|
||||
|
||||
# Start enemy movement after player has moved a bit
|
||||
if player_path_index == 10:
|
||||
sequence_step = 1 # Enable enemy movement
|
||||
else:
|
||||
# Player reached destination, start perspective transition
|
||||
start_perspective_transition()
|
||||
|
||||
if sequence_step >= 1 and sequence_step < 50:
|
||||
# Phase 2: Enemy movement (concurrent with player)
|
||||
if enemy_path_index < len(enemy_path):
|
||||
x, y = enemy_path[enemy_path_index]
|
||||
move_entity_smooth(enemy, x, y)
|
||||
enemy.update_visibility()
|
||||
|
||||
# Check if enemy is visible to player
|
||||
if grid.perspective == 0:
|
||||
enemy_cell_idx = int(enemy.y) * grid.grid_x + int(enemy.x)
|
||||
if enemy_cell_idx < len(player.gridstate) and player.gridstate[enemy_cell_idx].visible:
|
||||
status_text.text = "Enemy spotted!"
|
||||
|
||||
enemy_path_index += 1
|
||||
|
||||
elif sequence_step == 101:
|
||||
# Phase 3: Continue following enemy after perspective shift
|
||||
if enemy_path_index < len(enemy_path):
|
||||
x, y = enemy_path[enemy_path_index]
|
||||
move_entity_smooth(enemy, x, y)
|
||||
enemy.update_visibility()
|
||||
|
||||
# Camera follows enemy
|
||||
update_camera_smooth(enemy.x, enemy.y)
|
||||
|
||||
enemy_path_index += 1
|
||||
status_text.text = f"Following enemy... Step {enemy_path_index}/{len(enemy_path)}"
|
||||
else:
|
||||
status_text.text = "Demo complete! Press R to restart"
|
||||
sequence_step = 200 # Done
|
||||
|
||||
def handle_keys(key, state):
|
||||
"""Handle keyboard input"""
|
||||
global paused, sequence_step, player_path_index, enemy_path_index, move_timer
|
||||
key = key.lower()
|
||||
if state != "start":
|
||||
return
|
||||
|
||||
if key == "q":
|
||||
print("Exiting sizzle reel...")
|
||||
sys.exit(0)
|
||||
elif key == "space":
|
||||
paused = not paused
|
||||
status_text.text = "PAUSED" if paused else "Running..."
|
||||
elif key == "r":
|
||||
# Reset everything
|
||||
player.x, player.y = 3, 3
|
||||
enemy.x, enemy.y = 35, 20
|
||||
player.update_visibility()
|
||||
enemy.update_visibility()
|
||||
grid.perspective = 0
|
||||
perspective_text.text = "Perspective: Player"
|
||||
perspective_text.fill_color = mcrfpy.Color(100, 255, 100)
|
||||
sequence_step = 0
|
||||
player_path_index = 0
|
||||
enemy_path_index = 0
|
||||
move_timer = 0
|
||||
update_camera_smooth(player.x, player.y, 0.5)
|
||||
|
||||
# Reset zoom
|
||||
zoom_reset = mcrfpy.Animation("zoom", 1.2, 0.5, "easeOut")
|
||||
zoom_reset.start(grid)
|
||||
|
||||
status_text.text = "Demo restarted!"
|
||||
|
||||
# Initialize everything
|
||||
print("Path & Vision Sizzle Reel")
|
||||
print("=========================")
|
||||
print("Demonstrating:")
|
||||
print("- Smooth entity movement along calculated paths")
|
||||
print("- Camera following with animated grid centering")
|
||||
print("- Field of view updates as entities move")
|
||||
print("- Dramatic perspective transitions with zoom effects")
|
||||
print()
|
||||
|
||||
create_scene()
|
||||
setup_paths()
|
||||
setup_ui()
|
||||
|
||||
# Set scene and input
|
||||
mcrfpy.setScene("path_vision_demo")
|
||||
mcrfpy.keypressScene(handle_keys)
|
||||
|
||||
# Initial camera setup
|
||||
grid.zoom = 1.2
|
||||
update_camera_smooth(player.x, player.y, 0.1)
|
||||
|
||||
# Start the sequence
|
||||
mcrfpy.setTimer("sequence", sequence_tick, 50) # Tick every 50ms
|
||||
|
||||
print("Demo started!")
|
||||
print("- Player (@) will navigate through rooms")
|
||||
print("- Enemy (E) will move on a different path")
|
||||
print("- Watch for the dramatic perspective shift!")
|
||||
print()
|
||||
print("Controls: Space=Pause, R=Restart, Q=Quit")
|
||||
377
tests/demos/pathfinding_showcase.py
Normal file
377
tests/demos/pathfinding_showcase.py
Normal file
|
|
@ -0,0 +1,377 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Pathfinding Showcase Demo
|
||||
=========================
|
||||
|
||||
Demonstrates various pathfinding scenarios with multiple entities.
|
||||
|
||||
Features:
|
||||
- Multiple entities pathfinding simultaneously
|
||||
- Chase mode: entities pursue targets
|
||||
- Flee mode: entities avoid threats
|
||||
- Patrol mode: entities follow waypoints
|
||||
- Visual debugging: show Dijkstra distance field
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
import random
|
||||
|
||||
# Colors
|
||||
WALL_COLOR = mcrfpy.Color(40, 40, 40)
|
||||
FLOOR_COLOR = mcrfpy.Color(220, 220, 240)
|
||||
PATH_COLOR = mcrfpy.Color(180, 250, 180)
|
||||
THREAT_COLOR = mcrfpy.Color(255, 100, 100)
|
||||
GOAL_COLOR = mcrfpy.Color(100, 255, 100)
|
||||
DIJKSTRA_COLORS = [
|
||||
mcrfpy.Color(50, 50, 100), # Far
|
||||
mcrfpy.Color(70, 70, 150),
|
||||
mcrfpy.Color(90, 90, 200),
|
||||
mcrfpy.Color(110, 110, 250),
|
||||
mcrfpy.Color(150, 150, 255),
|
||||
mcrfpy.Color(200, 200, 255), # Near
|
||||
]
|
||||
|
||||
# Entity types
|
||||
PLAYER = 64 # @
|
||||
ENEMY = 69 # E
|
||||
TREASURE = 36 # $
|
||||
PATROL = 80 # P
|
||||
|
||||
# Global state
|
||||
grid = None
|
||||
player = None
|
||||
enemies = []
|
||||
treasures = []
|
||||
patrol_entities = []
|
||||
mode = "CHASE"
|
||||
show_dijkstra = False
|
||||
animation_speed = 3.0
|
||||
|
||||
# Track waypoints separately since Entity doesn't have custom attributes
|
||||
entity_waypoints = {} # entity -> [(x, y), ...]
|
||||
entity_waypoint_indices = {} # entity -> current index
|
||||
|
||||
def create_dungeon():
|
||||
"""Create a dungeon-like map"""
|
||||
global grid
|
||||
|
||||
mcrfpy.createScene("pathfinding_showcase")
|
||||
|
||||
# Create larger grid for showcase
|
||||
grid = mcrfpy.Grid(grid_x=30, grid_y=20)
|
||||
grid.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
|
||||
# Initialize all as floor
|
||||
for y in range(20):
|
||||
for x in range(30):
|
||||
grid.at(x, y).walkable = True
|
||||
grid.at(x, y).transparent = True
|
||||
grid.at(x, y).color = FLOOR_COLOR
|
||||
|
||||
# Create rooms and corridors
|
||||
rooms = [
|
||||
(2, 2, 8, 6), # Top-left room
|
||||
(20, 2, 8, 6), # Top-right room
|
||||
(11, 8, 8, 6), # Center room
|
||||
(2, 14, 8, 5), # Bottom-left room
|
||||
(20, 14, 8, 5), # Bottom-right room
|
||||
]
|
||||
|
||||
# Create room walls
|
||||
for rx, ry, rw, rh in rooms:
|
||||
# Top and bottom walls
|
||||
for x in range(rx, rx + rw):
|
||||
if 0 <= x < 30:
|
||||
grid.at(x, ry).walkable = False
|
||||
grid.at(x, ry).color = WALL_COLOR
|
||||
grid.at(x, ry + rh - 1).walkable = False
|
||||
grid.at(x, ry + rh - 1).color = WALL_COLOR
|
||||
|
||||
# Left and right walls
|
||||
for y in range(ry, ry + rh):
|
||||
if 0 <= y < 20:
|
||||
grid.at(rx, y).walkable = False
|
||||
grid.at(rx, y).color = WALL_COLOR
|
||||
grid.at(rx + rw - 1, y).walkable = False
|
||||
grid.at(rx + rw - 1, y).color = WALL_COLOR
|
||||
|
||||
# Create doorways
|
||||
doorways = [
|
||||
(6, 2), (24, 2), # Top room doors
|
||||
(6, 7), (24, 7), # Top room doors bottom
|
||||
(15, 8), (15, 13), # Center room doors
|
||||
(6, 14), (24, 14), # Bottom room doors
|
||||
(11, 11), (18, 11), # Center room side doors
|
||||
]
|
||||
|
||||
for x, y in doorways:
|
||||
if 0 <= x < 30 and 0 <= y < 20:
|
||||
grid.at(x, y).walkable = True
|
||||
grid.at(x, y).color = FLOOR_COLOR
|
||||
|
||||
# Add some corridors
|
||||
# Horizontal corridors
|
||||
for x in range(10, 20):
|
||||
grid.at(x, 5).walkable = True
|
||||
grid.at(x, 5).color = FLOOR_COLOR
|
||||
grid.at(x, 16).walkable = True
|
||||
grid.at(x, 16).color = FLOOR_COLOR
|
||||
|
||||
# Vertical corridors
|
||||
for y in range(5, 17):
|
||||
grid.at(10, y).walkable = True
|
||||
grid.at(10, y).color = FLOOR_COLOR
|
||||
grid.at(19, y).walkable = True
|
||||
grid.at(19, y).color = FLOOR_COLOR
|
||||
|
||||
def spawn_entities():
|
||||
"""Spawn various entity types"""
|
||||
global player, enemies, treasures, patrol_entities
|
||||
|
||||
# Clear existing entities
|
||||
#grid.entities.clear()
|
||||
enemies = []
|
||||
treasures = []
|
||||
patrol_entities = []
|
||||
|
||||
# Spawn player in center room
|
||||
player = mcrfpy.Entity((15, 11), mcrfpy.default_texture, PLAYER)
|
||||
grid.entities.append(player)
|
||||
|
||||
# Spawn enemies in corners
|
||||
enemy_positions = [(4, 4), (24, 4), (4, 16), (24, 16)]
|
||||
for x, y in enemy_positions:
|
||||
enemy = mcrfpy.Entity((x, y), mcrfpy.default_texture, ENEMY)
|
||||
grid.entities.append(enemy)
|
||||
enemies.append(enemy)
|
||||
|
||||
# Spawn treasures
|
||||
treasure_positions = [(6, 5), (24, 5), (15, 10)]
|
||||
for x, y in treasure_positions:
|
||||
treasure = mcrfpy.Entity((x, y), mcrfpy.default_texture, TREASURE)
|
||||
grid.entities.append(treasure)
|
||||
treasures.append(treasure)
|
||||
|
||||
# Spawn patrol entities
|
||||
patrol = mcrfpy.Entity((10, 10), mcrfpy.default_texture, PATROL)
|
||||
# Store waypoints separately since Entity doesn't support custom attributes
|
||||
entity_waypoints[patrol] = [(10, 10), (19, 10), (19, 16), (10, 16)] # Square patrol
|
||||
entity_waypoint_indices[patrol] = 0
|
||||
grid.entities.append(patrol)
|
||||
patrol_entities.append(patrol)
|
||||
|
||||
def visualize_dijkstra(target_x, target_y):
|
||||
"""Visualize Dijkstra distance field"""
|
||||
if not show_dijkstra:
|
||||
return
|
||||
|
||||
# Compute Dijkstra from target
|
||||
grid.compute_dijkstra(target_x, target_y)
|
||||
|
||||
# Color tiles based on distance
|
||||
max_dist = 30.0
|
||||
for y in range(20):
|
||||
for x in range(30):
|
||||
if grid.at(x, y).walkable:
|
||||
dist = grid.get_dijkstra_distance(x, y)
|
||||
if dist is not None and dist < max_dist:
|
||||
# Map distance to color index
|
||||
color_idx = int((dist / max_dist) * len(DIJKSTRA_COLORS))
|
||||
color_idx = min(color_idx, len(DIJKSTRA_COLORS) - 1)
|
||||
grid.at(x, y).color = DIJKSTRA_COLORS[color_idx]
|
||||
|
||||
def move_enemies(dt):
|
||||
"""Move enemies based on current mode"""
|
||||
if mode == "CHASE":
|
||||
# Enemies chase player
|
||||
for enemy in enemies:
|
||||
path = enemy.path_to(int(player.x), int(player.y))
|
||||
if path and len(path) > 1: # Don't move onto player
|
||||
# Move towards player
|
||||
next_x, next_y = path[1]
|
||||
# Smooth movement
|
||||
dx = next_x - enemy.x
|
||||
dy = next_y - enemy.y
|
||||
enemy.x += dx * dt * animation_speed
|
||||
enemy.y += dy * dt * animation_speed
|
||||
|
||||
elif mode == "FLEE":
|
||||
# Enemies flee from player
|
||||
for enemy in enemies:
|
||||
# Compute opposite direction
|
||||
dx = enemy.x - player.x
|
||||
dy = enemy.y - player.y
|
||||
|
||||
# Find safe spot in that direction
|
||||
target_x = int(enemy.x + dx * 2)
|
||||
target_y = int(enemy.y + dy * 2)
|
||||
|
||||
# Clamp to grid
|
||||
target_x = max(0, min(29, target_x))
|
||||
target_y = max(0, min(19, target_y))
|
||||
|
||||
path = enemy.path_to(target_x, target_y)
|
||||
if path and len(path) > 0:
|
||||
next_x, next_y = path[0]
|
||||
# Move away from player
|
||||
dx = next_x - enemy.x
|
||||
dy = next_y - enemy.y
|
||||
enemy.x += dx * dt * animation_speed
|
||||
enemy.y += dy * dt * animation_speed
|
||||
|
||||
def move_patrols(dt):
|
||||
"""Move patrol entities along waypoints"""
|
||||
for patrol in patrol_entities:
|
||||
if patrol not in entity_waypoints:
|
||||
continue
|
||||
|
||||
# Get current waypoint
|
||||
waypoints = entity_waypoints[patrol]
|
||||
waypoint_index = entity_waypoint_indices[patrol]
|
||||
target_x, target_y = waypoints[waypoint_index]
|
||||
|
||||
# Check if reached waypoint
|
||||
dist = abs(patrol.x - target_x) + abs(patrol.y - target_y)
|
||||
if dist < 0.5:
|
||||
# Move to next waypoint
|
||||
entity_waypoint_indices[patrol] = (waypoint_index + 1) % len(waypoints)
|
||||
waypoint_index = entity_waypoint_indices[patrol]
|
||||
target_x, target_y = waypoints[waypoint_index]
|
||||
|
||||
# Path to waypoint
|
||||
path = patrol.path_to(target_x, target_y)
|
||||
if path and len(path) > 0:
|
||||
next_x, next_y = path[0]
|
||||
dx = next_x - patrol.x
|
||||
dy = next_y - patrol.y
|
||||
patrol.x += dx * dt * animation_speed * 0.5 # Slower patrol speed
|
||||
patrol.y += dy * dt * animation_speed * 0.5
|
||||
|
||||
def update_entities(dt):
|
||||
"""Update all entity movements"""
|
||||
move_enemies(dt / 1000.0) # Convert to seconds
|
||||
move_patrols(dt / 1000.0)
|
||||
|
||||
# Update Dijkstra visualization
|
||||
if show_dijkstra and player:
|
||||
visualize_dijkstra(int(player.x), int(player.y))
|
||||
|
||||
def handle_keypress(scene_name, keycode):
|
||||
"""Handle keyboard input"""
|
||||
global mode, show_dijkstra, player
|
||||
|
||||
# Mode switching
|
||||
if keycode == 49: # '1'
|
||||
mode = "CHASE"
|
||||
mode_text.text = "Mode: CHASE - Enemies pursue player"
|
||||
clear_colors()
|
||||
elif keycode == 50: # '2'
|
||||
mode = "FLEE"
|
||||
mode_text.text = "Mode: FLEE - Enemies avoid player"
|
||||
clear_colors()
|
||||
elif keycode == 51: # '3'
|
||||
mode = "PATROL"
|
||||
mode_text.text = "Mode: PATROL - Entities follow waypoints"
|
||||
clear_colors()
|
||||
|
||||
# Toggle Dijkstra visualization
|
||||
elif keycode == 68 or keycode == 100: # 'D' or 'd'
|
||||
show_dijkstra = not show_dijkstra
|
||||
debug_text.text = f"Dijkstra Debug: {'ON' if show_dijkstra else 'OFF'}"
|
||||
if not show_dijkstra:
|
||||
clear_colors()
|
||||
|
||||
# Move player with arrow keys or WASD
|
||||
elif keycode in [87, 119]: # W/w - Up
|
||||
if player.y > 0:
|
||||
path = player.path_to(int(player.x), int(player.y) - 1)
|
||||
if path:
|
||||
player.y -= 1
|
||||
elif keycode in [83, 115]: # S/s - Down
|
||||
if player.y < 19:
|
||||
path = player.path_to(int(player.x), int(player.y) + 1)
|
||||
if path:
|
||||
player.y += 1
|
||||
elif keycode in [65, 97]: # A/a - Left
|
||||
if player.x > 0:
|
||||
path = player.path_to(int(player.x) - 1, int(player.y))
|
||||
if path:
|
||||
player.x -= 1
|
||||
elif keycode in [68, 100]: # D/d - Right
|
||||
if player.x < 29:
|
||||
path = player.path_to(int(player.x) + 1, int(player.y))
|
||||
if path:
|
||||
player.x += 1
|
||||
|
||||
# Reset
|
||||
elif keycode == 82 or keycode == 114: # 'R' or 'r'
|
||||
spawn_entities()
|
||||
clear_colors()
|
||||
|
||||
# Quit
|
||||
elif keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC
|
||||
print("\nExiting pathfinding showcase...")
|
||||
sys.exit(0)
|
||||
|
||||
def clear_colors():
|
||||
"""Reset floor colors"""
|
||||
for y in range(20):
|
||||
for x in range(30):
|
||||
if grid.at(x, y).walkable:
|
||||
grid.at(x, y).color = FLOOR_COLOR
|
||||
|
||||
# Create the showcase
|
||||
print("Pathfinding Showcase Demo")
|
||||
print("=========================")
|
||||
print("Controls:")
|
||||
print(" WASD - Move player")
|
||||
print(" 1 - Chase mode (enemies pursue)")
|
||||
print(" 2 - Flee mode (enemies avoid)")
|
||||
print(" 3 - Patrol mode")
|
||||
print(" D - Toggle Dijkstra visualization")
|
||||
print(" R - Reset entities")
|
||||
print(" Q/ESC - Quit")
|
||||
|
||||
# Create dungeon
|
||||
create_dungeon()
|
||||
spawn_entities()
|
||||
|
||||
# Set up UI
|
||||
ui = mcrfpy.sceneUI("pathfinding_showcase")
|
||||
ui.append(grid)
|
||||
|
||||
# Scale and position
|
||||
grid.size = (750, 500) # 30*25, 20*25
|
||||
grid.position = (25, 60)
|
||||
|
||||
# Add title
|
||||
title = mcrfpy.Caption("Pathfinding Showcase", 300, 10)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Add mode text
|
||||
mode_text = mcrfpy.Caption("Mode: CHASE - Enemies pursue player", 25, 580)
|
||||
mode_text.fill_color = mcrfpy.Color(255, 255, 200)
|
||||
ui.append(mode_text)
|
||||
|
||||
# Add debug text
|
||||
debug_text = mcrfpy.Caption("Dijkstra Debug: OFF", 25, 600)
|
||||
debug_text.fill_color = mcrfpy.Color(200, 200, 255)
|
||||
ui.append(debug_text)
|
||||
|
||||
# Add legend
|
||||
legend = mcrfpy.Caption("@ Player E Enemy $ Treasure P Patrol", 25, 620)
|
||||
legend.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(legend)
|
||||
|
||||
# Set up input handling
|
||||
mcrfpy.keypressScene(handle_keypress)
|
||||
|
||||
# Set up animation timer
|
||||
mcrfpy.setTimer("entities", update_entities, 16) # 60 FPS
|
||||
|
||||
# Show scene
|
||||
mcrfpy.setScene("pathfinding_showcase")
|
||||
|
||||
print("\nShowcase ready! Move with WASD and watch entities react.")
|
||||
226
tests/demos/simple_text_input.py
Normal file
226
tests/demos/simple_text_input.py
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple Text Input Widget for McRogueFace
|
||||
Minimal implementation focusing on core functionality
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
|
||||
class TextInput:
|
||||
"""Simple text input widget"""
|
||||
def __init__(self, x, y, width, label=""):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.width = width
|
||||
self.label = label
|
||||
self.text = ""
|
||||
self.cursor_pos = 0
|
||||
self.focused = False
|
||||
|
||||
# Create UI elements
|
||||
self.frame = mcrfpy.Frame(self.x, self.y, self.width, 24)
|
||||
self.frame.fill_color = (255, 255, 255, 255)
|
||||
self.frame.outline_color = (128, 128, 128, 255)
|
||||
self.frame.outline = 2
|
||||
|
||||
# Label
|
||||
if self.label:
|
||||
self.label_caption = mcrfpy.Caption(self.label, self.x, self.y - 20)
|
||||
self.label_caption.fill_color = (255, 255, 255, 255)
|
||||
|
||||
# Text display
|
||||
self.text_caption = mcrfpy.Caption("", self.x + 4, self.y + 4)
|
||||
self.text_caption.fill_color = (0, 0, 0, 255)
|
||||
|
||||
# Cursor (a simple vertical line using a frame)
|
||||
self.cursor = mcrfpy.Frame(self.x + 4, self.y + 4, 2, 16)
|
||||
self.cursor.fill_color = (0, 0, 0, 255)
|
||||
self.cursor.visible = False
|
||||
|
||||
# Click handler
|
||||
self.frame.click = self._on_click
|
||||
|
||||
def _on_click(self, x, y, button):
|
||||
"""Handle clicks"""
|
||||
if button == 1: # Left click
|
||||
# Request focus
|
||||
global current_focus
|
||||
if current_focus and current_focus != self:
|
||||
current_focus.blur()
|
||||
current_focus = self
|
||||
self.focus()
|
||||
|
||||
def focus(self):
|
||||
"""Give focus to this input"""
|
||||
self.focused = True
|
||||
self.frame.outline_color = (0, 120, 255, 255)
|
||||
self.frame.outline = 3
|
||||
self.cursor.visible = True
|
||||
self._update_cursor()
|
||||
|
||||
def blur(self):
|
||||
"""Remove focus"""
|
||||
self.focused = False
|
||||
self.frame.outline_color = (128, 128, 128, 255)
|
||||
self.frame.outline = 2
|
||||
self.cursor.visible = False
|
||||
|
||||
def handle_key(self, key):
|
||||
"""Process keyboard input"""
|
||||
if not self.focused:
|
||||
return False
|
||||
|
||||
if key == "BackSpace":
|
||||
if self.cursor_pos > 0:
|
||||
self.text = self.text[:self.cursor_pos-1] + self.text[self.cursor_pos:]
|
||||
self.cursor_pos -= 1
|
||||
elif key == "Delete":
|
||||
if self.cursor_pos < len(self.text):
|
||||
self.text = self.text[:self.cursor_pos] + self.text[self.cursor_pos+1:]
|
||||
elif key == "Left":
|
||||
self.cursor_pos = max(0, self.cursor_pos - 1)
|
||||
elif key == "Right":
|
||||
self.cursor_pos = min(len(self.text), self.cursor_pos + 1)
|
||||
elif key == "Home":
|
||||
self.cursor_pos = 0
|
||||
elif key == "End":
|
||||
self.cursor_pos = len(self.text)
|
||||
elif len(key) == 1 and key.isprintable():
|
||||
self.text = self.text[:self.cursor_pos] + key + self.text[self.cursor_pos:]
|
||||
self.cursor_pos += 1
|
||||
else:
|
||||
return False
|
||||
|
||||
self._update_display()
|
||||
return True
|
||||
|
||||
def _update_display(self):
|
||||
"""Update text display"""
|
||||
self.text_caption.text = self.text
|
||||
self._update_cursor()
|
||||
|
||||
def _update_cursor(self):
|
||||
"""Update cursor position"""
|
||||
if self.focused:
|
||||
# Estimate character width (roughly 10 pixels per char)
|
||||
self.cursor.x = self.x + 4 + (self.cursor_pos * 10)
|
||||
|
||||
def add_to_scene(self, scene):
|
||||
"""Add all components to scene"""
|
||||
scene.append(self.frame)
|
||||
if hasattr(self, 'label_caption'):
|
||||
scene.append(self.label_caption)
|
||||
scene.append(self.text_caption)
|
||||
scene.append(self.cursor)
|
||||
|
||||
|
||||
# Global focus tracking
|
||||
current_focus = None
|
||||
text_inputs = []
|
||||
|
||||
|
||||
def demo_test(timer_name):
|
||||
"""Run automated demo after scene loads"""
|
||||
print("\n=== Text Input Widget Demo ===")
|
||||
|
||||
# Test typing in first field
|
||||
print("Testing first input field...")
|
||||
text_inputs[0].focus()
|
||||
for char in "Hello":
|
||||
text_inputs[0].handle_key(char)
|
||||
|
||||
print(f"First field contains: '{text_inputs[0].text}'")
|
||||
|
||||
# Test second field
|
||||
print("\nTesting second input field...")
|
||||
text_inputs[1].focus()
|
||||
for char in "World":
|
||||
text_inputs[1].handle_key(char)
|
||||
|
||||
print(f"Second field contains: '{text_inputs[1].text}'")
|
||||
|
||||
# Test text operations
|
||||
print("\nTesting cursor movement and deletion...")
|
||||
text_inputs[1].handle_key("Home")
|
||||
text_inputs[1].handle_key("Delete")
|
||||
print(f"After delete at start: '{text_inputs[1].text}'")
|
||||
|
||||
text_inputs[1].handle_key("End")
|
||||
text_inputs[1].handle_key("BackSpace")
|
||||
print(f"After backspace at end: '{text_inputs[1].text}'")
|
||||
|
||||
print("\n=== Demo Complete! ===")
|
||||
print("Text input widget is working successfully!")
|
||||
print("Features demonstrated:")
|
||||
print(" - Text entry")
|
||||
print(" - Focus management (blue outline)")
|
||||
print(" - Cursor positioning")
|
||||
print(" - Delete/Backspace operations")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def create_scene():
|
||||
"""Create the demo scene"""
|
||||
global text_inputs
|
||||
|
||||
mcrfpy.createScene("demo")
|
||||
scene = mcrfpy.sceneUI("demo")
|
||||
|
||||
# Background
|
||||
bg = mcrfpy.Frame(0, 0, 800, 600)
|
||||
bg.fill_color = (40, 40, 40, 255)
|
||||
scene.append(bg)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption("Text Input Widget Demo", 10, 10)
|
||||
title.fill_color = (255, 255, 255, 255)
|
||||
scene.append(title)
|
||||
|
||||
# Create input fields
|
||||
input1 = TextInput(50, 100, 300, "Name:")
|
||||
input1.add_to_scene(scene)
|
||||
text_inputs.append(input1)
|
||||
|
||||
input2 = TextInput(50, 160, 300, "Email:")
|
||||
input2.add_to_scene(scene)
|
||||
text_inputs.append(input2)
|
||||
|
||||
input3 = TextInput(50, 220, 400, "Comment:")
|
||||
input3.add_to_scene(scene)
|
||||
text_inputs.append(input3)
|
||||
|
||||
# Status text
|
||||
status = mcrfpy.Caption("Click to focus, type to enter text", 50, 280)
|
||||
status.fill_color = (200, 200, 200, 255)
|
||||
scene.append(status)
|
||||
|
||||
# Keyboard handler
|
||||
def handle_keys(scene_name, key):
|
||||
global current_focus, text_inputs
|
||||
|
||||
# Tab to switch fields
|
||||
if key == "Tab" and current_focus:
|
||||
idx = text_inputs.index(current_focus)
|
||||
next_idx = (idx + 1) % len(text_inputs)
|
||||
text_inputs[next_idx]._on_click(0, 0, 1)
|
||||
else:
|
||||
# Pass to focused input
|
||||
if current_focus:
|
||||
current_focus.handle_key(key)
|
||||
# Update status
|
||||
texts = [inp.text for inp in text_inputs]
|
||||
status.text = f"Values: {texts[0]} | {texts[1]} | {texts[2]}"
|
||||
|
||||
mcrfpy.keypressScene("demo", handle_keys)
|
||||
mcrfpy.setScene("demo")
|
||||
|
||||
# Schedule test
|
||||
mcrfpy.setTimer("test", demo_test, 500)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Starting simple text input demo...")
|
||||
create_scene()
|
||||
190
tests/demos/sizzle_reel_final.py
Normal file
190
tests/demos/sizzle_reel_final.py
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
McRogueFace Animation Sizzle Reel - Final Version
|
||||
=================================================
|
||||
|
||||
Complete demonstration of all animation capabilities.
|
||||
This version works properly with the game loop and avoids API issues.
|
||||
|
||||
WARNING: This demo causes a segmentation fault due to a bug in the
|
||||
AnimationManager. When UI elements with active animations are removed
|
||||
from the scene, the AnimationManager crashes when trying to update them.
|
||||
|
||||
Use sizzle_reel_final_fixed.py instead, which works around this issue
|
||||
by hiding objects off-screen instead of removing them.
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
|
||||
# Configuration
|
||||
DEMO_DURATION = 6.0 # Duration for each demo
|
||||
|
||||
# All available easing functions
|
||||
EASING_FUNCTIONS = [
|
||||
"linear", "easeIn", "easeOut", "easeInOut",
|
||||
"easeInQuad", "easeOutQuad", "easeInOutQuad",
|
||||
"easeInCubic", "easeOutCubic", "easeInOutCubic",
|
||||
"easeInQuart", "easeOutQuart", "easeInOutQuart",
|
||||
"easeInSine", "easeOutSine", "easeInOutSine",
|
||||
"easeInExpo", "easeOutExpo", "easeInOutExpo",
|
||||
"easeInCirc", "easeOutCirc", "easeInOutCirc",
|
||||
"easeInElastic", "easeOutElastic", "easeInOutElastic",
|
||||
"easeInBack", "easeOutBack", "easeInOutBack",
|
||||
"easeInBounce", "easeOutBounce", "easeInOutBounce"
|
||||
]
|
||||
|
||||
# Track demo state
|
||||
current_demo = 0
|
||||
subtitle = None
|
||||
|
||||
def create_scene():
|
||||
"""Create the demo scene"""
|
||||
mcrfpy.createScene("demo")
|
||||
mcrfpy.setScene("demo")
|
||||
|
||||
ui = mcrfpy.sceneUI("demo")
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption("Animation Sizzle Reel", 500, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 0)
|
||||
title.outline = 2
|
||||
title.font_size = 28
|
||||
ui.append(title)
|
||||
|
||||
# Subtitle
|
||||
global subtitle
|
||||
subtitle = mcrfpy.Caption("Starting...", 450, 60)
|
||||
subtitle.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(subtitle)
|
||||
|
||||
return ui
|
||||
|
||||
def demo1_frame_animations():
|
||||
"""Frame position, size, and color animations"""
|
||||
ui = mcrfpy.sceneUI("demo")
|
||||
subtitle.text = "Demo 1: Frame Animations"
|
||||
|
||||
# Create frame
|
||||
f = mcrfpy.Frame(100, 150, 200, 100)
|
||||
f.fill_color = mcrfpy.Color(50, 50, 150)
|
||||
f.outline = 3
|
||||
f.outline_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(f)
|
||||
|
||||
# Animate properties
|
||||
mcrfpy.Animation("x", 600.0, 2.0, "easeInOutBack").start(f)
|
||||
mcrfpy.Animation("y", 300.0, 2.0, "easeInOutElastic").start(f)
|
||||
mcrfpy.Animation("w", 300.0, 2.5, "easeInOutCubic").start(f)
|
||||
mcrfpy.Animation("h", 150.0, 2.5, "easeInOutCubic").start(f)
|
||||
mcrfpy.Animation("fill_color", (255, 100, 50, 200), 3.0, "easeInOutSine").start(f)
|
||||
mcrfpy.Animation("outline", 8.0, 3.0, "easeInOutQuad").start(f)
|
||||
|
||||
def demo2_caption_animations():
|
||||
"""Caption movement and text effects"""
|
||||
ui = mcrfpy.sceneUI("demo")
|
||||
subtitle.text = "Demo 2: Caption Animations"
|
||||
|
||||
# Moving caption
|
||||
c1 = mcrfpy.Caption("Bouncing Text!", 100, 200)
|
||||
c1.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
c1.font_size = 28
|
||||
ui.append(c1)
|
||||
mcrfpy.Animation("x", 800.0, 3.0, "easeOutBounce").start(c1)
|
||||
|
||||
# Color cycling
|
||||
c2 = mcrfpy.Caption("Color Cycle", 400, 300)
|
||||
c2.outline = 2
|
||||
c2.font_size = 28
|
||||
ui.append(c2)
|
||||
mcrfpy.Animation("fill_color", (255, 0, 0, 255), 1.0, "linear").start(c2)
|
||||
|
||||
# Typewriter effect
|
||||
c3 = mcrfpy.Caption("", 100, 400)
|
||||
c3.fill_color = mcrfpy.Color(0, 255, 255)
|
||||
c3.font_size = 28
|
||||
ui.append(c3)
|
||||
mcrfpy.Animation("text", "Typewriter effect animation...", 3.0, "linear").start(c3)
|
||||
|
||||
def demo3_easing_showcase():
|
||||
"""Show all 30 easing functions"""
|
||||
ui = mcrfpy.sceneUI("demo")
|
||||
subtitle.text = "Demo 3: All 30 Easing Functions"
|
||||
|
||||
# Create a small frame for each easing
|
||||
for i, easing in enumerate(EASING_FUNCTIONS[:15]): # First 15
|
||||
row = i // 5
|
||||
col = i % 5
|
||||
x = 100 + col * 200
|
||||
y = 150 + row * 100
|
||||
|
||||
# Frame
|
||||
f = mcrfpy.Frame(x, y, 20, 20)
|
||||
f.fill_color = mcrfpy.Color(100, 150, 255)
|
||||
ui.append(f)
|
||||
|
||||
# Label
|
||||
label = mcrfpy.Caption(easing[:10], x, y - 20)
|
||||
label.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(label)
|
||||
|
||||
# Animate with this easing
|
||||
mcrfpy.Animation("x", float(x + 150), 3.0, easing).start(f)
|
||||
|
||||
def demo4_performance():
|
||||
"""Many simultaneous animations"""
|
||||
ui = mcrfpy.sceneUI("demo")
|
||||
subtitle.text = "Demo 4: 50+ Simultaneous Animations"
|
||||
|
||||
for i in range(50):
|
||||
x = 100 + (i % 10) * 100
|
||||
y = 150 + (i // 10) * 100
|
||||
|
||||
f = mcrfpy.Frame(x, y, 30, 30)
|
||||
f.fill_color = mcrfpy.Color((i*37)%256, (i*73)%256, (i*113)%256)
|
||||
ui.append(f)
|
||||
|
||||
# Animate to random position
|
||||
target_x = 150 + (i % 8) * 110
|
||||
target_y = 200 + (i // 8) * 90
|
||||
easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)]
|
||||
|
||||
mcrfpy.Animation("x", float(target_x), 2.5, easing).start(f)
|
||||
mcrfpy.Animation("y", float(target_y), 2.5, easing).start(f)
|
||||
mcrfpy.Animation("opacity", 0.3 + (i%7)*0.1, 2.0, "easeInOutSine").start(f)
|
||||
|
||||
def clear_demo_objects():
|
||||
"""Clear scene except title and subtitle"""
|
||||
ui = mcrfpy.sceneUI("demo")
|
||||
# Keep removing items after the first 2 (title and subtitle)
|
||||
while len(ui) > 2:
|
||||
# Remove the last item
|
||||
ui.remove(len(ui)-1)
|
||||
|
||||
def next_demo(runtime):
|
||||
"""Run the next demo"""
|
||||
global current_demo
|
||||
|
||||
clear_demo_objects()
|
||||
|
||||
demos = [
|
||||
demo1_frame_animations,
|
||||
demo2_caption_animations,
|
||||
demo3_easing_showcase,
|
||||
demo4_performance
|
||||
]
|
||||
|
||||
if current_demo < len(demos):
|
||||
demos[current_demo]()
|
||||
current_demo += 1
|
||||
|
||||
if current_demo < len(demos):
|
||||
#mcrfpy.setTimer("next", next_demo, int(DEMO_DURATION * 1000))
|
||||
pass
|
||||
else:
|
||||
subtitle.text = "Demo Complete!"
|
||||
|
||||
# Initialize
|
||||
print("Starting Animation Sizzle Reel...")
|
||||
create_scene()
|
||||
mcrfpy.setTimer("start", next_demo, int(DEMO_DURATION * 1000))
|
||||
next_demo(0)
|
||||
193
tests/demos/sizzle_reel_final_fixed.py
Normal file
193
tests/demos/sizzle_reel_final_fixed.py
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
McRogueFace Animation Sizzle Reel - Fixed Version
|
||||
=================================================
|
||||
|
||||
This version works around the animation crash by:
|
||||
1. Using shorter demo durations to ensure animations complete before clearing
|
||||
2. Adding a delay before clearing to let animations finish
|
||||
3. Not removing objects, just hiding them off-screen instead
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
|
||||
# Configuration
|
||||
DEMO_DURATION = 3.5 # Slightly shorter to ensure animations complete
|
||||
CLEAR_DELAY = 0.5 # Extra delay before clearing
|
||||
|
||||
# All available easing functions
|
||||
EASING_FUNCTIONS = [
|
||||
"linear", "easeIn", "easeOut", "easeInOut",
|
||||
"easeInQuad", "easeOutQuad", "easeInOutQuad",
|
||||
"easeInCubic", "easeOutCubic", "easeInOutCubic",
|
||||
"easeInQuart", "easeOutQuart", "easeInOutQuart",
|
||||
"easeInSine", "easeOutSine", "easeInOutSine",
|
||||
"easeInExpo", "easeOutExpo", "easeInOutExpo",
|
||||
"easeInCirc", "easeOutCirc", "easeInOutCirc",
|
||||
"easeInElastic", "easeOutElastic", "easeInOutElastic",
|
||||
"easeInBack", "easeOutBack", "easeInOutBack",
|
||||
"easeInBounce", "easeOutBounce", "easeInOutBounce"
|
||||
]
|
||||
|
||||
# Track demo state
|
||||
current_demo = 0
|
||||
subtitle = None
|
||||
demo_objects = [] # Track objects to hide instead of remove
|
||||
|
||||
def create_scene():
|
||||
"""Create the demo scene"""
|
||||
mcrfpy.createScene("demo")
|
||||
mcrfpy.setScene("demo")
|
||||
|
||||
ui = mcrfpy.sceneUI("demo")
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption("Animation Sizzle Reel", 500, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 0)
|
||||
title.outline = 2
|
||||
ui.append(title)
|
||||
|
||||
# Subtitle
|
||||
global subtitle
|
||||
subtitle = mcrfpy.Caption("Starting...", 450, 60)
|
||||
subtitle.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(subtitle)
|
||||
|
||||
return ui
|
||||
|
||||
def hide_demo_objects():
|
||||
"""Hide demo objects by moving them off-screen instead of removing"""
|
||||
global demo_objects
|
||||
# Move all demo objects far off-screen
|
||||
for obj in demo_objects:
|
||||
obj.x = -1000
|
||||
obj.y = -1000
|
||||
demo_objects = []
|
||||
|
||||
def demo1_frame_animations():
|
||||
"""Frame position, size, and color animations"""
|
||||
global demo_objects
|
||||
ui = mcrfpy.sceneUI("demo")
|
||||
subtitle.text = "Demo 1: Frame Animations"
|
||||
|
||||
# Create frame
|
||||
f = mcrfpy.Frame(100, 150, 200, 100)
|
||||
f.fill_color = mcrfpy.Color(50, 50, 150)
|
||||
f.outline = 3
|
||||
f.outline_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(f)
|
||||
demo_objects.append(f)
|
||||
|
||||
# Animate properties with shorter durations
|
||||
mcrfpy.Animation("x", 600.0, 2.0, "easeInOutBack").start(f)
|
||||
mcrfpy.Animation("y", 300.0, 2.0, "easeInOutElastic").start(f)
|
||||
mcrfpy.Animation("w", 300.0, 2.5, "easeInOutCubic").start(f)
|
||||
mcrfpy.Animation("h", 150.0, 2.5, "easeInOutCubic").start(f)
|
||||
mcrfpy.Animation("fill_color", (255, 100, 50, 200), 3.0, "easeInOutSine").start(f)
|
||||
mcrfpy.Animation("outline", 8.0, 3.0, "easeInOutQuad").start(f)
|
||||
|
||||
def demo2_caption_animations():
|
||||
"""Caption movement and text effects"""
|
||||
global demo_objects
|
||||
ui = mcrfpy.sceneUI("demo")
|
||||
subtitle.text = "Demo 2: Caption Animations"
|
||||
|
||||
# Moving caption
|
||||
c1 = mcrfpy.Caption("Bouncing Text!", 100, 200)
|
||||
c1.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(c1)
|
||||
demo_objects.append(c1)
|
||||
mcrfpy.Animation("x", 800.0, 3.0, "easeOutBounce").start(c1)
|
||||
|
||||
# Color cycling
|
||||
c2 = mcrfpy.Caption("Color Cycle", 400, 300)
|
||||
c2.outline = 2
|
||||
ui.append(c2)
|
||||
demo_objects.append(c2)
|
||||
mcrfpy.Animation("fill_color", (255, 0, 0, 255), 1.0, "linear").start(c2)
|
||||
|
||||
# Static text (no typewriter effect to avoid issues)
|
||||
c3 = mcrfpy.Caption("Animation Demo", 100, 400)
|
||||
c3.fill_color = mcrfpy.Color(0, 255, 255)
|
||||
ui.append(c3)
|
||||
demo_objects.append(c3)
|
||||
|
||||
def demo3_easing_showcase():
|
||||
"""Show all 30 easing functions"""
|
||||
global demo_objects
|
||||
ui = mcrfpy.sceneUI("demo")
|
||||
subtitle.text = "Demo 3: All 30 Easing Functions"
|
||||
|
||||
# Create a small frame for each easing
|
||||
for i, easing in enumerate(EASING_FUNCTIONS[:15]): # First 15
|
||||
row = i // 5
|
||||
col = i % 5
|
||||
x = 100 + col * 200
|
||||
y = 150 + row * 100
|
||||
|
||||
# Frame
|
||||
f = mcrfpy.Frame(x, y, 20, 20)
|
||||
f.fill_color = mcrfpy.Color(100, 150, 255)
|
||||
ui.append(f)
|
||||
demo_objects.append(f)
|
||||
|
||||
# Label
|
||||
label = mcrfpy.Caption(easing[:10], x, y - 20)
|
||||
label.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(label)
|
||||
demo_objects.append(label)
|
||||
|
||||
# Animate with this easing
|
||||
mcrfpy.Animation("x", float(x + 150), 3.0, easing).start(f)
|
||||
|
||||
def demo4_performance():
|
||||
"""Many simultaneous animations"""
|
||||
global demo_objects
|
||||
ui = mcrfpy.sceneUI("demo")
|
||||
subtitle.text = "Demo 4: 50+ Simultaneous Animations"
|
||||
|
||||
for i in range(50):
|
||||
x = 100 + (i % 10) * 80
|
||||
y = 150 + (i // 10) * 80
|
||||
|
||||
f = mcrfpy.Frame(x, y, 30, 30)
|
||||
f.fill_color = mcrfpy.Color((i*37)%256, (i*73)%256, (i*113)%256)
|
||||
ui.append(f)
|
||||
demo_objects.append(f)
|
||||
|
||||
# Animate to random position
|
||||
target_x = 150 + (i % 8) * 90
|
||||
target_y = 200 + (i // 8) * 70
|
||||
easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)]
|
||||
|
||||
mcrfpy.Animation("x", float(target_x), 2.5, easing).start(f)
|
||||
mcrfpy.Animation("y", float(target_y), 2.5, easing).start(f)
|
||||
|
||||
def next_demo(runtime):
|
||||
"""Run the next demo with proper cleanup"""
|
||||
global current_demo
|
||||
|
||||
# First hide old objects
|
||||
hide_demo_objects()
|
||||
|
||||
demos = [
|
||||
demo1_frame_animations,
|
||||
demo2_caption_animations,
|
||||
demo3_easing_showcase,
|
||||
demo4_performance
|
||||
]
|
||||
|
||||
if current_demo < len(demos):
|
||||
demos[current_demo]()
|
||||
current_demo += 1
|
||||
|
||||
if current_demo < len(demos):
|
||||
mcrfpy.setTimer("next", next_demo, int(DEMO_DURATION * 1000))
|
||||
else:
|
||||
subtitle.text = "Demo Complete!"
|
||||
mcrfpy.setTimer("exit", lambda t: mcrfpy.exit(), 2000)
|
||||
|
||||
# Initialize
|
||||
print("Starting Animation Sizzle Reel (Fixed)...")
|
||||
create_scene()
|
||||
mcrfpy.setTimer("start", next_demo, 500)
|
||||
149
tests/demos/text_input_demo.py
Normal file
149
tests/demos/text_input_demo.py
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Text Input Demo with Auto-Test
|
||||
Demonstrates the text input widget system with automated testing
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
from text_input_widget import FocusManager, TextInput
|
||||
|
||||
|
||||
def test_text_input(timer_name):
|
||||
"""Automated test that runs after scene is loaded"""
|
||||
print("Testing text input widget system...")
|
||||
|
||||
# Take a screenshot of the initial state
|
||||
automation.screenshot("text_input_initial.png")
|
||||
|
||||
# Simulate typing in the first field
|
||||
print("Clicking on first field...")
|
||||
automation.click(200, 130) # Click on name field
|
||||
|
||||
# Type some text
|
||||
for char in "John Doe":
|
||||
mcrfpy.keypressScene("text_input_demo", char)
|
||||
|
||||
# Tab to next field
|
||||
mcrfpy.keypressScene("text_input_demo", "Tab")
|
||||
|
||||
# Type email
|
||||
for char in "john@example.com":
|
||||
mcrfpy.keypressScene("text_input_demo", char)
|
||||
|
||||
# Tab to comment field
|
||||
mcrfpy.keypressScene("text_input_demo", "Tab")
|
||||
|
||||
# Type comment
|
||||
for char in "Testing the widget!":
|
||||
mcrfpy.keypressScene("text_input_demo", char)
|
||||
|
||||
# Take final screenshot
|
||||
automation.screenshot("text_input_filled.png")
|
||||
|
||||
print("Text input test complete!")
|
||||
print("Screenshots saved: text_input_initial.png, text_input_filled.png")
|
||||
|
||||
# Exit after test
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def create_demo():
|
||||
"""Create a demo scene with multiple text input fields"""
|
||||
mcrfpy.createScene("text_input_demo")
|
||||
scene = mcrfpy.sceneUI("text_input_demo")
|
||||
|
||||
# Create background
|
||||
bg = mcrfpy.Frame(0, 0, 800, 600)
|
||||
bg.fill_color = (40, 40, 40, 255)
|
||||
scene.append(bg)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(10, 10, "Text Input Widget Demo - Auto Test")
|
||||
title.color = (255, 255, 255, 255)
|
||||
scene.append(title)
|
||||
|
||||
# Instructions
|
||||
instructions = mcrfpy.Caption(10, 50, "This will automatically test the text input system")
|
||||
instructions.color = (200, 200, 200, 255)
|
||||
scene.append(instructions)
|
||||
|
||||
# Create focus manager
|
||||
focus_manager = FocusManager()
|
||||
|
||||
# Create text input fields
|
||||
fields = []
|
||||
|
||||
# Name field
|
||||
name_input = TextInput(50, 120, 300, "Name:", 16)
|
||||
name_input._focus_manager = focus_manager
|
||||
focus_manager.register(name_input)
|
||||
scene.append(name_input.frame)
|
||||
if hasattr(name_input, 'label_text'):
|
||||
scene.append(name_input.label_text)
|
||||
scene.append(name_input.text_display)
|
||||
scene.append(name_input.cursor)
|
||||
fields.append(name_input)
|
||||
|
||||
# Email field
|
||||
email_input = TextInput(50, 180, 300, "Email:", 16)
|
||||
email_input._focus_manager = focus_manager
|
||||
focus_manager.register(email_input)
|
||||
scene.append(email_input.frame)
|
||||
if hasattr(email_input, 'label_text'):
|
||||
scene.append(email_input.label_text)
|
||||
scene.append(email_input.text_display)
|
||||
scene.append(email_input.cursor)
|
||||
fields.append(email_input)
|
||||
|
||||
# Comment field
|
||||
comment_input = TextInput(50, 240, 400, "Comment:", 16)
|
||||
comment_input._focus_manager = focus_manager
|
||||
focus_manager.register(comment_input)
|
||||
scene.append(comment_input.frame)
|
||||
if hasattr(comment_input, 'label_text'):
|
||||
scene.append(comment_input.label_text)
|
||||
scene.append(comment_input.text_display)
|
||||
scene.append(comment_input.cursor)
|
||||
fields.append(comment_input)
|
||||
|
||||
# Result display
|
||||
result_text = mcrfpy.Caption(50, 320, "Values will appear here as you type...")
|
||||
result_text.color = (150, 255, 150, 255)
|
||||
scene.append(result_text)
|
||||
|
||||
def update_result(*args):
|
||||
"""Update the result display with current field values"""
|
||||
name = fields[0].get_text()
|
||||
email = fields[1].get_text()
|
||||
comment = fields[2].get_text()
|
||||
result_text.text = f"Name: {name} | Email: {email} | Comment: {comment}"
|
||||
|
||||
# Set change handlers
|
||||
for field in fields:
|
||||
field.on_change = update_result
|
||||
|
||||
# Keyboard handler
|
||||
def handle_keys(scene_name, key):
|
||||
"""Global keyboard handler"""
|
||||
# Let focus manager handle the key first
|
||||
if not focus_manager.handle_key(key):
|
||||
# Handle focus switching
|
||||
if key == "Tab":
|
||||
focus_manager.focus_next()
|
||||
elif key == "Escape":
|
||||
print("Demo terminated by user")
|
||||
sys.exit(0)
|
||||
|
||||
mcrfpy.keypressScene("text_input_demo", handle_keys)
|
||||
|
||||
# Set the scene
|
||||
mcrfpy.setScene("text_input_demo")
|
||||
|
||||
# Schedule the automated test
|
||||
mcrfpy.setTimer("test", test_text_input, 500) # Run test after 500ms
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_demo()
|
||||
320
tests/demos/text_input_standalone.py
Normal file
320
tests/demos/text_input_standalone.py
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Standalone Text Input Widget System for McRogueFace
|
||||
Complete implementation with demo and automated test
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
|
||||
class FocusManager:
|
||||
"""Manages focus state across multiple widgets"""
|
||||
def __init__(self):
|
||||
self.widgets = []
|
||||
self.focused_widget = None
|
||||
self.focus_index = -1
|
||||
|
||||
def register(self, widget):
|
||||
"""Register a widget with the focus manager"""
|
||||
self.widgets.append(widget)
|
||||
if self.focused_widget is None:
|
||||
self.focus(widget)
|
||||
|
||||
def focus(self, widget):
|
||||
"""Set focus to a specific widget"""
|
||||
if self.focused_widget:
|
||||
self.focused_widget.on_blur()
|
||||
|
||||
self.focused_widget = widget
|
||||
self.focus_index = self.widgets.index(widget) if widget in self.widgets else -1
|
||||
|
||||
if widget:
|
||||
widget.on_focus()
|
||||
|
||||
def focus_next(self):
|
||||
"""Focus the next widget in the list"""
|
||||
if not self.widgets:
|
||||
return
|
||||
|
||||
self.focus_index = (self.focus_index + 1) % len(self.widgets)
|
||||
self.focus(self.widgets[self.focus_index])
|
||||
|
||||
def handle_key(self, key):
|
||||
"""Route key events to focused widget. Returns True if handled."""
|
||||
if self.focused_widget:
|
||||
return self.focused_widget.handle_key(key)
|
||||
return False
|
||||
|
||||
|
||||
class TextInput:
|
||||
"""A text input widget with cursor support"""
|
||||
def __init__(self, x, y, width, label="", font_size=16):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.width = width
|
||||
self.label = label
|
||||
self.font_size = font_size
|
||||
|
||||
# Text state
|
||||
self.text = ""
|
||||
self.cursor_pos = 0
|
||||
|
||||
# Visual state
|
||||
self.focused = False
|
||||
|
||||
# Create UI elements
|
||||
self._create_ui()
|
||||
|
||||
def _create_ui(self):
|
||||
"""Create the visual components"""
|
||||
# Background frame
|
||||
self.frame = mcrfpy.Frame(self.x, self.y, self.width, self.font_size + 8)
|
||||
self.frame.outline = 2
|
||||
self.frame.fill_color = (255, 255, 255, 255)
|
||||
self.frame.outline_color = (128, 128, 128, 255)
|
||||
|
||||
# Label (if provided)
|
||||
if self.label:
|
||||
self.label_text = mcrfpy.Caption(
|
||||
self.x - 5,
|
||||
self.y - self.font_size - 5,
|
||||
self.label
|
||||
)
|
||||
self.label_text.color = (255, 255, 255, 255)
|
||||
|
||||
# Text display
|
||||
self.text_display = mcrfpy.Caption(
|
||||
self.x + 4,
|
||||
self.y + 4,
|
||||
""
|
||||
)
|
||||
self.text_display.color = (0, 0, 0, 255)
|
||||
|
||||
# Cursor (using a thin frame)
|
||||
self.cursor = mcrfpy.Frame(
|
||||
self.x + 4,
|
||||
self.y + 4,
|
||||
2,
|
||||
self.font_size
|
||||
)
|
||||
self.cursor.fill_color = (0, 0, 0, 255)
|
||||
self.cursor.visible = False
|
||||
|
||||
# Click handler
|
||||
self.frame.click = self._on_click
|
||||
|
||||
def _on_click(self, x, y, button):
|
||||
"""Handle mouse clicks on the input field"""
|
||||
if button == 1: # Left click
|
||||
if hasattr(self, '_focus_manager'):
|
||||
self._focus_manager.focus(self)
|
||||
|
||||
def on_focus(self):
|
||||
"""Called when this widget receives focus"""
|
||||
self.focused = True
|
||||
self.frame.outline_color = (0, 120, 255, 255)
|
||||
self.frame.outline = 3
|
||||
self.cursor.visible = True
|
||||
self._update_cursor_position()
|
||||
|
||||
def on_blur(self):
|
||||
"""Called when this widget loses focus"""
|
||||
self.focused = False
|
||||
self.frame.outline_color = (128, 128, 128, 255)
|
||||
self.frame.outline = 2
|
||||
self.cursor.visible = False
|
||||
|
||||
def handle_key(self, key):
|
||||
"""Handle keyboard input. Returns True if key was handled."""
|
||||
if not self.focused:
|
||||
return False
|
||||
|
||||
handled = True
|
||||
|
||||
# Special keys
|
||||
if key == "BackSpace":
|
||||
if self.cursor_pos > 0:
|
||||
self.text = self.text[:self.cursor_pos-1] + self.text[self.cursor_pos:]
|
||||
self.cursor_pos -= 1
|
||||
elif key == "Delete":
|
||||
if self.cursor_pos < len(self.text):
|
||||
self.text = self.text[:self.cursor_pos] + self.text[self.cursor_pos+1:]
|
||||
elif key == "Left":
|
||||
self.cursor_pos = max(0, self.cursor_pos - 1)
|
||||
elif key == "Right":
|
||||
self.cursor_pos = min(len(self.text), self.cursor_pos + 1)
|
||||
elif key == "Home":
|
||||
self.cursor_pos = 0
|
||||
elif key == "End":
|
||||
self.cursor_pos = len(self.text)
|
||||
elif key == "Tab":
|
||||
handled = False # Let focus manager handle
|
||||
elif len(key) == 1 and key.isprintable():
|
||||
# Regular character input
|
||||
self.text = self.text[:self.cursor_pos] + key + self.text[self.cursor_pos:]
|
||||
self.cursor_pos += 1
|
||||
else:
|
||||
handled = False
|
||||
|
||||
# Update display
|
||||
self._update_display()
|
||||
|
||||
return handled
|
||||
|
||||
def _update_display(self):
|
||||
"""Update the text display and cursor position"""
|
||||
self.text_display.text = self.text
|
||||
self._update_cursor_position()
|
||||
|
||||
def _update_cursor_position(self):
|
||||
"""Update cursor visual position based on text position"""
|
||||
if not self.focused:
|
||||
return
|
||||
|
||||
# Simple character width estimation (monospace assumption)
|
||||
char_width = self.font_size * 0.6
|
||||
cursor_x = self.x + 4 + int(self.cursor_pos * char_width)
|
||||
self.cursor.x = cursor_x
|
||||
|
||||
def get_text(self):
|
||||
"""Get the current text content"""
|
||||
return self.text
|
||||
|
||||
def add_to_scene(self, scene):
|
||||
"""Add all components to a scene"""
|
||||
scene.append(self.frame)
|
||||
if hasattr(self, 'label_text'):
|
||||
scene.append(self.label_text)
|
||||
scene.append(self.text_display)
|
||||
scene.append(self.cursor)
|
||||
|
||||
|
||||
def run_automated_test(timer_name):
|
||||
"""Automated test that demonstrates the text input functionality"""
|
||||
print("\n=== Running Text Input Widget Test ===")
|
||||
|
||||
# Take initial screenshot
|
||||
if hasattr(mcrfpy, 'automation'):
|
||||
mcrfpy.automation.screenshot("text_input_test_1_initial.png")
|
||||
print("Screenshot 1: Initial state saved")
|
||||
|
||||
# Simulate some typing
|
||||
print("Simulating keyboard input...")
|
||||
|
||||
# The scene's keyboard handler will process these
|
||||
test_sequence = [
|
||||
("H", "Typing 'H'"),
|
||||
("e", "Typing 'e'"),
|
||||
("l", "Typing 'l'"),
|
||||
("l", "Typing 'l'"),
|
||||
("o", "Typing 'o'"),
|
||||
("Tab", "Switching to next field"),
|
||||
("T", "Typing 'T'"),
|
||||
("e", "Typing 'e'"),
|
||||
("s", "Typing 's'"),
|
||||
("t", "Typing 't'"),
|
||||
("Tab", "Switching to comment field"),
|
||||
("W", "Typing 'W'"),
|
||||
("o", "Typing 'o'"),
|
||||
("r", "Typing 'r'"),
|
||||
("k", "Typing 'k'"),
|
||||
("s", "Typing 's'"),
|
||||
("!", "Typing '!'"),
|
||||
]
|
||||
|
||||
# Process each key
|
||||
for key, desc in test_sequence:
|
||||
print(f" - {desc}")
|
||||
# Trigger the scene's keyboard handler
|
||||
if hasattr(mcrfpy, '_scene_key_handler'):
|
||||
mcrfpy._scene_key_handler("text_input_demo", key)
|
||||
|
||||
# Take final screenshot
|
||||
if hasattr(mcrfpy, 'automation'):
|
||||
mcrfpy.automation.screenshot("text_input_test_2_filled.png")
|
||||
print("Screenshot 2: Filled state saved")
|
||||
|
||||
print("\n=== Text Input Test Complete! ===")
|
||||
print("The text input widget system is working correctly.")
|
||||
print("Features demonstrated:")
|
||||
print(" - Focus management (blue outline on focused field)")
|
||||
print(" - Text entry with cursor")
|
||||
print(" - Tab navigation between fields")
|
||||
print(" - Visual feedback")
|
||||
|
||||
# Exit successfully
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def create_demo():
|
||||
"""Create the demo scene"""
|
||||
mcrfpy.createScene("text_input_demo")
|
||||
scene = mcrfpy.sceneUI("text_input_demo")
|
||||
|
||||
# Create background
|
||||
bg = mcrfpy.Frame(0, 0, 800, 600)
|
||||
bg.fill_color = (40, 40, 40, 255)
|
||||
scene.append(bg)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(10, 10, "Text Input Widget System")
|
||||
title.color = (255, 255, 255, 255)
|
||||
scene.append(title)
|
||||
|
||||
# Instructions
|
||||
info = mcrfpy.Caption(10, 50, "Click to focus | Tab to switch fields | Type to enter text")
|
||||
info.color = (200, 200, 200, 255)
|
||||
scene.append(info)
|
||||
|
||||
# Create focus manager
|
||||
focus_manager = FocusManager()
|
||||
|
||||
# Create text inputs
|
||||
name_input = TextInput(50, 120, 300, "Name:", 16)
|
||||
name_input._focus_manager = focus_manager
|
||||
focus_manager.register(name_input)
|
||||
name_input.add_to_scene(scene)
|
||||
|
||||
email_input = TextInput(50, 180, 300, "Email:", 16)
|
||||
email_input._focus_manager = focus_manager
|
||||
focus_manager.register(email_input)
|
||||
email_input.add_to_scene(scene)
|
||||
|
||||
comment_input = TextInput(50, 240, 400, "Comment:", 16)
|
||||
comment_input._focus_manager = focus_manager
|
||||
focus_manager.register(comment_input)
|
||||
comment_input.add_to_scene(scene)
|
||||
|
||||
# Status display
|
||||
status = mcrfpy.Caption(50, 320, "Ready for input...")
|
||||
status.color = (150, 255, 150, 255)
|
||||
scene.append(status)
|
||||
|
||||
# Store references for the keyboard handler
|
||||
widgets = [name_input, email_input, comment_input]
|
||||
|
||||
# Keyboard handler
|
||||
def handle_keys(scene_name, key):
|
||||
"""Global keyboard handler"""
|
||||
if not focus_manager.handle_key(key):
|
||||
if key == "Tab":
|
||||
focus_manager.focus_next()
|
||||
|
||||
# Update status
|
||||
texts = [w.get_text() for w in widgets]
|
||||
status.text = f"Name: '{texts[0]}' | Email: '{texts[1]}' | Comment: '{texts[2]}'"
|
||||
|
||||
# Store handler reference for test
|
||||
mcrfpy._scene_key_handler = handle_keys
|
||||
|
||||
mcrfpy.keypressScene("text_input_demo", handle_keys)
|
||||
mcrfpy.setScene("text_input_demo")
|
||||
|
||||
# Schedule automated test
|
||||
mcrfpy.setTimer("test", run_automated_test, 1000) # Run after 1 second
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Starting Text Input Widget Demo...")
|
||||
create_demo()
|
||||
320
tests/demos/text_input_widget.py
Normal file
320
tests/demos/text_input_widget.py
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Text Input Widget System for McRogueFace
|
||||
A pure Python implementation of focusable text input fields
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, List, Callable
|
||||
|
||||
|
||||
class FocusManager:
|
||||
"""Manages focus state across multiple widgets"""
|
||||
def __init__(self):
|
||||
self.widgets: List['TextInput'] = []
|
||||
self.focused_widget: Optional['TextInput'] = None
|
||||
self.focus_index: int = -1
|
||||
|
||||
def register(self, widget: 'TextInput'):
|
||||
"""Register a widget with the focus manager"""
|
||||
self.widgets.append(widget)
|
||||
if self.focused_widget is None:
|
||||
self.focus(widget)
|
||||
|
||||
def focus(self, widget: 'TextInput'):
|
||||
"""Set focus to a specific widget"""
|
||||
if self.focused_widget:
|
||||
self.focused_widget.on_blur()
|
||||
|
||||
self.focused_widget = widget
|
||||
self.focus_index = self.widgets.index(widget) if widget in self.widgets else -1
|
||||
|
||||
if widget:
|
||||
widget.on_focus()
|
||||
|
||||
def focus_next(self):
|
||||
"""Focus the next widget in the list"""
|
||||
if not self.widgets:
|
||||
return
|
||||
|
||||
self.focus_index = (self.focus_index + 1) % len(self.widgets)
|
||||
self.focus(self.widgets[self.focus_index])
|
||||
|
||||
def focus_prev(self):
|
||||
"""Focus the previous widget in the list"""
|
||||
if not self.widgets:
|
||||
return
|
||||
|
||||
self.focus_index = (self.focus_index - 1) % len(self.widgets)
|
||||
self.focus(self.widgets[self.focus_index])
|
||||
|
||||
def handle_key(self, key: str) -> bool:
|
||||
"""Route key events to focused widget. Returns True if handled."""
|
||||
if self.focused_widget:
|
||||
return self.focused_widget.handle_key(key)
|
||||
return False
|
||||
|
||||
|
||||
class TextInput:
|
||||
"""A text input widget with cursor and selection support"""
|
||||
def __init__(self, x: int, y: int, width: int = 200, label: str = "",
|
||||
font_size: int = 16, on_change: Optional[Callable] = None):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.width = width
|
||||
self.label = label
|
||||
self.font_size = font_size
|
||||
self.on_change = on_change
|
||||
|
||||
# Text state
|
||||
self.text = ""
|
||||
self.cursor_pos = 0
|
||||
self.selection_start = -1
|
||||
self.selection_end = -1
|
||||
|
||||
# Visual state
|
||||
self.focused = False
|
||||
self.cursor_visible = True
|
||||
self.cursor_blink_timer = 0
|
||||
|
||||
# Create UI elements
|
||||
self._create_ui()
|
||||
|
||||
def _create_ui(self):
|
||||
"""Create the visual components"""
|
||||
# Background frame
|
||||
self.frame = mcrfpy.Frame(self.x, self.y, self.width, self.font_size + 8)
|
||||
self.frame.outline = 2
|
||||
self.frame.fill_color = (255, 255, 255, 255)
|
||||
self.frame.outline_color = (128, 128, 128, 255)
|
||||
|
||||
# Label (if provided)
|
||||
if self.label:
|
||||
self.label_text = mcrfpy.Caption(
|
||||
self.x - 5,
|
||||
self.y - self.font_size - 5,
|
||||
self.label
|
||||
)
|
||||
self.label_text.color = (255, 255, 255, 255)
|
||||
|
||||
# Text display
|
||||
self.text_display = mcrfpy.Caption(
|
||||
self.x + 4,
|
||||
self.y + 4,
|
||||
""
|
||||
)
|
||||
self.text_display.color = (0, 0, 0, 255)
|
||||
|
||||
# Cursor (using a thin frame)
|
||||
self.cursor = mcrfpy.Frame(
|
||||
self.x + 4,
|
||||
self.y + 4,
|
||||
2,
|
||||
self.font_size
|
||||
)
|
||||
self.cursor.fill_color = (0, 0, 0, 255)
|
||||
self.cursor.visible = False
|
||||
|
||||
# Click handler
|
||||
self.frame.click = self._on_click
|
||||
|
||||
def _on_click(self, x: int, y: int, button: int):
|
||||
"""Handle mouse clicks on the input field"""
|
||||
if button == 1: # Left click
|
||||
# Request focus through the focus manager
|
||||
if hasattr(self, '_focus_manager'):
|
||||
self._focus_manager.focus(self)
|
||||
|
||||
def on_focus(self):
|
||||
"""Called when this widget receives focus"""
|
||||
self.focused = True
|
||||
self.frame.outline_color = (0, 120, 255, 255)
|
||||
self.frame.outline = 3
|
||||
self.cursor.visible = True
|
||||
self._update_cursor_position()
|
||||
|
||||
def on_blur(self):
|
||||
"""Called when this widget loses focus"""
|
||||
self.focused = False
|
||||
self.frame.outline_color = (128, 128, 128, 255)
|
||||
self.frame.outline = 2
|
||||
self.cursor.visible = False
|
||||
|
||||
def handle_key(self, key: str) -> bool:
|
||||
"""Handle keyboard input. Returns True if key was handled."""
|
||||
if not self.focused:
|
||||
return False
|
||||
|
||||
handled = True
|
||||
old_text = self.text
|
||||
|
||||
# Special keys
|
||||
if key == "BackSpace":
|
||||
if self.cursor_pos > 0:
|
||||
self.text = self.text[:self.cursor_pos-1] + self.text[self.cursor_pos:]
|
||||
self.cursor_pos -= 1
|
||||
elif key == "Delete":
|
||||
if self.cursor_pos < len(self.text):
|
||||
self.text = self.text[:self.cursor_pos] + self.text[self.cursor_pos+1:]
|
||||
elif key == "Left":
|
||||
self.cursor_pos = max(0, self.cursor_pos - 1)
|
||||
elif key == "Right":
|
||||
self.cursor_pos = min(len(self.text), self.cursor_pos + 1)
|
||||
elif key == "Home":
|
||||
self.cursor_pos = 0
|
||||
elif key == "End":
|
||||
self.cursor_pos = len(self.text)
|
||||
elif key == "Return":
|
||||
handled = False # Let parent handle submit
|
||||
elif key == "Tab":
|
||||
handled = False # Let focus manager handle
|
||||
elif len(key) == 1 and key.isprintable():
|
||||
# Regular character input
|
||||
self.text = self.text[:self.cursor_pos] + key + self.text[self.cursor_pos:]
|
||||
self.cursor_pos += 1
|
||||
else:
|
||||
handled = False
|
||||
|
||||
# Update display
|
||||
if old_text != self.text:
|
||||
self._update_display()
|
||||
if self.on_change:
|
||||
self.on_change(self.text)
|
||||
else:
|
||||
self._update_cursor_position()
|
||||
|
||||
return handled
|
||||
|
||||
def _update_display(self):
|
||||
"""Update the text display and cursor position"""
|
||||
self.text_display.text = self.text
|
||||
self._update_cursor_position()
|
||||
|
||||
def _update_cursor_position(self):
|
||||
"""Update cursor visual position based on text position"""
|
||||
if not self.focused:
|
||||
return
|
||||
|
||||
# Simple character width estimation (monospace assumption)
|
||||
char_width = self.font_size * 0.6
|
||||
cursor_x = self.x + 4 + int(self.cursor_pos * char_width)
|
||||
self.cursor.x = cursor_x
|
||||
|
||||
def set_text(self, text: str):
|
||||
"""Set the text content"""
|
||||
self.text = text
|
||||
self.cursor_pos = len(text)
|
||||
self._update_display()
|
||||
|
||||
def get_text(self) -> str:
|
||||
"""Get the current text content"""
|
||||
return self.text
|
||||
|
||||
|
||||
# Demo application
|
||||
def create_demo():
|
||||
"""Create a demo scene with multiple text input fields"""
|
||||
mcrfpy.createScene("text_input_demo")
|
||||
scene = mcrfpy.sceneUI("text_input_demo")
|
||||
|
||||
# Create background
|
||||
bg = mcrfpy.Frame(0, 0, 800, 600)
|
||||
bg.fill_color = (40, 40, 40, 255)
|
||||
scene.append(bg)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(10, 10, "Text Input Widget Demo")
|
||||
title.color = (255, 255, 255, 255)
|
||||
scene.append(title)
|
||||
|
||||
# Instructions
|
||||
instructions = mcrfpy.Caption(10, 50, "Click to focus, Tab to switch fields, Type to enter text")
|
||||
instructions.color = (200, 200, 200, 255)
|
||||
scene.append(instructions)
|
||||
|
||||
# Create focus manager
|
||||
focus_manager = FocusManager()
|
||||
|
||||
# Create text input fields
|
||||
fields = []
|
||||
|
||||
# Name field
|
||||
name_input = TextInput(50, 120, 300, "Name:", 16)
|
||||
name_input._focus_manager = focus_manager
|
||||
focus_manager.register(name_input)
|
||||
scene.append(name_input.frame)
|
||||
if hasattr(name_input, 'label_text'):
|
||||
scene.append(name_input.label_text)
|
||||
scene.append(name_input.text_display)
|
||||
scene.append(name_input.cursor)
|
||||
fields.append(name_input)
|
||||
|
||||
# Email field
|
||||
email_input = TextInput(50, 180, 300, "Email:", 16)
|
||||
email_input._focus_manager = focus_manager
|
||||
focus_manager.register(email_input)
|
||||
scene.append(email_input.frame)
|
||||
if hasattr(email_input, 'label_text'):
|
||||
scene.append(email_input.label_text)
|
||||
scene.append(email_input.text_display)
|
||||
scene.append(email_input.cursor)
|
||||
fields.append(email_input)
|
||||
|
||||
# Comment field
|
||||
comment_input = TextInput(50, 240, 400, "Comment:", 16)
|
||||
comment_input._focus_manager = focus_manager
|
||||
focus_manager.register(comment_input)
|
||||
scene.append(comment_input.frame)
|
||||
if hasattr(comment_input, 'label_text'):
|
||||
scene.append(comment_input.label_text)
|
||||
scene.append(comment_input.text_display)
|
||||
scene.append(comment_input.cursor)
|
||||
fields.append(comment_input)
|
||||
|
||||
# Result display
|
||||
result_text = mcrfpy.Caption(50, 320, "Type in the fields above...")
|
||||
result_text.color = (150, 255, 150, 255)
|
||||
scene.append(result_text)
|
||||
|
||||
def update_result(*args):
|
||||
"""Update the result display with current field values"""
|
||||
name = fields[0].get_text()
|
||||
email = fields[1].get_text()
|
||||
comment = fields[2].get_text()
|
||||
result_text.text = f"Name: {name} | Email: {email} | Comment: {comment}"
|
||||
|
||||
# Set change handlers
|
||||
for field in fields:
|
||||
field.on_change = update_result
|
||||
|
||||
# Keyboard handler
|
||||
def handle_keys(scene_name, key):
|
||||
"""Global keyboard handler"""
|
||||
# Let focus manager handle the key first
|
||||
if not focus_manager.handle_key(key):
|
||||
# Handle focus switching
|
||||
if key == "Tab":
|
||||
focus_manager.focus_next()
|
||||
elif key == "Escape":
|
||||
print("Demo complete!")
|
||||
sys.exit(0)
|
||||
|
||||
mcrfpy.keypressScene("text_input_demo", handle_keys)
|
||||
|
||||
# Set the scene
|
||||
mcrfpy.setScene("text_input_demo")
|
||||
|
||||
# Add a timer for cursor blinking (optional enhancement)
|
||||
def blink_cursor(timer_name):
|
||||
"""Blink the cursor for the focused widget"""
|
||||
if focus_manager.focused_widget and focus_manager.focused_widget.focused:
|
||||
cursor = focus_manager.focused_widget.cursor
|
||||
cursor.visible = not cursor.visible
|
||||
|
||||
mcrfpy.setTimer("cursor_blink", blink_cursor, 500) # Blink every 500ms
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_demo()
|
||||
Loading…
Add table
Add a link
Reference in a new issue