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
340
tests/cookbook/features/demo_shaders.py
Normal file
340
tests/cookbook/features/demo_shaders.py
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Shader Effects Demo - GLSL shader visual effects
|
||||
|
||||
Interactive controls:
|
||||
1-4: Focus on specific shader
|
||||
Space: Toggle shaders on/off
|
||||
R: Reset all shaders
|
||||
ESC: Exit demo
|
||||
|
||||
Shader uniforms available:
|
||||
- float time: Seconds since engine start
|
||||
- float delta_time: Seconds since last frame
|
||||
- vec2 resolution: Texture size in pixels
|
||||
- vec2 mouse: Mouse position in window coordinates
|
||||
"""
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
|
||||
class ShaderDemo:
|
||||
def __init__(self):
|
||||
self.scene = mcrfpy.Scene("shader_demo")
|
||||
self.ui = self.scene.children
|
||||
self.shaders_enabled = True
|
||||
self.shader_frames = []
|
||||
self.shaders = []
|
||||
self.setup()
|
||||
|
||||
def setup(self):
|
||||
"""Build the demo scene."""
|
||||
# Background
|
||||
bg = mcrfpy.Frame(
|
||||
pos=(0, 0),
|
||||
size=(1024, 768),
|
||||
fill_color=mcrfpy.Color(15, 15, 20)
|
||||
)
|
||||
self.ui.append(bg)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(
|
||||
text="Shader Effects 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)
|
||||
|
||||
# Create four shader demo quadrants
|
||||
self._create_pulse_shader(50, 100)
|
||||
self._create_vignette_shader(530, 100)
|
||||
self._create_wave_shader(50, 420)
|
||||
self._create_color_shift_shader(530, 420)
|
||||
|
||||
# Instructions
|
||||
instr = mcrfpy.Caption(
|
||||
text="1-4: Focus shader | Space: Toggle on/off | R: Reset | ESC: Exit",
|
||||
pos=(50, 730),
|
||||
font_size=14,
|
||||
fill_color=mcrfpy.Color(120, 120, 120)
|
||||
)
|
||||
self.ui.append(instr)
|
||||
|
||||
# Status
|
||||
self.status = mcrfpy.Caption(
|
||||
text="Shaders: Enabled",
|
||||
pos=(50, 700),
|
||||
font_size=16,
|
||||
fill_color=mcrfpy.Color(100, 200, 100)
|
||||
)
|
||||
self.ui.append(self.status)
|
||||
|
||||
def _create_pulse_shader(self, x, y):
|
||||
"""Create pulsing glow shader demo."""
|
||||
# Label
|
||||
label = mcrfpy.Caption(
|
||||
text="1. Pulse Glow",
|
||||
pos=(x + 200, y - 20),
|
||||
font_size=16,
|
||||
fill_color=mcrfpy.Color(150, 150, 150)
|
||||
)
|
||||
self.ui.append(label)
|
||||
|
||||
# Frame to apply shader to
|
||||
frame = mcrfpy.Frame(
|
||||
pos=(x, y),
|
||||
size=(420, 280),
|
||||
fill_color=mcrfpy.Color(80, 60, 120),
|
||||
outline_color=mcrfpy.Color(150, 100, 200),
|
||||
outline=2
|
||||
)
|
||||
|
||||
# Add some content
|
||||
content = mcrfpy.Caption(
|
||||
text="Brightness pulses\nusing time uniform",
|
||||
pos=(210, 100),
|
||||
font_size=18,
|
||||
fill_color=mcrfpy.Color(255, 255, 255)
|
||||
)
|
||||
frame.children.append(content)
|
||||
|
||||
# Create pulse shader
|
||||
pulse_shader = mcrfpy.Shader('''
|
||||
uniform sampler2D texture;
|
||||
uniform float time;
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_TexCoord[0].xy;
|
||||
vec4 color = texture2D(texture, uv);
|
||||
|
||||
// Pulse brightness
|
||||
float pulse = 0.7 + 0.3 * sin(time * 2.0);
|
||||
color.rgb *= pulse;
|
||||
|
||||
gl_FragColor = color * gl_Color;
|
||||
}
|
||||
''', dynamic=True)
|
||||
|
||||
frame.shader = pulse_shader
|
||||
self.ui.append(frame)
|
||||
self.shader_frames.append(frame)
|
||||
self.shaders.append(pulse_shader)
|
||||
|
||||
def _create_vignette_shader(self, x, y):
|
||||
"""Create vignette (darkened edges) shader demo."""
|
||||
# Label
|
||||
label = mcrfpy.Caption(
|
||||
text="2. Vignette",
|
||||
pos=(x + 200, y - 20),
|
||||
font_size=16,
|
||||
fill_color=mcrfpy.Color(150, 150, 150)
|
||||
)
|
||||
self.ui.append(label)
|
||||
|
||||
# Frame
|
||||
frame = mcrfpy.Frame(
|
||||
pos=(x, y),
|
||||
size=(420, 280),
|
||||
fill_color=mcrfpy.Color(100, 80, 60),
|
||||
outline_color=mcrfpy.Color(200, 150, 100),
|
||||
outline=2
|
||||
)
|
||||
|
||||
content = mcrfpy.Caption(
|
||||
text="Darkened edges\nclassic vignette effect",
|
||||
pos=(210, 100),
|
||||
font_size=18,
|
||||
fill_color=mcrfpy.Color(255, 255, 255)
|
||||
)
|
||||
frame.children.append(content)
|
||||
|
||||
# Create vignette shader
|
||||
vignette_shader = mcrfpy.Shader('''
|
||||
uniform sampler2D texture;
|
||||
uniform vec2 resolution;
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_TexCoord[0].xy;
|
||||
vec4 color = texture2D(texture, uv);
|
||||
|
||||
// Calculate distance from center
|
||||
vec2 center = vec2(0.5, 0.5);
|
||||
float dist = distance(uv, center);
|
||||
|
||||
// Vignette effect - darken edges
|
||||
float vignette = 1.0 - smoothstep(0.3, 0.8, dist);
|
||||
color.rgb *= vignette;
|
||||
|
||||
gl_FragColor = color * gl_Color;
|
||||
}
|
||||
''', dynamic=False)
|
||||
|
||||
frame.shader = vignette_shader
|
||||
self.ui.append(frame)
|
||||
self.shader_frames.append(frame)
|
||||
self.shaders.append(vignette_shader)
|
||||
|
||||
def _create_wave_shader(self, x, y):
|
||||
"""Create wave distortion shader demo."""
|
||||
# Label
|
||||
label = mcrfpy.Caption(
|
||||
text="3. Wave Distortion",
|
||||
pos=(x + 200, y - 20),
|
||||
font_size=16,
|
||||
fill_color=mcrfpy.Color(150, 150, 150)
|
||||
)
|
||||
self.ui.append(label)
|
||||
|
||||
# Frame
|
||||
frame = mcrfpy.Frame(
|
||||
pos=(x, y),
|
||||
size=(420, 280),
|
||||
fill_color=mcrfpy.Color(60, 100, 120),
|
||||
outline_color=mcrfpy.Color(100, 180, 220),
|
||||
outline=2
|
||||
)
|
||||
|
||||
content = mcrfpy.Caption(
|
||||
text="Sine wave\nUV distortion",
|
||||
pos=(210, 100),
|
||||
font_size=18,
|
||||
fill_color=mcrfpy.Color(255, 255, 255)
|
||||
)
|
||||
frame.children.append(content)
|
||||
|
||||
# Create wave shader
|
||||
wave_shader = mcrfpy.Shader('''
|
||||
uniform sampler2D texture;
|
||||
uniform float time;
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_TexCoord[0].xy;
|
||||
|
||||
// Apply wave distortion
|
||||
float wave = sin(uv.y * 20.0 + time * 3.0) * 0.01;
|
||||
uv.x += wave;
|
||||
|
||||
vec4 color = texture2D(texture, uv);
|
||||
gl_FragColor = color * gl_Color;
|
||||
}
|
||||
''', dynamic=True)
|
||||
|
||||
frame.shader = wave_shader
|
||||
self.ui.append(frame)
|
||||
self.shader_frames.append(frame)
|
||||
self.shaders.append(wave_shader)
|
||||
|
||||
def _create_color_shift_shader(self, x, y):
|
||||
"""Create chromatic aberration / color shift shader demo."""
|
||||
# Label
|
||||
label = mcrfpy.Caption(
|
||||
text="4. Color Shift",
|
||||
pos=(x + 200, y - 20),
|
||||
font_size=16,
|
||||
fill_color=mcrfpy.Color(150, 150, 150)
|
||||
)
|
||||
self.ui.append(label)
|
||||
|
||||
# Frame
|
||||
frame = mcrfpy.Frame(
|
||||
pos=(x, y),
|
||||
size=(420, 280),
|
||||
fill_color=mcrfpy.Color(80, 80, 80),
|
||||
outline_color=mcrfpy.Color(150, 150, 150),
|
||||
outline=2
|
||||
)
|
||||
|
||||
content = mcrfpy.Caption(
|
||||
text="Chromatic aberration\nRGB channel offset",
|
||||
pos=(210, 100),
|
||||
font_size=18,
|
||||
fill_color=mcrfpy.Color(255, 255, 255)
|
||||
)
|
||||
frame.children.append(content)
|
||||
|
||||
# Create color shift shader
|
||||
colorshift_shader = mcrfpy.Shader('''
|
||||
uniform sampler2D texture;
|
||||
uniform float time;
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_TexCoord[0].xy;
|
||||
|
||||
// Offset for each channel
|
||||
float offset = 0.005 + 0.003 * sin(time);
|
||||
|
||||
// Sample each channel with slight offset
|
||||
float r = texture2D(texture, uv + vec2(offset, 0.0)).r;
|
||||
float g = texture2D(texture, uv).g;
|
||||
float b = texture2D(texture, uv - vec2(offset, 0.0)).b;
|
||||
float a = texture2D(texture, uv).a;
|
||||
|
||||
gl_FragColor = vec4(r, g, b, a) * gl_Color;
|
||||
}
|
||||
''', dynamic=True)
|
||||
|
||||
frame.shader = colorshift_shader
|
||||
self.ui.append(frame)
|
||||
self.shader_frames.append(frame)
|
||||
self.shaders.append(colorshift_shader)
|
||||
|
||||
def toggle_shaders(self):
|
||||
"""Toggle all shaders on/off."""
|
||||
self.shaders_enabled = not self.shaders_enabled
|
||||
|
||||
for frame, shader in zip(self.shader_frames, self.shaders):
|
||||
if self.shaders_enabled:
|
||||
frame.shader = shader
|
||||
self.status.text = "Shaders: Enabled"
|
||||
self.status.fill_color = mcrfpy.Color(100, 200, 100)
|
||||
else:
|
||||
frame.shader = None
|
||||
self.status.text = "Shaders: Disabled"
|
||||
self.status.fill_color = mcrfpy.Color(200, 100, 100)
|
||||
|
||||
def on_key(self, key, state):
|
||||
"""Handle keyboard input."""
|
||||
if state != "start":
|
||||
return
|
||||
|
||||
if key == "Escape":
|
||||
sys.exit(0)
|
||||
elif key == "Space":
|
||||
self.toggle_shaders()
|
||||
elif key == "R":
|
||||
# Re-enable all shaders
|
||||
self.shaders_enabled = False
|
||||
self.toggle_shaders()
|
||||
elif key in ("Num1", "Num2", "Num3", "Num4"):
|
||||
# Focus on specific shader (could zoom in)
|
||||
idx = int(key[-1]) - 1
|
||||
if idx < len(self.shader_frames):
|
||||
self.status.text = f"Focused: Shader {idx + 1}"
|
||||
|
||||
def activate(self):
|
||||
"""Activate the demo scene."""
|
||||
self.scene.on_key = self.on_key
|
||||
mcrfpy.current_scene = self.scene
|
||||
|
||||
|
||||
def main():
|
||||
"""Run the shader demo."""
|
||||
demo = ShaderDemo()
|
||||
demo.activate()
|
||||
|
||||
# Headless mode: capture screenshot and exit
|
||||
try:
|
||||
if mcrfpy.headless_mode():
|
||||
from mcrfpy import automation
|
||||
mcrfpy.Timer("screenshot", lambda rt: (
|
||||
automation.screenshot("screenshots/features/shader_demo.png"),
|
||||
sys.exit(0)
|
||||
), 200)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue