"""
filters_demo.py - Comprehensive Filter System Demo for LunaEngine
ENGINE PATH:
lunaengine -> examples -> filters_demo.py
DESCRIPTION:
This demo showcases all 19 available filters in the LunaEngine OpenGL renderer.
Users can:
1. Apply individual filters with configurable parameters
2. Combine multiple filters
3. Test different region types (fullscreen, rectangle, circle)
4. Adjust filter intensity and feather effects
5. Save/Load filter presets
6. View real-time performance impact
DEPENDENCIES:
- OpenGLRenderer with Filter system
- FilterType and FilterRegionType enums
- Filter class from backend.opengl
FEATURES DEMONSTRATED:
1. All 19 filter types with live preview
2. Real-time parameter adjustment
3. Region-based filtering (rectangle/circle regions)
4. Multiple filter stacking
5. Performance monitoring
6. Filter preset management
7. Visual feedback for active filters
"""
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from lunaengine.core import LunaEngine, Scene
from lunaengine.ui import *
from lunaengine.backend.opengl import Filter, FilterType, FilterRegionType
from lunaengine.utils.performance import PerformanceMonitor
import pygame
import json
from typing import Dict, List, Tuple, Optional
class FilterDemoScene(Scene):
"""
Comprehensive filter system demo scene.
Shows all available filters in LunaEngine with real-time controls
and visual feedback.
"""
def __init__(self, engine: LunaEngine):
super().__init__(engine)
# Demo state
self.demo_state = {
'active_filters': [], # List of active Filter objects
'selected_filter_type': FilterType.VIGNETTE,
'selected_region_type': FilterRegionType.FULLSCREEN,
'filter_intensity': 0.7,
'filter_radius': 50.0,
'filter_feather': 10.0,
'region_x': 100,
'region_y': 100,
'region_width': 300,
'region_height': 200,
'show_grid': True,
'show_region_outline': True,
'performance_mode': False,
'filter_presets': self.load_default_presets(),
'current_preset': 'Default',
'filter_count': 0,
'animation_enabled': False,
'animation_speed': 1.0,
}
# Animation state
self.animation_time = 0.0
# Initialize UI
self.setup_ui()
# Register key events
@engine.on_event(pygame.KEYDOWN)
def on_key_press(event):
self.handle_key_press(event.key)
def on_enter(self, previous_scene: Optional[str] = None):
"""Called when scene becomes active."""
super().on_enter(previous_scene)
print("=" * 60)
print("FILTER SYSTEM DEMO")
print("=" * 60)
print("\nAvailable Filters (19 total):")
for i, filter_type in enumerate(FilterType, 1):
print(f" {i:2d}. {filter_type.value:20s} - {self.get_filter_description(filter_type)}")
print("\nControls:")
print(" SPACE - Apply/Remove selected filter")
print(" C - Clear all filters")
print(" R - Reset to default view")
print(" G - Toggle grid display")
print(" O - Toggle region outline")
print(" P - Toggle performance mode")
print(" A - Toggle filter animation")
print(" S - Save current filter setup")
print(" L - Load saved preset")
print(" ESC - Return to main menu")
print(" F1-F4 - Quick preset slots (1-4)")
print("\nUse the UI controls to adjust filter parameters.")
print("=" * 60)
# Apply a default filter to start
self.apply_quick_filter(FilterType.VIGNETTE, 0.5)
def on_exit(self, next_scene: Optional[str] = None):
"""Called when scene is being exited."""
super().on_exit(next_scene)
# Clean up all filters
self.clear_all_filters()
print("Filter demo scene cleaned up.")
def get_filter_description(self, filter_type: FilterType) -> str:
"""Get description for a filter type."""
descriptions = {
FilterType.NONE: "No effect",
FilterType.VIGNETTE: "Darkens edges of screen",
FilterType.BLUR: "Gaussian blur effect",
FilterType.SEPIA: "Old photo/warm brown tone",
FilterType.GRAYSCALE: "Black and white conversion",
FilterType.INVERT: "Color inversion",
FilterType.TEMPERATURE_WARM: "Warm orange/yellow tint",
FilterType.TEMPERATURE_COLD: "Cool blue tint",
FilterType.NIGHT_VISION: "Green monochrome with scanlines",
FilterType.CRT: "Old CRT monitor effects",
FilterType.PIXELATE: "Pixelation/low-res effect",
FilterType.BLOOM: "Glow/light bleed effect",
FilterType.EDGE_DETECT: "Edge detection (outlines)",
FilterType.EMBOSS: "3D embossed texture",
FilterType.SHARPEN: "Image sharpening",
FilterType.POSTERIZE: "Reduces color palette",
FilterType.NEON: "Neon glow around edges",
FilterType.RADIAL_BLUR: "Zoom/motion blur from center",
FilterType.FISHEYE: "Fish-eye lens distortion",
FilterType.TWIRL: "Swirling vortex effect",
}
return descriptions.get(filter_type, "Unknown filter")
def load_default_presets(self) -> Dict[str, List[Dict]]:
"""Load default filter presets."""
return {
'Default': [
{'type': FilterType.VIGNETTE.value, 'intensity': 0.5, 'region': 'fullscreen'}
],
'Old Film': [
{'type': FilterType.SEPIA.value, 'intensity': 0.8, 'region': 'fullscreen'},
{'type': FilterType.VIGNETTE.value, 'intensity': 0.3, 'region': 'fullscreen'}
],
'Sci-Fi': [
{'type': FilterType.NIGHT_VISION.value, 'intensity': 0.9, 'region': 'fullscreen'},
{'type': FilterType.CRT.value, 'intensity': 0.4, 'region': 'fullscreen'}
],
'Retro Game': [
{'type': FilterType.PIXELATE.value, 'intensity': 0.6, 'region': 'fullscreen'},
{'type': FilterType.CRT.value, 'intensity': 0.7, 'region': 'fullscreen'}
],
'Dreamy': [
{'type': FilterType.BLOOM.value, 'intensity': 0.5, 'region': 'fullscreen'},
{'type': FilterType.BLUR.value, 'intensity': 0.3, 'region': 'fullscreen'}
],
'Sketch': [
{'type': FilterType.EDGE_DETECT.value, 'intensity': 0.8, 'region': 'fullscreen'},
{'type': FilterType.GRAYSCALE.value, 'intensity': 1.0, 'region': 'fullscreen'}
],
'Warm Sunset': [
{'type': FilterType.TEMPERATURE_WARM.value, 'intensity': 0.7, 'region': 'fullscreen'},
{'type': FilterType.VIGNETTE.value, 'intensity': 0.4, 'region': 'fullscreen'}
],
'Cold Arctic': [
{'type': FilterType.TEMPERATURE_COLD.value, 'intensity': 0.7, 'region': 'fullscreen'},
{'type': FilterType.BLUR.value, 'intensity': 0.2, 'region': 'fullscreen'}
],
}
def setup_ui(self):
"""Setup comprehensive UI for filter controls."""
# Set theme
self.engine.set_global_theme(ThemeType.DEFAULT)
# Title
title = TextLabel(self.engine.width // 2, 20,
"LunaEngine - Filter System Demo",
36, root_point=(0.5, 0))
self.add_ui_element(title)
subtitle = TextLabel(self.engine.width // 2, 60,
"19 Filters • Real-time Controls • Performance Monitoring",
18, (200, 200, 255), root_point=(0.5, 0))
self.add_ui_element(subtitle)
# Create main container with tabs
self.main_tabs = Tabination(20, 100, self.engine.width - 40, self.engine.height - 120, 30)
# Tab 1: Filter Selection & Control
self.main_tabs.add_tab('Filters')
self.setup_filters_tab()
# Tab 2: Region Controls
self.main_tabs.add_tab('Region')
self.setup_region_tab()
# Tab 3: Presets & Performance
self.main_tabs.add_tab('Presets')
self.setup_presets_tab()
# Tab 4: Info & Help
self.main_tabs.add_tab('Info')
self.setup_info_tab()
self.add_ui_element(self.main_tabs)
# Active filters display (always visible)
self.active_filters_label = TextLabel(self.engine.width - 10, 90,
"Active: 0", 16, (100, 255, 100),
root_point=(1, 0))
self.add_ui_element(self.active_filters_label)
# Performance display
self.performance_label = TextLabel(self.engine.width - 10, self.engine.height - 10,
"FPS: --", 16, (255, 200, 100),
root_point=(1, 1))
self.add_ui_element(self.performance_label)
# Quick controls hint
controls_hint = TextLabel(10, self.engine.height - 10,
"SPACE: Apply/Remove | C: Clear | ESC: Menu",
14, (150, 150, 200), root_point=(0, 1))
self.add_ui_element(controls_hint)
def setup_filters_tab(self):
"""Setup filter selection and controls tab."""
# Filter type selection
type_label = TextLabel(20, 20, "Filter Type:", 24, (255, 255, 0))
self.main_tabs.add_to_tab('Filters', type_label)
# Create dropdown with all filter types
filter_names = [ft.value.replace('_', ' ').title() for ft in FilterType]
self.filter_dropdown = Dropdown(150, 15, 250, 35, filter_names)
self.filter_dropdown.set_on_selection_changed(self.on_filter_selected)
self.main_tabs.add_to_tab('Filters', self.filter_dropdown)
# Filter description
self.filter_description = TextLabel(20, 65, "", 16, (200, 200, 255))
self.main_tabs.add_to_tab('Filters', self.filter_description)
# Intensity control
intensity_label = TextLabel(20, 110, "Intensity:", 20, (200, 200, 255))
self.main_tabs.add_to_tab('Filters',intensity_label)
self.intensity_slider = Slider(120, 105, 200, 30, 0.0, 1.0, 0.7)
self.intensity_slider.on_value_changed = self.on_intensity_changed
self.main_tabs.add_to_tab('Filters', self.intensity_slider)
self.intensity_value = TextLabel(330, 110, "0.70", 18)
self.main_tabs.add_to_tab('Filters', self.intensity_value)
# Feather control (for region edges)
feather_label = TextLabel(20, 160, "Feather (edge softness):", 20, (200, 200, 255))
self.main_tabs.add_to_tab('Filters', feather_label)
self.feather_slider = Slider(220, 155, 200, 30, 0.0, 100.0, 10.0)
self.feather_slider.on_value_changed = self.on_feather_changed
self.main_tabs.add_to_tab('Filters', self.feather_slider)
self.feather_value = TextLabel(430, 160, "10.0", 18)
self.main_tabs.add_to_tab('Filters', self.feather_value)
# Animation controls
anim_label = TextLabel(20, 210, "Animation:", 20, (200, 200, 255))
self.main_tabs.add_to_tab('Filters', anim_label)
self.anim_toggle = Switch(120, 205, 60, 30, False)
self.anim_toggle.set_on_toggle(self.on_animation_toggled)
self.main_tabs.add_to_tab('Filters', self.anim_toggle)
anim_state_label = TextLabel(190, 210, "OFF", 18, (200, 150, 150))
self.main_tabs.add_to_tab('Filters', anim_state_label)
self.anim_state_label = anim_state_label
self.anim_speed_slider = Slider(250, 205, 150, 30, 0.1, 3.0, 1.0)
self.anim_speed_slider.on_value_changed = self.on_animation_speed_changed
self.main_tabs.add_to_tab('Filters', self.anim_speed_slider)
self.anim_speed_value = TextLabel(410, 210, "1.0x", 18)
self.main_tabs.add_to_tab('Filters', self.anim_speed_value)
# Control buttons
y_buttons = 270
# Apply/Remove button
self.apply_button = Button(20, y_buttons, 180, 40, "Apply Filter")
self.apply_button.set_on_click(self.toggle_current_filter)
self.main_tabs.add_to_tab('Filters', self.apply_button)
# Clear all button
clear_button = Button(210, y_buttons, 180, 40, "Clear All")
clear_button.set_on_click(self.clear_all_filters)
self.main_tabs.add_to_tab('Filters', clear_button)
# Reset button
reset_button = Button(400, y_buttons, 180, 40, "Reset View")
reset_button.set_on_click(self.reset_view)
self.main_tabs.add_to_tab('Filters', reset_button)
# Active filters list
list_label = TextLabel(20, 330, "Active Filters:", 22, (255, 255, 0))
self.main_tabs.add_to_tab('Filters', list_label)
# Scrollable list of active filters
self.active_list_frame = ScrollingFrame(20, 360, 560, 200, 540, 300)
self.main_tabs.add_to_tab('Filters', self.active_list_frame)
# Update initial description
self.update_filter_description()
def setup_region_tab(self):
"""Setup region controls tab."""
# Region type selection
region_label = TextLabel(20, 20, "Region Type:", 24, (255, 255, 0))
self.main_tabs.add_to_tab('Region', region_label)
region_types = [rt.value.replace('_', ' ').title() for rt in FilterRegionType]
self.region_dropdown = Dropdown(150, 15, 200, 35, region_types)
self.region_dropdown.set_on_selection_changed(self.on_region_selected)
self.main_tabs.add_to_tab('Region', self.region_dropdown)
# Region position controls
pos_label = TextLabel(20, 80, "Region Position & Size:", 22, (200, 200, 255))
self.main_tabs.add_to_tab('Region', pos_label)
# X position
x_label = TextLabel(20, 120, "X:", 18)
self.main_tabs.add_to_tab('Region', x_label)
self.x_slider = Slider(50, 115, 200, 25, 0, self.engine.width, 100)
self.x_slider.on_value_changed = self.on_region_x_changed
self.main_tabs.add_to_tab('Region', self.x_slider)
self.x_value = TextLabel(260, 120, "100", 16)
self.main_tabs.add_to_tab('Region', self.x_value)
# Y position
y_label = TextLabel(20, 160, "Y:", 18)
self.main_tabs.add_to_tab('Region', y_label)
self.y_slider = Slider(50, 155, 200, 25, 0, self.engine.height, 100)
self.y_slider.on_value_changed = self.on_region_y_changed
self.main_tabs.add_to_tab('Region', self.y_slider)
self.y_value = TextLabel(260, 160, "100", 16)
self.main_tabs.add_to_tab('Region', self.y_value)
# Width
width_label = TextLabel(20, 200, "Width:", 18)
self.main_tabs.add_to_tab('Region', width_label)
self.width_slider = Slider(80, 195, 200, 25, 10, 500, 300)
self.width_slider.on_value_changed = self.on_region_width_changed
self.main_tabs.add_to_tab('Region', self.width_slider)
self.width_value = TextLabel(290, 200, "300", 16)
self.main_tabs.add_to_tab('Region', self.width_value)
# Height
height_label = TextLabel(20, 240, "Height:", 18)
self.main_tabs.add_to_tab('Region', height_label)
self.height_slider = Slider(80, 235, 200, 25, 10, 400, 200)
self.height_slider.on_value_changed = self.on_region_height_changed
self.main_tabs.add_to_tab('Region', self.height_slider)
self.height_value = TextLabel(290, 240, "200", 16)
self.main_tabs.add_to_tab('Region', self.height_value)
# Radius control (for circular regions)
radius_label = TextLabel(20, 280, "Radius (%):", 18)
self.main_tabs.add_to_tab('Region', radius_label)
self.radius_slider = Slider(120, 275, 200, 25, 10, 100, 50)
self.radius_slider.on_value_changed = self.on_radius_changed
self.main_tabs.add_to_tab('Region', self.radius_slider)
self.radius_value = TextLabel(330, 280, "50%", 16)
self.main_tabs.add_to_tab('Region', self.radius_value)
# Visual feedback toggle
visual_label = TextLabel(20, 320, "Visual Feedback:", 22, (200, 200, 255))
self.main_tabs.add_to_tab('Region', visual_label)
self.grid_toggle = Switch(20, 350, 60, 30, True)
self.grid_toggle.set_on_toggle(lambda s: self.toggle_setting('show_grid', s))
self.main_tabs.add_to_tab('Region', self.grid_toggle)
grid_label = TextLabel(90, 355, "Show Grid", 18)
self.main_tabs.add_to_tab('Region', grid_label)
self.outline_toggle = Switch(20, 390, 60, 30, True)
self.outline_toggle.set_on_toggle(lambda s: self.toggle_setting('show_region_outline', s))
self.main_tabs.add_to_tab('Region', self.outline_toggle)
outline_label = TextLabel(90, 395, "Show Region Outline", 18)
self.main_tabs.add_to_tab('Region', outline_label)
# Quick region buttons
region_buttons_y = 440
fullscreen_btn = Button(20, region_buttons_y, 120, 35, "Fullscreen")
fullscreen_btn.set_on_click(lambda: self.set_region_type(FilterRegionType.FULLSCREEN))
self.main_tabs.add_to_tab('Region', fullscreen_btn)
center_btn = Button(150, region_buttons_y, 120, 35, "Center")
center_btn.set_on_click(self.center_region)
self.main_tabs.add_to_tab('Region', center_btn)
quarter_btn = Button(280, region_buttons_y, 120, 35, "Quarter")
quarter_btn.set_on_click(self.set_quarter_region)
self.main_tabs.add_to_tab('Region', quarter_btn)
# Update initial values
self.update_region_controls()
def setup_presets_tab(self):
"""Setup presets and performance tab."""
# Preset selection
preset_label = TextLabel(20, 20, "Filter Presets:", 24, (255, 255, 0))
self.main_tabs.add_to_tab('Presets', preset_label)
preset_names = list(self.demo_state['filter_presets'].keys())
self.preset_dropdown = Dropdown(150, 15, 200, 35, preset_names)
self.preset_dropdown.set_on_selection_changed(self.on_preset_selected)
self.main_tabs.add_to_tab('Presets', self.preset_dropdown)
# Load preset button
load_btn = Button(360, 15, 100, 35, "Load")
load_btn.set_on_click(self.load_selected_preset)
self.main_tabs.add_to_tab('Presets', load_btn)
# Save current button
save_btn = Button(470, 15, 100, 35, "Save As...")
save_btn.set_on_click(self.save_current_preset)
self.main_tabs.add_to_tab('Presets', save_btn)
# Preset description
self.preset_description = TextLabel(20, 70, "", 16, (200, 200, 255))
self.main_tabs.add_to_tab('Presets', self.preset_description)
# Quick preset slots
quick_label = TextLabel(20, 110, "Quick Slots (F1-F4):", 22, (200, 200, 255))
self.main_tabs.add_to_tab('Presets', quick_label)
quick_y = 140
for i in range(4):
slot_btn = Button(20 + (i * 150), quick_y, 140, 35, f"Slot {i+1}: Empty")
slot_btn.set_on_click(lambda idx=i: self.load_quick_preset(idx))
self.main_tabs.add_to_tab('Presets', slot_btn)
# Store reference
if not hasattr(self, 'quick_slot_buttons'):
self.quick_slot_buttons = []
self.quick_slot_buttons.append(slot_btn)
# Performance section
perf_label = TextLabel(20, 200, "Performance:", 24, (255, 255, 0))
self.main_tabs.add_to_tab('Presets', perf_label)
self.perf_toggle = Switch(20, 230, 60, 30, False)
self.perf_toggle.set_on_toggle(self.on_performance_toggled)
self.main_tabs.add_to_tab('Presets', self.perf_toggle)
perf_state_label = TextLabel(90, 235, "Performance Mode: OFF", 18)
self.main_tabs.add_to_tab('Presets', perf_state_label)
self.perf_state_label = perf_state_label
# Performance stats
self.perf_stats = TextLabel(20, 280, "", 16, (200, 255, 200))
self.main_tabs.add_to_tab('Presets', self.perf_stats)
# Filter count impact
self.filter_impact = TextLabel(20, 310, "", 16, (255, 200, 200))
self.main_tabs.add_to_tab('Presets', self.filter_impact)
# Update preset description
self.update_preset_description()
def setup_info_tab(self):
"""Setup information and help tab."""
# Demo info
info_lines = [
"FILTER SYSTEM DEMO",
"",
"This demo showcases the 19 filters available",
"in LunaEngine's OpenGL renderer.",
"",
"Key Features:",
"• Real-time filter application & removal",
"• Adjustable intensity and parameters",
"• Region-based filtering (rectangle/circle)",
"• Multiple filter stacking",
"• Performance monitoring",
"• Filter preset saving/loading",
"• Visual feedback for active regions",
"",
"Filter Types:",
"1. Vignette - Darkens screen edges",
"2. Blur - Gaussian blur effect",
"3. Sepia - Old photo/warm tone",
"4. Grayscale - Black & white",
"5. Invert - Color inversion",
"6. Temperature Warm - Warm orange tint",
"7. Temperature Cold - Cool blue tint",
"8. Night Vision - Green with scanlines",
"9. CRT - Old monitor effects",
"10. Pixelate - Low-res pixelation",
"11. Bloom - Glow/light bleed",
"12. Edge Detect - Edge outlines",
"13. Emboss - 3D textured effect",
"14. Sharpen - Image sharpening",
"15. Posterize - Reduced colors",
"16. Neon - Neon glow on edges",
"17. Radial Blur - Zoom/motion blur",
"18. Fisheye - Lens distortion",
"19. Twirl - Swirling vortex",
"",
"Performance Tips:",
"• Fewer filters = better performance",
"• Smaller regions = less GPU load",
"• Complex filters (blur, bloom) are heavier",
"• Enable Performance Mode to reduce overhead",
"",
"Use the UI controls or keyboard shortcuts!"
]
y_pos = 20
for line in info_lines:
if line.startswith("FILTER SYSTEM") or line.startswith("Key Features") or line.startswith("Filter Types") or line.startswith("Performance Tips"):
# Section headers
label = TextLabel(20, y_pos, line, 20, (255, 255, 0))
y_pos += 30
elif line == "":
# Empty line
y_pos += 10
elif line[0].isdigit() and ". " in line:
# Numbered list item
label = TextLabel(40, y_pos, line, 16, (200, 200, 255))
y_pos += 25
else:
# Regular text
label = TextLabel(20, y_pos, line, 16, (200, 200, 255))
y_pos += 25
self.main_tabs.add_to_tab('Info', label)
# Keyboard shortcuts
shortcuts = [
"",
"Keyboard Shortcuts:",
"SPACE - Toggle selected filter",
"C - Clear all filters",
"R - Reset to default view",
"G - Toggle grid display",
"O - Toggle region outline",
"P - Toggle performance mode",
"A - Toggle filter animation",
"S - Save current setup",
"L - Load saved preset",
"ESC - Return to menu",
"F1-F4 - Quick preset slots",
]
y_pos += 20
for line in shortcuts:
if line == "":
y_pos += 10
elif line == "Keyboard Shortcuts:":
label = TextLabel(20, y_pos, line, 22, (255, 200, 100))
y_pos += 30
else:
label = TextLabel(40, y_pos, line, 18, (200, 255, 200))
y_pos += 28
if line:
self.main_tabs.add_to_tab('Info', label)
# ============================================================================
# FILTER MANAGEMENT METHODS
# ============================================================================
def create_filter_from_current_settings(self) -> Filter:
"""Create a Filter object from current UI settings."""
filter_obj = Filter(
filter_type=self.demo_state['selected_filter_type'],
intensity=self.demo_state['filter_intensity'],
region_type=self.demo_state['selected_region_type'],
region_pos=(self.demo_state['region_x'], self.demo_state['region_y']),
region_size=(self.demo_state['region_width'], self.demo_state['region_height']),
radius=self.demo_state['filter_radius'],
feather=self.demo_state['filter_feather'],
blend_mode="normal"
)
return filter_obj
def apply_filter(self, filter_obj: Filter):
"""Apply a filter to the renderer."""
if self.engine.renderer and hasattr(self.engine.renderer, 'add_filter'):
self.engine.renderer.add_filter(filter_obj)
self.demo_state['active_filters'].append(filter_obj)
self.demo_state['filter_count'] = len(self.demo_state['active_filters'])
print(f"Applied filter: {filter_obj.filter_type.value}")
self.update_active_filters_list()
self.update_active_filters_label()
def remove_filter(self, filter_obj: Filter):
"""Remove a filter from the renderer."""
if self.engine.renderer and hasattr(self.engine.renderer, 'remove_filter'):
self.engine.renderer.remove_filter(filter_obj)
if filter_obj in self.demo_state['active_filters']:
self.demo_state['active_filters'].remove(filter_obj)
self.demo_state['filter_count'] = len(self.demo_state['active_filters'])
print(f"Removed filter: {filter_obj.filter_type.value}")
self.update_active_filters_list()
self.update_active_filters_label()
def clear_all_filters(self):
"""Remove all filters from the renderer."""
if self.engine.renderer and hasattr(self.engine.renderer, 'clear_filters'):
self.engine.renderer.clear_filters()
self.demo_state['active_filters'].clear()
self.demo_state['filter_count'] = 0
print("Cleared all filters")
self.update_active_filters_list()
self.update_active_filters_label()
def is_filter_active(self, filter_type: FilterType) -> bool:
"""Check if a filter type is currently active."""
return any(f.filter_type == filter_type for f in self.demo_state['active_filters'])
def toggle_current_filter(self):
"""Toggle the currently selected filter on/off."""
filter_type = self.demo_state['selected_filter_type']
# Check if this filter type is already active
active_filters = [f for f in self.demo_state['active_filters']
if f.filter_type == filter_type]
if active_filters:
# Remove all instances of this filter type
for filter_obj in active_filters[:]: # Copy list for safe removal
self.remove_filter(filter_obj)
self.apply_button.set_text("Apply Filter")
else:
# Apply new filter
filter_obj = self.create_filter_from_current_settings()
self.apply_filter(filter_obj)
self.apply_button.set_text("Remove Filter")
def apply_quick_filter(self, filter_type: FilterType, intensity: float = 0.5):
"""Apply a filter with default settings."""
# Clear existing filters of this type first
existing = [f for f in self.demo_state['active_filters']
if f.filter_type == filter_type]
for f in existing:
self.remove_filter(f)
# Create and apply new filter
filter_obj = Filter(
filter_type=filter_type,
intensity=intensity,
region_type=FilterRegionType.FULLSCREEN,
region_pos=(0, 0),
region_size=(self.engine.width, self.engine.height),
radius=50.0,
feather=10.0
)
self.apply_filter(filter_obj)
# Update UI to match
self.demo_state['selected_filter_type'] = filter_type
self.demo_state['filter_intensity'] = intensity
filter_index = list(FilterType).index(filter_type)
self.filter_dropdown.set_selected_index(filter_index)
self.intensity_slider.set_value(intensity)
self.intensity_value.set_text(f"{intensity:.2f}")
self.apply_button.set_text("Remove Filter")
self.update_filter_description()
# ============================================================================
# REGION MANAGEMENT
# ============================================================================
def set_region_type(self, region_type: FilterRegionType):
"""Set the current region type."""
self.demo_state['selected_region_type'] = region_type
# Update dropdown
region_index = list(FilterRegionType).index(region_type)
self.region_dropdown.set_selected_index(region_index)
# Update active filters with new region type
self.update_active_filters_regions()
def center_region(self):
"""Center the region on screen."""
center_x = self.engine.width // 2 - self.demo_state['region_width'] // 2
center_y = self.engine.height // 2 - self.demo_state['region_height'] // 2
self.demo_state['region_x'] = center_x
self.demo_state['region_y'] = center_y
self.x_slider.set_value(center_x)
self.y_slider.set_value(center_y)
self.x_value.set_text(str(center_x))
self.y_value.set_text(str(center_y))
self.update_active_filters_regions()
def set_quarter_region(self):
"""Set region to quarter of screen."""
quarter_w = self.engine.width // 2
quarter_h = self.engine.height // 2
quarter_x = self.engine.width // 4
quarter_y = self.engine.height // 4
self.demo_state['region_width'] = quarter_w
self.demo_state['region_height'] = quarter_h
self.demo_state['region_x'] = quarter_x
self.demo_state['region_y'] = quarter_y
self.width_slider.set_value(quarter_w)
self.height_slider.set_value(quarter_h)
self.x_slider.set_value(quarter_x)
self.y_slider.set_value(quarter_y)
self.width_value.set_text(str(quarter_w))
self.height_value.set_text(str(quarter_h))
self.x_value.set_text(str(quarter_x))
self.y_value.set_text(str(quarter_y))
self.update_active_filters_regions()
def update_active_filters_regions(self):
"""Update region settings for all active filters."""
for filter_obj in self.demo_state['active_filters']:
filter_obj.region_type = self.demo_state['selected_region_type']
filter_obj.region_pos = (self.demo_state['region_x'], self.demo_state['region_y'])
filter_obj.region_size = (self.demo_state['region_width'], self.demo_state['region_height'])
filter_obj.radius = self.demo_state['filter_radius']
filter_obj.feather = self.demo_state['filter_feather']
# ============================================================================
# PRESET MANAGEMENT
# ============================================================================
def load_selected_preset(self):
"""Load the selected preset."""
preset_name = self.demo_state['current_preset']
if preset_name in self.demo_state['filter_presets']:
self.load_preset(preset_name)
def load_preset(self, preset_name: str):
"""Load a specific preset."""
print(f"Loading preset: {preset_name}")
# Clear existing filters
self.clear_all_filters()
# Apply filters from preset
if preset_name in self.demo_state['filter_presets']:
preset_data = self.demo_state['filter_presets'][preset_name]
for filter_data in preset_data:
try:
filter_type = FilterType(filter_data['type'])
intensity = filter_data.get('intensity', 0.7)
region_type_str = filter_data.get('region', 'fullscreen')
# Convert region string to enum
if region_type_str == 'fullscreen':
region_type = FilterRegionType.FULLSCREEN
region_pos = (0, 0)
region_size = (self.engine.width, self.engine.height)
elif region_type_str == 'rectangle':
region_type = FilterRegionType.RECTANGLE
region_pos = (100, 100)
region_size = (300, 200)
else: # circle or default to fullscreen
region_type = FilterRegionType.FULLSCREEN
region_pos = (0, 0)
region_size = (self.engine.width, self.engine.height)
filter_obj = Filter(
filter_type=filter_type,
intensity=intensity,
region_type=region_type,
region_pos=region_pos,
region_size=region_size,
radius=50.0,
feather=10.0
)
self.apply_filter(filter_obj)
except (KeyError, ValueError) as e:
print(f"Error loading filter from preset: {e}")
self.demo_state['current_preset'] = preset_name
self.update_preset_description()
def save_current_preset(self):
"""Save current filter setup as a new preset."""
# In a real implementation, you'd prompt for a name
# For this demo, we'll create an auto-named preset
preset_count = len([k for k in self.demo_state['filter_presets'].keys()
if k.startswith('Custom ')])
preset_name = f"Custom {preset_count + 1}"
# Convert current filters to preset data
preset_data = []
for filter_obj in self.demo_state['active_filters']:
filter_data = {
'type': filter_obj.filter_type.value,
'intensity': filter_obj.intensity,
'region': filter_obj.region_type.value,
'radius': filter_obj.radius,
'feather': filter_obj.feather
}
preset_data.append(filter_data)
# Save the preset
self.demo_state['filter_presets'][preset_name] = preset_data
self.demo_state['current_preset'] = preset_name
# Update UI
preset_names = list(self.demo_state['filter_presets'].keys())
self.preset_dropdown.set_options(preset_names)
self.preset_dropdown.set_selected_index(preset_names.index(preset_name))
self.update_preset_description()
print(f"Saved preset: {preset_name} with {len(preset_data)} filters")
def load_quick_preset(self, slot_index: int):
"""Load a quick preset from a slot."""
# In a real implementation, you'd have actual slot storage
# For this demo, we'll use some predefined quick presets
quick_presets = {
0: 'Old Film',
1: 'Sci-Fi',
2: 'Retro Game',
3: 'Dreamy'
}
if slot_index in quick_presets:
preset_name = quick_presets[slot_index]
self.load_preset(preset_name)
print(f"Loaded quick preset {slot_index + 1}: {preset_name}")
else:
print(f"No quick preset in slot {slot_index + 1}")
# ============================================================================
# UI UPDATE METHODS
# ============================================================================
def update_filter_description(self):
"""Update the filter description label."""
filter_type = self.demo_state['selected_filter_type']
description = self.get_filter_description(filter_type)
is_active = self.is_filter_active(filter_type)
status = " (ACTIVE)" if is_active else " (INACTIVE)"
self.filter_description.set_text(f"{description}{status}")
def update_preset_description(self):
"""Update the preset description label."""
preset_name = self.demo_state['current_preset']
if preset_name in self.demo_state['filter_presets']:
filter_count = len(self.demo_state['filter_presets'][preset_name])
self.preset_description.set_text(
f"{preset_name}: {filter_count} filter{'s' if filter_count != 1 else ''}"
)
else:
self.preset_description.set_text("No preset selected")
def update_active_filters_list(self):
"""Update the scrollable list of active filters."""
# Clear existing items
self.active_list_frame.clear_children()
# Add current active filters
for i, filter_obj in enumerate(self.demo_state['active_filters']):
y_pos = i * 30
# Filter name and intensity
filter_text = f"{i+1}. {filter_obj.filter_type.value}: {filter_obj.intensity:.2f}"
filter_label = TextLabel(10, y_pos + 5, filter_text, 16)
self.active_list_frame.add_child(filter_label)
# Remove button
remove_btn = Button(400, y_pos, 60, 25, "Remove")
remove_btn.set_on_click(lambda f=filter_obj: self.remove_filter(f))
self.active_list_frame.add_child(remove_btn)
# Intensity slider for this filter
intensity_slider = Slider(470, y_pos, 80, 25, 0.0, 1.0, filter_obj.intensity)
intensity_slider.on_value_changed = lambda v, f=filter_obj: self.update_filter_intensity(f, v)
self.active_list_frame.add_child(intensity_slider)
def update_active_filters_label(self):
"""Update the active filters counter."""
count = self.demo_state['filter_count']
color = (100, 255, 100) if count <= 3 else (255, 200, 100) if count <= 6 else (255, 100, 100)
self.active_filters_label.set_text(f"Active: {count}")
self.active_filters_label.set_text_color(color)
def update_region_controls(self):
"""Update region control values in UI."""
self.x_value.set_text(str(self.demo_state['region_x']))
self.y_value.set_text(str(self.demo_state['region_y']))
self.width_value.set_text(str(self.demo_state['region_width']))
self.height_value.set_text(str(self.demo_state['region_height']))
self.radius_value.set_text(f"{self.demo_state['filter_radius']}%")
def update_filter_intensity(self, filter_obj: Filter, intensity: float):
"""Update intensity of a specific filter."""
filter_obj.intensity = max(0.0, min(1.0, intensity))
print(f"Updated {filter_obj.filter_type.value} intensity to {intensity:.2f}")
def update_performance_display(self):
"""Update performance statistics display."""
if not self.demo_state['performance_mode']:
self.perf_stats.set_text("Performance mode disabled")
return
# Get FPS stats
fps_stats = self.engine.get_fps_stats()
# Calculate filter performance impact
filter_count = self.demo_state['filter_count']
base_fps = 60 # Assuming 60 FPS baseline with no filters
# Simple heuristic: each filter reduces FPS by 2-10% depending on type
# This is just for demonstration
performance_text = (
f"FPS: {fps_stats['current_fps']:.1f} (Target: {self.engine.fps})\n"
f"Frame Time: {fps_stats['frame_time_ms']:.2f} ms\n"
f"Active Filters: {filter_count}\n"
f"1% Low: {fps_stats['percentile_1']:.1f} FPS"
)
self.perf_stats.set_text(performance_text)
# Update filter impact warning
if filter_count == 0:
self.filter_impact.set_text("No filters - Maximum performance")
self.filter_impact.set_text_color((100, 255, 100))
elif filter_count <= 3:
self.filter_impact.set_text("Light filter load - Good performance")
self.filter_impact.set_text_color((200, 255, 100))
elif filter_count <= 6:
self.filter_impact.set_text("Medium filter load - Acceptable performance")
self.filter_impact.set_text_color((255, 200, 100))
else:
self.filter_impact.set_text("Heavy filter load - Reduced performance")
self.filter_impact.set_text_color((255, 100, 100))
def reset_view(self):
"""Reset to default view."""
self.clear_all_filters()
# Reset region to center
self.center_region()
# Reset selected filter to vignette
self.demo_state['selected_filter_type'] = FilterType.VIGNETTE
self.filter_dropdown.set_selected_index(0)
# Reset intensity
self.demo_state['filter_intensity'] = 0.5
self.intensity_slider.set_value(0.5)
self.intensity_value.set_text("0.50")
# Update UI
self.update_filter_description()
self.apply_button.set_text("Apply Filter")
print("View reset to default")
# ============================================================================
# EVENT HANDLERS
# ============================================================================
def on_filter_selected(self, index: int, value: str):
"""Handle filter type selection."""
# Convert string back to FilterType enum
filter_name = value.lower().replace(' ', '_')
try:
filter_type = FilterType(filter_name)
self.demo_state['selected_filter_type'] = filter_type
# Update description and button text
self.update_filter_description()
# Update apply button text based on whether filter is active
if self.is_filter_active(filter_type):
self.apply_button.set_text("Remove Filter")
else:
self.apply_button.set_text("Apply Filter")
except ValueError:
print(f"Invalid filter type: {filter_name}")
def on_region_selected(self, index: int, value: str):
"""Handle region type selection."""
region_name = value.lower().replace(' ', '_')
try:
region_type = FilterRegionType(region_name)
self.set_region_type(region_type)
except ValueError:
print(f"Invalid region type: {region_name}")
def on_intensity_changed(self, value: float):
"""Handle intensity slider change."""
self.demo_state['filter_intensity'] = value
self.intensity_value.set_text(f"{value:.2f}")
# Update intensity of all active filters of selected type
filter_type = self.demo_state['selected_filter_type']
for filter_obj in self.demo_state['active_filters']:
if filter_obj.filter_type == filter_type:
filter_obj.intensity = value
def on_feather_changed(self, value: float):
"""Handle feather slider change."""
self.demo_state['filter_feather'] = value
self.feather_value.set_text(f"{value:.1f}")
self.update_active_filters_regions()
def on_region_x_changed(self, value: float):
"""Handle region X position change."""
self.demo_state['region_x'] = int(value)
self.x_value.set_text(str(int(value)))
self.update_active_filters_regions()
def on_region_y_changed(self, value: float):
"""Handle region Y position change."""
self.demo_state['region_y'] = int(value)
self.y_value.set_text(str(int(value)))
self.update_active_filters_regions()
def on_region_width_changed(self, value: float):
"""Handle region width change."""
self.demo_state['region_width'] = int(value)
self.width_value.set_text(str(int(value)))
self.update_active_filters_regions()
def on_region_height_changed(self, value: float):
"""Handle region height change."""
self.demo_state['region_height'] = int(value)
self.height_value.set_text(str(int(value)))
self.update_active_filters_regions()
def on_radius_changed(self, value: float):
"""Handle radius slider change."""
self.demo_state['filter_radius'] = value
self.radius_value.set_text(f"{value}%")
self.update_active_filters_regions()
def on_preset_selected(self, index: int, value: str):
"""Handle preset selection."""
self.demo_state['current_preset'] = value
self.update_preset_description()
def on_performance_toggled(self, enabled: bool):
"""Handle performance mode toggle."""
self.demo_state['performance_mode'] = enabled
state_text = "Performance Mode: ON" if enabled else "Performance Mode: OFF"
self.perf_state_label.set_text(state_text)
if enabled:
print("Performance mode enabled - showing stats")
else:
print("Performance mode disabled")
def on_animation_toggled(self, enabled: bool):
"""Handle animation toggle."""
self.demo_state['animation_enabled'] = enabled
state_text = "ON" if enabled else "OFF"
color = (100, 255, 100) if enabled else (200, 150, 150)
self.anim_state_label.set_text(state_text)
self.anim_state_label.set_text_color(color)
if enabled:
print("Filter animation enabled")
else:
print("Filter animation disabled")
def on_animation_speed_changed(self, value: float):
"""Handle animation speed change."""
self.demo_state['animation_speed'] = value
self.anim_speed_value.set_text(f"{value:.1f}x")
def toggle_setting(self, setting_name: str, value: bool):
"""Toggle a boolean setting."""
self.demo_state[setting_name] = value
print(f"{setting_name.replace('_', ' ').title()}: {'ON' if value else 'OFF'}")
def handle_key_press(self, key):
"""Handle keyboard input."""
if key == pygame.K_ESCAPE:
# Return to main menu
self.engine.set_scene("MainMenu")
elif key == pygame.K_SPACE:
# Toggle current filter
self.toggle_current_filter()
elif key == pygame.K_c:
# Clear all filters
self.clear_all_filters()
elif key == pygame.K_r:
# Reset view
self.reset_view()
elif key == pygame.K_g:
# Toggle grid
self.demo_state['show_grid'] = not self.demo_state['show_grid']
self.grid_toggle.set_value(self.demo_state['show_grid'])
print(f"Grid: {'ON' if self.demo_state['show_grid'] else 'OFF'}")
elif key == pygame.K_o:
# Toggle region outline
self.demo_state['show_region_outline'] = not self.demo_state['show_region_outline']
self.outline_toggle.set_value(self.demo_state['show_region_outline'])
print(f"Region Outline: {'ON' if self.demo_state['show_region_outline'] else 'OFF'}")
elif key == pygame.K_p:
# Toggle performance mode
self.demo_state['performance_mode'] = not self.demo_state['performance_mode']
self.perf_toggle.set_value(self.demo_state['performance_mode'])
self.on_performance_toggled(self.demo_state['performance_mode'])
elif key == pygame.K_a:
# Toggle animation
self.demo_state['animation_enabled'] = not self.demo_state['animation_enabled']
self.anim_toggle.set_value(self.demo_state['animation_enabled'])
self.on_animation_toggled(self.demo_state['animation_enabled'])
elif key == pygame.K_s:
# Save preset
self.save_current_preset()
elif key == pygame.K_l:
# Load preset
self.load_selected_preset()
elif pygame.K_F1 <= key <= pygame.K_F4:
# Quick preset slots
slot_index = key - pygame.K_F1
self.load_quick_preset(slot_index)
# ============================================================================
# UPDATE & RENDER
# ============================================================================
def update(self, dt):
"""Update scene logic."""
# Update animation time if enabled
if self.demo_state['animation_enabled']:
self.animation_time += dt * self.demo_state['animation_speed']
# Animate filter intensities (sin wave between 0.3 and 0.8)
for filter_obj in self.demo_state['active_filters']:
# Different animation for each filter based on its type
anim_offset = hash(filter_obj.filter_type.value) % 100 / 100.0
intensity = 0.5 + 0.3 * math.sin(self.animation_time * 2 + anim_offset * 2 * math.pi)
filter_obj.intensity = max(0.1, min(1.0, intensity))
# Update performance display
self.update_performance_display()
# Update FPS display
fps_stats = self.engine.get_fps_stats()
self.performance_label.set_text(f"FPS: {fps_stats['current_fps']:.1f}")
def render(self, renderer):
"""Render the scene."""
# Get current theme colors
theme = ThemeManager.get_theme(ThemeManager.get_current_theme())
# Draw background with gradient
renderer.fill_screen(theme.background)
# Draw test pattern to see filter effects clearly
self.draw_test_pattern(renderer)
# Draw grid if enabled
if self.demo_state['show_grid']:
self.draw_grid(renderer)
# Draw region outline if enabled and not fullscreen
if (self.demo_state['show_region_outline'] and
self.demo_state['selected_region_type'] != FilterRegionType.FULLSCREEN):
self.draw_region_outline(renderer)
# Draw filter information overlay
self.draw_filter_info(renderer)
def draw_test_pattern(self, renderer):
"""Draw a test pattern to visualize filter effects."""
# Color gradient background
width, height = self.engine.width, self.engine.height
# Draw color bands
colors = [
(255, 0, 0, 200), # Red
(255, 128, 0, 200), # Orange
(255, 255, 0, 200), # Yellow
(0, 255, 0, 200), # Green
(0, 255, 255, 200), # Cyan
(0, 0, 255, 200), # Blue
(128, 0, 255, 200), # Purple
(255, 0, 255, 200) # Magenta
]
band_width = width // len(colors)
for i, color in enumerate(colors):
x = i * band_width
renderer.draw_rect(x, 0, band_width, height, color, fill=True)
# Draw geometric shapes
center_x, center_y = width // 2, height // 2
# Circles
circle_colors = [(255, 255, 255, 150), (0, 0, 0, 150)]
for i, color in enumerate(circle_colors):
radius = 50 + i * 30
renderer.draw_circle(center_x, center_y, radius, color, fill=False, border_width=3)
# Lines
line_color = (255, 255, 255, 180)
renderer.draw_line(0, center_y, width, center_y, line_color, 2)
renderer.draw_line(center_x, 0, center_x, height, line_color, 2)
# Diagonal lines
renderer.draw_line(0, 0, width, height, line_color, 2)
renderer.draw_line(width, 0, 0, height, line_color, 2)
# Text labels for orientation
font = pygame.font.Font(None, 24)
labels = [
("TOP-LEFT", 50, 50),
("TOP-RIGHT", width - 100, 50),
("BOTTOM-LEFT", 50, height - 50),
("BOTTOM-RIGHT", width - 150, height - 50),
("CENTER", center_x - 40, center_y - 15)
]
for text, x, y in labels:
text_surface = font.render(text, True, (255, 255, 255))
renderer.draw_surface(text_surface, x, y)
def draw_grid(self, renderer):
"""Draw a grid overlay."""
width, height = self.engine.width, self.engine.height
grid_color = (100, 100, 100, 80)
grid_size = 50
# Vertical lines
for x in range(0, width, grid_size):
renderer.draw_line(x, 0, x, height, grid_color, 1)
# Horizontal lines
for y in range(0, height, grid_size):
renderer.draw_line(0, y, width, y, grid_color, 1)
# Center lines (thicker)
center_color = (150, 150, 150, 120)
renderer.draw_line(width // 2, 0, width // 2, height, center_color, 2)
renderer.draw_line(0, height // 2, width, height // 2, center_color, 2)
def draw_region_outline(self, renderer):
"""Draw outline of current filter region."""
x, y = self.demo_state['region_x'], self.demo_state['region_y']
width, height = self.demo_state['region_width'], self.demo_state['region_height']
region_type = self.demo_state['selected_region_type']
if region_type == FilterRegionType.RECTANGLE:
# Rectangle outline
outline_color = (255, 255, 0, 200)
renderer.draw_rect(x, y, width, height, outline_color, fill=False, border_width=3)
# Corner markers
marker_color = (255, 200, 0, 255)
marker_size = 10
corners = [(x, y), (x + width, y), (x + width, y + height), (x, y + height)]
for cx, cy in corners:
renderer.draw_rect(cx - marker_size//2, cy - marker_size//2,
marker_size, marker_size, marker_color, fill=True)
elif region_type == FilterRegionType.CIRCLE:
# Circle outline
center_x = x + width // 2
center_y = y + height // 2
radius = min(width, height) // 2 * (self.demo_state['filter_radius'] / 100.0)
outline_color = (0, 255, 255, 200)
renderer.draw_circle(center_x, center_y, int(radius), outline_color, fill=False, border_width=3)
# Center marker
marker_color = (0, 200, 255, 255)
renderer.draw_rect(center_x - 5, center_y - 5, 10, 10, marker_color, fill=True)
def draw_filter_info(self, renderer):
"""Draw filter information overlay."""
width, height = self.engine.width, self.engine.height
# Active filters summary
if self.demo_state['active_filters']:
y_pos = height - 120
# Background panel
renderer.draw_rect(10, y_pos - 10, width - 20, 110, (0, 0, 0, 180), fill=True)
# Title
font_large = pygame.font.Font(None, 24)
title_text = f"Active Filters: {len(self.demo_state['active_filters'])}"
title_surface = font_large.render(title_text, True, (255, 255, 100))
renderer.draw_surface(title_surface, 20, y_pos)
# Filter list
font = pygame.font.Font(None, 18)
for i, filter_obj in enumerate(self.demo_state['active_filters']):
if i < 5: # Show only first 5 to avoid clutter
filter_text = f"{filter_obj.filter_type.value}: {filter_obj.intensity:.2f}"
if filter_obj.region_type != FilterRegionType.FULLSCREEN:
filter_text += f" ({filter_obj.region_type.value})"
text_surface = font.render(filter_text, True, (200, 200, 255))
renderer.draw_surface(text_surface, 40, y_pos + 30 + i * 20)
if len(self.demo_state['active_filters']) > 5:
more_text = f"... and {len(self.demo_state['active_filters']) - 5} more"
text_surface = font.render(more_text, True, (150, 150, 200))
renderer.draw_surface(text_surface, 40, y_pos + 130)
class MainMenuScene(Scene):
"""
Simple main menu for the filter demo.
"""
def __init__(self, engine: LunaEngine):
super().__init__(engine)
# Title
title = TextLabel(self.engine.width // 2, 100,
"LunaEngine Filter System",
48, root_point=(0.5, 0))
self.add_ui_element(title)
subtitle = TextLabel(self.engine.width // 2, 160,
"19 Post-Processing Filters • OpenGL Powered",
24, (200, 200, 255), root_point=(0.5, 0))
self.add_ui_element(subtitle)
# Demo button
demo_btn = Button(self.engine.width // 2, 250, 300, 50,
"Start Filter Demo", 32, root_point=(0.5, 0))
demo_btn.set_on_click(lambda: engine.set_scene("FilterDemo"))
self.add_ui_element(demo_btn)
# Quick filter test buttons
quick_label = TextLabel(self.engine.width // 2, 320,
"Quick Tests:", 28, (255, 255, 0), root_point=(0.5, 0))
self.add_ui_element(quick_label)
# Row 1
vignette_btn = Button(self.engine.width // 2 - 160, 370, 150, 40, "Vignette")
vignette_btn.set_on_click(lambda: self.quick_test(FilterType.VIGNETTE))
self.add_ui_element(vignette_btn)
blur_btn = Button(self.engine.width // 2, 370, 150, 40, "Blur")
blur_btn.set_on_click(lambda: self.quick_test(FilterType.BLUR))
self.add_ui_element(blur_btn)
sepia_btn = Button(self.engine.width // 2 + 160, 370, 150, 40, "Sepia")
sepia_btn.set_on_click(lambda: self.quick_test(FilterType.SEPIA))
self.add_ui_element(sepia_btn)
# Row 2
crt_btn = Button(self.engine.width // 2 - 160, 420, 150, 40, "CRT")
crt_btn.set_on_click(lambda: self.quick_test(FilterType.CRT))
self.add_ui_element(crt_btn)
neon_btn = Button(self.engine.width // 2, 420, 150, 40, "Neon")
neon_btn.set_on_click(lambda: self.quick_test(FilterType.NEON))
self.add_ui_element(neon_btn)
bloom_btn = Button(self.engine.width // 2 + 160, 420, 150, 40, "Bloom")
bloom_btn.set_on_click(lambda: self.quick_test(FilterType.BLOOM))
self.add_ui_element(bloom_btn)
# Exit button
exit_btn = Button(self.engine.width // 2, 500, 200, 40, "Exit", 28, root_point=(0.5, 0))
exit_btn.set_on_click(lambda: setattr(engine, 'running', False))
self.add_ui_element(exit_btn)
# Info text
info_text = TextLabel(self.engine.width // 2, 560,
"Press SPACE in demo to toggle filters • ESC to return here",
16, (150, 200, 255), root_point=(0.5, 0))
self.add_ui_element(info_text)
def quick_test(self, filter_type: FilterType):
"""Quick test a specific filter."""
# Switch to demo scene
self.engine.set_scene("FilterDemo")
# Apply the filter (with a small delay to ensure scene is loaded)
import threading
import time
def apply_after_delay():
time.sleep(0.1) # Small delay for scene transition
scene = self.engine.current_scene
if isinstance(scene, FilterDemoScene):
scene.apply_quick_filter(filter_type)
thread = threading.Thread(target=apply_after_delay, daemon=True)
thread.start()
def on_enter(self, previous_scene: Optional[str] = None):
super().on_enter(previous_scene)
print("\n" + "="*60)
print("MAIN MENU")
print("="*60)
print("Select 'Start Filter Demo' for full controls")
print("Or try a quick filter test button!")
print("="*60)
def update(self, dt):
pass
def render(self, renderer):
# Draw gradient background
colors = [(20, 10, 40), (40, 20, 60), (30, 15, 50)]
height = self.engine.height
for i, color in enumerate(colors):
segment_height = height // len(colors)
y = i * segment_height
renderer.draw_rect(0, y, self.engine.width, segment_height, color, fill=True)
# Draw UI
for element in self.ui_elements:
element.render(renderer)
def main():
"""
Main entry point for the filter system demo.
Creates the engine, registers scenes, and starts the main loop.
"""
# Initialize engine
engine = LunaEngine(
title="LunaEngine - Filter System Demo",
width=1024,
height=768,
fullscreen=False
)
# Set target FPS
engine.fps = 60
# Register scenes
engine.add_scene("MainMenu", MainMenuScene)
engine.add_scene("FilterDemo", FilterDemoScene)
# Start with main menu
engine.set_scene("MainMenu")
# Run the engine
print("\n" + "="*60)
print("Starting LunaEngine Filter System Demo")
print("="*60)
engine.run()
if __name__ == "__main__":
# Add math import for animation
import math
# Run the demo
main()
filters_demo.py - Comprehensive Filter System Demo for LunaEngine