Directory structure cleanup and organization overhaul
This commit is contained in:
parent
1a143982e1
commit
98fc49a978
119 changed files with 10483 additions and 4042 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)
|
||||
615
tests/demos/animation_sizzle_reel.py
Normal file
615
tests/demos/animation_sizzle_reel.py
Normal file
|
|
@ -0,0 +1,615 @@
|
|||
#!/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 = Grid(100, 150, grid_size=(20, 15), texture=texture,
|
||||
tile_width=24, tile_height=24)
|
||||
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, sprite_index=8)
|
||||
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, sprite_index=12)
|
||||
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(ui[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)
|
||||
318
tests/demos/animation_sizzle_reel_working.py
Normal file
318
tests/demos/animation_sizzle_reel_working.py
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
#!/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"]:
|
||||
if not mcrfpy.getTimer(timer):
|
||||
continue
|
||||
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.")
|
||||
1204
tests/demos/exhaustive_api_demo.py
Normal file
1204
tests/demos/exhaustive_api_demo.py
Normal file
File diff suppressed because it is too large
Load diff
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")
|
||||
373
tests/demos/pathfinding_showcase.py
Normal file
373
tests/demos/pathfinding_showcase.py
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
#!/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
|
||||
|
||||
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)
|
||||
player.sprite_index = 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)
|
||||
enemy.sprite_index = 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)
|
||||
treasure.sprite_index = TREASURE
|
||||
grid.entities.append(treasure)
|
||||
treasures.append(treasure)
|
||||
|
||||
# Spawn patrol entities
|
||||
patrol = mcrfpy.Entity(10, 10)
|
||||
patrol.sprite_index = PATROL
|
||||
patrol.waypoints = [(10, 10), (19, 10), (19, 16), (10, 16)] # Square patrol
|
||||
patrol.waypoint_index = 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 not hasattr(patrol, 'waypoints'):
|
||||
continue
|
||||
|
||||
# Get current waypoint
|
||||
target_x, target_y = patrol.waypoints[patrol.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
|
||||
patrol.waypoint_index = (patrol.waypoint_index + 1) % len(patrol.waypoints)
|
||||
target_x, target_y = patrol.waypoints[patrol.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.color = (255, 255, 255, 255)
|
||||
|
||||
# Text display
|
||||
self.text_caption = mcrfpy.Caption("", self.x + 4, self.y + 4)
|
||||
self.text_caption.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.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.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()
|
||||
177
tests/demos/sizzle_reel_final.py
Normal file
177
tests/demos/sizzle_reel_final.py
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
#!/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.
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
|
||||
# Configuration
|
||||
DEMO_DURATION = 4.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
|
||||
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)
|
||||
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
|
||||
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)
|
||||
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(ui[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))
|
||||
else:
|
||||
subtitle.text = "Demo Complete!"
|
||||
|
||||
# Initialize
|
||||
print("Starting Animation Sizzle Reel...")
|
||||
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", font_size=24)
|
||||
title.color = (255, 255, 255, 255)
|
||||
scene.append(title)
|
||||
|
||||
# Instructions
|
||||
instructions = mcrfpy.Caption(10, 50, "This will automatically test the text input system", font_size=14)
|
||||
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...", font_size=14)
|
||||
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()
|
||||
322
tests/demos/text_input_standalone.py
Normal file
322
tests/demos/text_input_standalone.py
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
#!/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,
|
||||
font_size=self.font_size
|
||||
)
|
||||
self.label_text.color = (255, 255, 255, 255)
|
||||
|
||||
# Text display
|
||||
self.text_display = mcrfpy.Caption(
|
||||
self.x + 4,
|
||||
self.y + 4,
|
||||
"",
|
||||
font_size=self.font_size
|
||||
)
|
||||
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", font_size=24)
|
||||
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", font_size=14)
|
||||
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...", font_size=14)
|
||||
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()
|
||||
322
tests/demos/text_input_widget.py
Normal file
322
tests/demos/text_input_widget.py
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
#!/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,
|
||||
font_size=self.font_size
|
||||
)
|
||||
self.label_text.color = (255, 255, 255, 255)
|
||||
|
||||
# Text display
|
||||
self.text_display = mcrfpy.Caption(
|
||||
self.x + 4,
|
||||
self.y + 4,
|
||||
"",
|
||||
font_size=self.font_size
|
||||
)
|
||||
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", font_size=24)
|
||||
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", font_size=14)
|
||||
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...", font_size=14)
|
||||
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