McRogueFace/tests/cookbook/apps/calculator.py

320 lines
9 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""Calculator App - Boss-key calculator with scene switching
Interactive controls:
0-9: Number input
+, -, *, /: Operations
Enter/=: Calculate result
C: Clear
ESC: Toggle between game and calculator scenes
Backspace: Delete last digit
This demonstrates:
- Scene switching (boss key pattern)
- Button grid layout
- State management
- Click handlers
"""
import mcrfpy
import sys
import os
# Add parent to path for imports
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from lib.button import Button
class Calculator:
"""A simple calculator with GUI."""
def __init__(self):
self.scene = mcrfpy.Scene("calculator")
self.ui = self.scene.children
self.expression = ""
self.result = ""
self.setup()
def setup(self):
"""Build the calculator UI."""
# Background
bg = mcrfpy.Frame(
pos=(0, 0),
size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 35)
)
self.ui.append(bg)
# Calculator frame
calc_frame = mcrfpy.Frame(
pos=(312, 100),
size=(400, 550),
fill_color=mcrfpy.Color(40, 40, 50),
outline_color=mcrfpy.Color(80, 80, 100),
outline=2
)
self.ui.append(calc_frame)
# Title
title = mcrfpy.Caption(
text="Calculator",
pos=(512, 130),
font_size=24,
fill_color=mcrfpy.Color(200, 200, 200)
)
self.ui.append(title)
# Display frame
display_bg = mcrfpy.Frame(
pos=(332, 170),
size=(360, 80),
fill_color=mcrfpy.Color(20, 25, 30),
outline_color=mcrfpy.Color(60, 60, 80),
outline=1
)
self.ui.append(display_bg)
# Expression display
self.expr_display = mcrfpy.Caption(
text="",
pos=(682, 180), # Right-aligned
font_size=18,
fill_color=mcrfpy.Color(150, 150, 150)
)
self.ui.append(self.expr_display)
# Result display
self.result_display = mcrfpy.Caption(
text="0",
pos=(682, 210), # Right-aligned
font_size=32,
fill_color=mcrfpy.Color(255, 255, 255)
)
self.ui.append(self.result_display)
# Button layout
button_layout = [
["C", "(", ")", "/"],
["7", "8", "9", "*"],
["4", "5", "6", "-"],
["1", "2", "3", "+"],
["0", ".", "DEL", "="],
]
start_x = 342
start_y = 270
btn_width = 80
btn_height = 50
spacing = 8
for row_idx, row in enumerate(button_layout):
for col_idx, label in enumerate(row):
x = start_x + col_idx * (btn_width + spacing)
y = start_y + row_idx * (btn_height + spacing)
# Different colors for different button types
if label in "0123456789.":
fill = mcrfpy.Color(60, 60, 70)
hover = mcrfpy.Color(80, 80, 95)
elif label == "=":
fill = mcrfpy.Color(80, 120, 80)
hover = mcrfpy.Color(100, 150, 100)
elif label == "C":
fill = mcrfpy.Color(120, 60, 60)
hover = mcrfpy.Color(150, 80, 80)
else:
fill = mcrfpy.Color(70, 70, 90)
hover = mcrfpy.Color(90, 90, 115)
btn = Button(
label,
pos=(x, y),
size=(btn_width, btn_height),
fill_color=fill,
hover_color=hover,
callback=lambda l=label: self.on_button(l),
font_size=20
)
self.ui.append(btn.frame)
# Instructions
instr = mcrfpy.Caption(
text="Press ESC to switch to game | Click buttons or use keyboard",
pos=(512, 580),
font_size=14,
fill_color=mcrfpy.Color(100, 100, 100)
)
self.ui.append(instr)
def on_button(self, label):
"""Handle button press."""
if label == "C":
self.expression = ""
self.result = "0"
elif label == "DEL":
self.expression = self.expression[:-1]
elif label == "=":
self.calculate()
else:
self.expression += label
self._update_display()
def calculate(self):
"""Evaluate the expression."""
if not self.expression:
return
try:
# Safe evaluation (only math operations)
allowed = set("0123456789+-*/.()")
if all(c in allowed for c in self.expression):
self.result = str(eval(self.expression))
if self.result.endswith('.0'):
self.result = self.result[:-2]
else:
self.result = "Error"
except Exception:
self.result = "Error"
def _update_display(self):
"""Update the display captions."""
self.expr_display.text = self.expression or ""
self.result_display.text = self.result or "0"
def on_key(self, key, state):
"""Handle keyboard input."""
if state != "start":
return
if key == "Escape":
# Switch to game scene
mcrfpy.current_scene = game_scene
return
# Map keys to buttons
key_map = {
"Num0": "0", "Num1": "1", "Num2": "2", "Num3": "3",
"Num4": "4", "Num5": "5", "Num6": "6", "Num7": "7",
"Num8": "8", "Num9": "9",
"Period": ".", "Add": "+", "Subtract": "-",
"Multiply": "*", "Divide": "/",
"Enter": "=", "Return": "=",
"C": "C", "Backspace": "DEL",
"LParen": "(", "RParen": ")",
}
if key in key_map:
self.on_button(key_map[key])
def activate(self):
"""Activate the calculator scene."""
self.scene.on_key = self.on_key
mcrfpy.current_scene = self.scene
class GamePlaceholder:
"""Placeholder game scene to switch to/from."""
def __init__(self):
self.scene = mcrfpy.Scene("game_placeholder")
self.ui = self.scene.children
self.setup()
def setup(self):
"""Build the game placeholder UI."""
# Background
bg = mcrfpy.Frame(
pos=(0, 0),
size=(1024, 768),
fill_color=mcrfpy.Color(20, 40, 20)
)
self.ui.append(bg)
# Title
title = mcrfpy.Caption(
text="GAME SCENE",
pos=(512, 350),
font_size=48,
fill_color=mcrfpy.Color(100, 200, 100)
)
title.outline = 3
title.outline_color = mcrfpy.Color(0, 0, 0)
self.ui.append(title)
# Subtitle
subtitle = mcrfpy.Caption(
text="Press ESC for Calculator (Boss Key!)",
pos=(512, 420),
font_size=20,
fill_color=mcrfpy.Color(150, 200, 150)
)
self.ui.append(subtitle)
# Fake game elements
for i in range(5):
fake_element = mcrfpy.Frame(
pos=(100 + i * 180, 550),
size=(150, 100),
fill_color=mcrfpy.Color(40 + i * 10, 60 + i * 5, 40),
outline_color=mcrfpy.Color(80, 120, 80),
outline=1
)
self.ui.append(fake_element)
label = mcrfpy.Caption(
text=f"Game Element {i+1}",
pos=(175 + i * 180, 590),
font_size=12,
fill_color=mcrfpy.Color(200, 200, 200)
)
self.ui.append(label)
def on_key(self, key, state):
"""Handle keyboard input."""
if state != "start":
return
if key == "Escape":
# Switch to calculator
calculator.activate()
def activate(self):
"""Activate the game scene."""
self.scene.on_key = self.on_key
mcrfpy.current_scene = self.scene
# Global instances
calculator = None
game_scene = None
def main():
"""Run the calculator app."""
global calculator, game_scene
calculator = Calculator()
game_scene = GamePlaceholder()
# Start with the game scene
game_scene.activate()
# Headless mode: capture screenshot and exit
try:
if mcrfpy.headless_mode():
from mcrfpy import automation
# Show calculator for screenshot
calculator.activate()
calculator.expression = "7+8"
calculator._update_display()
mcrfpy.Timer("screenshot", lambda rt: (
automation.screenshot("screenshots/apps/calculator.png"),
sys.exit(0)
), 100)
except AttributeError:
pass
if __name__ == "__main__":
main()