Cookbook structure: - lib/: Reusable component library (Button, StatBar, AnimationChain, etc.) - primitives/: Demo apps for individual components - features/: Demo apps for complex features (animation chaining, shaders) - apps/: Complete mini-applications (calculator, dialogue system) - automation/: Screenshot capture utilities API signature updates applied: - on_enter/on_exit/on_move callbacks now only receive (pos) per #230 - on_cell_enter/on_cell_exit callbacks only receive (cell_pos) per #230 - Animation chain library uses Timer-based sequencing (unaffected by #229) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2daebc84b5
commit
55f6ea9502
41 changed files with 8493 additions and 0 deletions
248
tests/cookbook/primitives/demo_text_box.py
Normal file
248
tests/cookbook/primitives/demo_text_box.py
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Text Box Widget Demo - Word-wrapped text with typewriter effect
|
||||
|
||||
Interactive controls:
|
||||
1: Show typewriter text
|
||||
2: Show instant text
|
||||
3: Skip animation
|
||||
4: Clear text
|
||||
D: Toggle dialogue mode
|
||||
ESC: Exit demo
|
||||
"""
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# Add parent to path for imports
|
||||
sys.path.insert(0, str(__file__).rsplit('/', 2)[0])
|
||||
from lib.text_box import TextBox, DialogueBox
|
||||
|
||||
|
||||
class TextBoxDemo:
|
||||
def __init__(self):
|
||||
self.scene = mcrfpy.Scene("text_box_demo")
|
||||
self.ui = self.scene.children
|
||||
self.setup()
|
||||
|
||||
def setup(self):
|
||||
"""Build the demo scene."""
|
||||
# Background
|
||||
bg = mcrfpy.Frame(
|
||||
pos=(0, 0),
|
||||
size=(1024, 768),
|
||||
fill_color=mcrfpy.Color(20, 20, 25)
|
||||
)
|
||||
self.ui.append(bg)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(
|
||||
text="Text Box Widget Demo",
|
||||
pos=(512, 30),
|
||||
font_size=28,
|
||||
fill_color=mcrfpy.Color(255, 255, 255)
|
||||
)
|
||||
title.outline = 2
|
||||
title.outline_color = mcrfpy.Color(0, 0, 0)
|
||||
self.ui.append(title)
|
||||
|
||||
# Section 1: Basic text box with typewriter
|
||||
section1_label = mcrfpy.Caption(
|
||||
text="Typewriter Effect (press 1 to play)",
|
||||
pos=(50, 80),
|
||||
font_size=16,
|
||||
fill_color=mcrfpy.Color(150, 150, 150)
|
||||
)
|
||||
self.ui.append(section1_label)
|
||||
|
||||
self.typewriter_box = TextBox(
|
||||
pos=(50, 110),
|
||||
size=(400, 120),
|
||||
text="",
|
||||
chars_per_second=40
|
||||
)
|
||||
self.ui.append(self.typewriter_box.frame)
|
||||
|
||||
self.sample_text = (
|
||||
"Welcome to McRogueFace! This is a demonstration of the "
|
||||
"typewriter effect. Each character appears one at a time, "
|
||||
"creating a classic RPG dialogue feel. You can adjust the "
|
||||
"speed by changing the chars_per_second parameter."
|
||||
)
|
||||
|
||||
# Completion indicator
|
||||
self.completion_label = mcrfpy.Caption(
|
||||
text="Status: Ready",
|
||||
pos=(50, 240),
|
||||
font_size=12,
|
||||
fill_color=mcrfpy.Color(100, 100, 100)
|
||||
)
|
||||
self.ui.append(self.completion_label)
|
||||
|
||||
# Section 2: Instant text
|
||||
section2_label = mcrfpy.Caption(
|
||||
text="Instant Text (press 2 to change)",
|
||||
pos=(500, 80),
|
||||
font_size=16,
|
||||
fill_color=mcrfpy.Color(150, 150, 150)
|
||||
)
|
||||
self.ui.append(section2_label)
|
||||
|
||||
self.instant_box = TextBox(
|
||||
pos=(500, 110),
|
||||
size=(450, 120),
|
||||
text="This text appeared instantly. Press 2 to change it to different content.",
|
||||
chars_per_second=0 # Instant display
|
||||
)
|
||||
self.ui.append(self.instant_box.frame)
|
||||
|
||||
# Section 3: Dialogue box with speaker
|
||||
section3_label = mcrfpy.Caption(
|
||||
text="Dialogue Box (press D to cycle speakers)",
|
||||
pos=(50, 290),
|
||||
font_size=16,
|
||||
fill_color=mcrfpy.Color(150, 150, 150)
|
||||
)
|
||||
self.ui.append(section3_label)
|
||||
|
||||
self.dialogue_box = DialogueBox(
|
||||
pos=(50, 320),
|
||||
size=(600, 150),
|
||||
speaker="Elder Sage",
|
||||
text="Greetings, adventurer. I have been expecting you. The ancient prophecy speaks of one who would come to restore balance to our world.",
|
||||
chars_per_second=35
|
||||
)
|
||||
self.ui.append(self.dialogue_box.frame)
|
||||
|
||||
self.dialogue_index = 0
|
||||
self.dialogues = [
|
||||
("Elder Sage", "Greetings, adventurer. I have been expecting you. The ancient prophecy speaks of one who would come to restore balance to our world."),
|
||||
("Hero", "I'm not sure I'm the right person for this task. What exactly must I do?"),
|
||||
("Elder Sage", "You must journey to the Forgotten Temple and retrieve the Crystal of Dawn. Only its light can dispel the darkness that threatens our land."),
|
||||
("Mysterious Voice", "Beware... the path is fraught with danger. Many have tried and failed before you..."),
|
||||
("Hero", "I accept this quest. Point me to the temple, and I shall not rest until the crystal is recovered!"),
|
||||
]
|
||||
|
||||
# Section 4: Different styles
|
||||
section4_label = mcrfpy.Caption(
|
||||
text="Custom Styles",
|
||||
pos=(50, 500),
|
||||
font_size=16,
|
||||
fill_color=mcrfpy.Color(150, 150, 150)
|
||||
)
|
||||
self.ui.append(section4_label)
|
||||
|
||||
# Dark theme
|
||||
dark_box = TextBox(
|
||||
pos=(50, 530),
|
||||
size=(280, 100),
|
||||
text="Dark theme with light text. Good for mysterious or ominous messages.",
|
||||
chars_per_second=0,
|
||||
bg_color=mcrfpy.Color(10, 10, 15),
|
||||
text_color=mcrfpy.Color(180, 180, 200),
|
||||
outline_color=mcrfpy.Color(60, 60, 80)
|
||||
)
|
||||
self.ui.append(dark_box.frame)
|
||||
|
||||
# Warning theme
|
||||
warning_box = TextBox(
|
||||
pos=(350, 530),
|
||||
size=(280, 100),
|
||||
text="Warning theme! Use for important alerts or danger notifications.",
|
||||
chars_per_second=0,
|
||||
bg_color=mcrfpy.Color(80, 40, 20),
|
||||
text_color=mcrfpy.Color(255, 200, 100),
|
||||
outline_color=mcrfpy.Color(200, 100, 50)
|
||||
)
|
||||
self.ui.append(warning_box.frame)
|
||||
|
||||
# System theme
|
||||
system_box = TextBox(
|
||||
pos=(650, 530),
|
||||
size=(280, 100),
|
||||
text="[SYSTEM] Connection established. Loading game data...",
|
||||
chars_per_second=0,
|
||||
bg_color=mcrfpy.Color(20, 40, 30),
|
||||
text_color=mcrfpy.Color(100, 255, 150),
|
||||
outline_color=mcrfpy.Color(50, 150, 80)
|
||||
)
|
||||
self.ui.append(system_box.frame)
|
||||
|
||||
# Instructions
|
||||
instr = mcrfpy.Caption(
|
||||
text="1: Play typewriter | 2: Change instant text | 3: Skip | 4: Clear | D: Next dialogue | ESC: Exit",
|
||||
pos=(50, 730),
|
||||
font_size=14,
|
||||
fill_color=mcrfpy.Color(120, 120, 120)
|
||||
)
|
||||
self.ui.append(instr)
|
||||
|
||||
def on_typewriter_complete(self):
|
||||
"""Called when typewriter animation finishes."""
|
||||
self.completion_label.text = "Status: Animation complete!"
|
||||
self.completion_label.fill_color = mcrfpy.Color(100, 200, 100)
|
||||
|
||||
def on_key(self, key, state):
|
||||
"""Handle keyboard input."""
|
||||
if state != "start":
|
||||
return
|
||||
|
||||
if key == "Escape":
|
||||
sys.exit(0)
|
||||
elif key == "Num1":
|
||||
# Start typewriter animation
|
||||
self.typewriter_box.on_complete = self.on_typewriter_complete
|
||||
self.typewriter_box.set_text(self.sample_text, animate=True)
|
||||
self.completion_label.text = "Status: Playing..."
|
||||
self.completion_label.fill_color = mcrfpy.Color(200, 200, 100)
|
||||
elif key == "Num2":
|
||||
# Change instant text
|
||||
texts = [
|
||||
"This text appeared instantly. Press 2 to change it to different content.",
|
||||
"Here's some different content! Text boxes can hold any message you want.",
|
||||
"The quick brown fox jumps over the lazy dog. Perfect for testing fonts!",
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Classic placeholder text.",
|
||||
]
|
||||
import random
|
||||
self.instant_box.set_text(random.choice(texts), animate=False)
|
||||
elif key == "Num3":
|
||||
# Skip animation
|
||||
self.typewriter_box.skip_animation()
|
||||
self.completion_label.text = "Status: Skipped"
|
||||
self.completion_label.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
elif key == "Num4":
|
||||
# Clear text
|
||||
self.typewriter_box.clear()
|
||||
self.completion_label.text = "Status: Cleared"
|
||||
self.completion_label.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
elif key == "D":
|
||||
# Cycle dialogue
|
||||
self.dialogue_index = (self.dialogue_index + 1) % len(self.dialogues)
|
||||
speaker, text = self.dialogues[self.dialogue_index]
|
||||
self.dialogue_box.set_dialogue(speaker, text, animate=True)
|
||||
|
||||
def activate(self):
|
||||
"""Activate the demo scene."""
|
||||
self.scene.on_key = self.on_key
|
||||
mcrfpy.current_scene = self.scene
|
||||
|
||||
|
||||
def main():
|
||||
"""Run the text box demo."""
|
||||
demo = TextBoxDemo()
|
||||
demo.activate()
|
||||
|
||||
# Headless mode: capture screenshot and exit
|
||||
try:
|
||||
if mcrfpy.headless_mode():
|
||||
from mcrfpy import automation
|
||||
# Trigger typewriter then screenshot
|
||||
demo.typewriter_box.set_text(demo.sample_text[:50], animate=False)
|
||||
mcrfpy.Timer("screenshot", lambda rt: (
|
||||
automation.screenshot("screenshots/primitives/text_box_demo.png"),
|
||||
sys.exit(0)
|
||||
), 100)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue