Component: bravura.components.AmbientBackground
Status: ✅ Production Ready
Performance: 🚀 Buttery-smooth 60 FPS with minimal CPU usage
License Tiers: Professional, Enterprise
Last Updated: October 19, 2025
---
The AmbientBackground component now supports instant theme color updates via the set_theme_tokens() method:
# Update theme colors instantly - NO recreation needed!
ambient_bg.set_theme_tokens(theme_manager.get_current_theme_colors())
Benefits:
See Updating on Theme Change for complete details.
---
The Ambient Background component creates dynamic, animated gradient backgrounds with floating glow particles that automatically adapt to your active theme. It provides a professional, modern look while maintaining excellent performance with buttery-smooth 60 FPS animation and optimized CPU usage.
---
import tkinter as tk
from bravura.themes.theme_manager import ProfessionalThemeManager
from bravura.components import AmbientBackground
class MyApp:
def __init__(self):
self.root = tk.Tk()
self.root.title("My App with Ambient Background")
self.root.geometry("800x600")
# Initialize theme manager FIRST
self.theme_manager = ProfessionalThemeManager(self)
self.theme_manager.apply_theme("wigley_site")
# Create ambient background with theme colors
self.ambient_bg = AmbientBackground(
parent_widget=self.root,
width=800,
height=600,
theme_tokens=self.theme_manager.get_current_theme_colors(),
enable_animation=True,
animation_speed="medium",
particle_count=20,
glow_effects=True
)
# Now create your UI widgets
# They will automatically appear above the ambient background
self.create_ui()
def create_ui(self):
frame = ttk.Frame(self.root)
frame.pack(fill="both", expand=True, padx=20, pady=20)
ttk.Label(frame, text="Hello Bravura!", font=("Segoe UI", 24)).pack(pady=20)
ttk.Button(frame, text="Click Me!").pack()
def run(self):
self.root.mainloop()
if __name__ == "__main__":
app = MyApp()
app.run()
---
AmbientBackground(
parent_widget, # Required: Parent tkinter widget
*args,
width=None, # Optional: Width in pixels (auto if None)
height=None, # Optional: Height in pixels (auto if None)
tokens=None, # Optional: Theme tokens (deprecated)
theme_tokens=None, # Recommended: Theme color tokens
enable_animation=True, # Enable/disable particle animation
animation_speed="medium", # "slow", "medium", "fast", or float
particle_count=20, # Number of particles (10-40 recommended)
glow_effects=True, # Enable glow effects
reduced_motion=False, # Accessibility: disable animations
auto_create=True # Automatically create on init
)
The tkinter widget to attach the background to (usually self.root).
Dictionary of theme colors. Get from theme manager:
theme_tokens=self.theme_manager.get_current_theme_colors()
Theme tokens used:
accent_color_1 → Primary particle coloraccent_color_2 → Secondary particle colorteal → Tertiary particle colorbackground_primary → Canvas background colorControls particle drift speed (independent of frame rate for consistent 60 FPS):
"slow" - Gentle, calm drift (0.35x speed)"medium" - Balanced drift (0.6x speed) [Default]"fast" - Quick, energetic drift (1.0x speed)Note: Animation always runs at smooth 60 FPS. This parameter only affects how fast particles drift, not the smoothness of the animation.
Number of animated glow particles:
Performance: Thanks to optimized 60 FPS rendering, even 40 particles run smoothly with minimal CPU impact.
Set to True to disable animations for accessibility. Respects user preferences for motion-sensitive users.
---
The ambient background uses your active theme's colors:
# Wigley Site theme
theme_manager.apply_theme("wigley_site")
ambient_bg = AmbientBackground(
root,
theme_tokens=theme_manager.get_current_theme_colors()
)
# Particles: Teal (#20C6B7), Gold (#F6B645), Cyan (#58D5FF)
# Background: Dark (#0B1115)
# Platinum theme
theme_manager.apply_theme("platinum")
ambient_bg = AmbientBackground(
root,
theme_tokens=theme_manager.get_current_theme_colors()
)
# Particles: Gold (#C5A572), Blue (#6B9BD2), Cyan (#58D5FF)
# Background: Dark gray (#2C2C34)
# White theme
theme_manager.apply_theme("white")
ambient_bg = AmbientBackground(
root,
theme_tokens=theme_manager.get_current_theme_colors()
)
# Particles: Blue (#0078d7), Green (#00AA00), Cyan (#58D5FF)
# Background: White (#ffffff)
New in v1.0.0: Ambient backgrounds now support instant theme updates via the set_theme_tokens() method - no recreation needed!
def change_theme(self, new_theme):
# Apply new theme
self.theme_manager.apply_theme(new_theme)
# Update ambient background colors instantly (FAST, no visual pop)
if hasattr(self, 'ambient_bg') and self.ambient_bg:
self.ambient_bg.set_theme_tokens(
self.theme_manager.get_current_theme_colors()
)
Performance Benefits:
Legacy Approach (Still Supported):
If you need to recreate for other reasons:
def change_theme_with_recreation(self, new_theme):
# Apply new theme
self.theme_manager.apply_theme(new_theme)
# Destroy old ambient background
if hasattr(self, 'ambient_bg') and self.ambient_bg:
self.ambient_bg.destroy()
# Create new ambient background with new theme colors
self.ambient_bg = AmbientBackground(
self.root,
width=800,
height=600,
theme_tokens=self.theme_manager.get_current_theme_colors()
)
Override theme colors with custom palette:
custom_colors = {
"accent_color_1": "#FF0000", # Red particles
"accent_color_2": "#00FF00", # Green particles
"teal": "#0000FF", # Blue particles
"background_primary": "#000000" # Black background
}
ambient_bg = AmbientBackground(
root,
theme_tokens=custom_colors
)
---
Ambient backgrounds render as a full-window canvas that's automatically lowered behind all UI widgets. The background is visible in the margins/padding around your main content:
┌────────────────────────────────┐
│░░ AMBIENT BACKGROUND ░░░░░░░░░░│ ← Visible margins!
│░ ┌──────────────────────────┐ │
│░ │ │ │
│░ │ Main Frame Content │ │
│░ │ (padded by 20px) │ │
│░ │ │ │
│░ └──────────────────────────┘ │
│░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│
└────────────────────────────────┘
↑ Padding creates visible margins
KEY INSIGHT: You must add padding when packing your main frame to create visible margins!
# ❌ BAD: No padding = ambient background hidden
main_frame = ttk.Frame(root)
main_frame.pack(fill="both", expand=True) # Fills entire window!
# ✅ GOOD: Padding creates visible margins
main_frame = ttk.Frame(root)
main_frame.pack(fill="both", expand=True, padx=20, pady=20) # 20px margins!
Result: The 20px padding on all sides creates 40px total visible margin where the ambient background glow is visible!
When adding padding, increase your window size accordingly:
# If you want 20px visible margins:
# Original window: 1200x800
# Add padding: 20px × 2 sides = 40px width, 40px height
# New window size: 1240x840
self.root.geometry("1240x840")
main_frame.pack(fill="both", expand=True, padx=20, pady=20)
Formula: new_size = original_size + (padding × 2)
| Window Size | Recommended Padding | Visible Margin |
|---|---|---|
| < 800px | 15px | 30px total |
| 800-1200px | 20px | 40px total |
| > 1200px | 25px | 50px total |
---
Symptom: Ambient background appears on top of buttons/labels.
Cause: Component automatically handles z-order via background_container.lower(). If you see this, something is interfering.
Solution: Ensure you're not manually calling .lower() or .raise_() on the canvas:
# ❌ DON'T DO THIS
ambient_bg.canvas.lower() # Component already handles this!
# ✅ DO THIS
# Just create the component, it handles z-order automatically
ambient_bg = AmbientBackground(root, theme_tokens=colors)
Symptom: Background appears solid with no particles.
Cause: Either glow_effects=False or theme tokens not provided.
Solution:
# ✅ Enable glow effects and pass theme tokens
ambient_bg = AmbientBackground(
root,
theme_tokens=theme_manager.get_current_theme_colors(), # Required!
glow_effects=True # Required!
)
Symptom: Particles look identical in all themes.
Cause: Not passing theme_tokens or not recreating on theme change.
Solution:
# Always pass theme tokens
ambient_bg = AmbientBackground(
root,
theme_tokens=self.theme_manager.get_current_theme_colors()
)
# Recreate when theme changes (see "Updating on Theme Change" above)
Symptom: Application feels slow, high CPU usage.
Cause: Too many particles or fast animation speed.
Solution:
# Reduce particle count
ambient_bg = AmbientBackground(
root,
theme_tokens=colors,
particle_count=10, # Lower for better performance
animation_speed="slow" # Slower = less CPU
)
Performance Guidelines
Excellent Performance Across All Settings:
Animation Quality:
Symptom: Warning message about lowering background.
Cause: Trying to manually lower the canvas instead of the container.
Solution:
# ❌ DON'T DO THIS
ambient_bg.canvas.lower() # Wrong! Causes error
# ✅ Component handles this automatically via background_container
# No manual lowering needed!
Symptom: Ambient background exists but you can't see it.
Cause: Main frame packed with no padding, filling the entire window.
Solution:
# ❌ DON'T DO THIS
main_frame.pack(fill="both", expand=True) # No visible margins!
# ✅ DO THIS
main_frame.pack(fill="both", expand=True, padx=20, pady=20) # Visible margins!
See "Understanding Ambient Background Visibility" section above for detailed explanation.
---
ambient_bg = AmbientBackground(
root,
theme_tokens=colors,
particle_count=10, # Smooth 60 FPS even on low-end
animation_speed="slow",
glow_effects=True
)
# Still maintains buttery-smooth 60 FPS with minimal CPU usage
ambient_bg = AmbientBackground(
root,
theme_tokens=colors,
particle_count=20, # Default
animation_speed="medium", # Default
glow_effects=True
)
ambient_bg = AmbientBackground(
root,
theme_tokens=colors,
particle_count=40, # Maximum visual richness
animation_speed="medium", # Smooth drift at 60 FPS
glow_effects=True
)
# Ultra-smooth 60 FPS with elegant harmonic easing
# Minimal CPU usage thanks to optimized rendering
import time
# Create ambient background
start_time = time.time()
ambient_bg = AmbientBackground(root, theme_tokens=colors)
# Check performance specs
print(f"Target FPS: 60 (fixed)")
print(f"Frame interval: ~16.67ms")
print(f"Animation: Buttery-smooth with harmonic easing")
# Measure creation time
creation_time = time.time() - start_time
print(f"Creation time: {creation_time:.3f}s")
# Verify smooth animation
print(f"Particle count: {ambient_bg.particle_count}")
print(f"Expected CPU usage: Minimal (optimized canvas rendering)")
---
Respect user preferences for reduced motion:
# Check if user prefers reduced motion (Windows example)
import winreg
def check_reduced_motion_preference():
try:
key = winreg.OpenKey(
winreg.HKEY_CURRENT_USER,
r"Control Panel\Accessibility\DynamicScrollbars"
)
value, _ = winreg.QueryValueEx(key, "DynamicScrollbars")
return value == 0
except:
return False # Default to animations enabled
# Apply preference
reduced_motion = check_reduced_motion_preference()
ambient_bg = AmbientBackground(
root,
theme_tokens=colors,
enable_animation=not reduced_motion,
reduced_motion=reduced_motion
)
BEST PRACTICE: Provide users with a Settings menu toggle for ambient backgrounds:
class MyApp:
def __init__(self):
self.root = tk.Tk()
self.root.geometry("1280x860")
# Theme manager
self.theme_manager = ProfessionalThemeManager(self)
self.theme_manager.apply_theme("wigley_site")
# Create ambient background initially
self.ambient_bg = AmbientBackground(
self.root,
tokens=self.theme_manager.get_current_theme_colors(),
enable_animation=True,
animation_speed="medium",
particle_count=30,
glow_effects=True
)
# Create UI with Settings menu
self.create_menu()
self.create_ui()
def create_menu(self):
"""Create menu bar with Settings option."""
menubar = tk.Menu(self.root)
self.root.config(menu=menubar)
settings_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Settings", menu=settings_menu)
# Add ambient background toggle
self.ambient_enabled_var = tk.BooleanVar(value=True)
settings_menu.add_checkbutton(
label="Enable Ambient Background",
variable=self.ambient_enabled_var,
command=self.toggle_ambient_background
)
def toggle_ambient_background(self):
"""Toggle ambient background on/off smoothly."""
enabled = self.ambient_enabled_var.get()
try:
if enabled and not self.ambient_bg:
# Create ambient background with current theme
self.ambient_bg = AmbientBackground(
self.root,
tokens=self.theme_manager.get_current_theme_colors(),
enable_animation=True,
animation_speed="medium",
particle_count=30,
glow_effects=True
)
print("✅ Ambient background enabled")
elif not enabled and self.ambient_bg:
# Destroy ambient background cleanly
self.ambient_bg.destroy()
self.ambient_bg = None
print("❌ Ambient background disabled")
except Exception as e:
print(f"Error toggling ambient background: {e}")
def create_ui(self):
# Pack main content WITH padding to show ambient margins
main_frame = ttk.Frame(self.root)
main_frame.pack(fill="both", expand=True, padx=20, pady=20)
# Your UI widgets here
ttk.Label(main_frame, text="My App", font=("Segoe UI", 24)).pack()
def run(self):
self.root.mainloop()
Key Points:
For more comprehensive settings dialogs:
class SettingsDialog:
def __init__(self, parent, theme_manager, app_instance):
self.parent = parent
self.theme_manager = theme_manager
self.app_instance = app_instance
def show(self):
dialog = tk.Toplevel(self.parent)
dialog.title("Settings")
dialog.geometry("450x600")
# Settings frame
settings_frame = ttk.LabelFrame(dialog, text="Display Settings", padding=15)
settings_frame.pack(fill="x", padx=20, pady=20)
# Ambient background toggle
if hasattr(self.app_instance, 'ambient_bg'):
current_state = self.app_instance.ambient_bg is not None
self.ambient_var = tk.BooleanVar(value=current_state)
ttk.Checkbutton(
settings_frame,
text="Enable ambient background animations",
variable=self.ambient_var,
command=self._toggle_ambient
).pack(anchor="w", pady=5)
def _toggle_ambient(self):
"""Toggle ambient background via settings dialog."""
if not hasattr(self.app_instance, 'toggle_ambient_background'):
return
enabled = self.ambient_var.get()
self.app_instance.toggle_ambient_background(enabled)
# In your main app class:
class MyApp:
def __init__(self):
# ... initialization ...
self.settings_dialog = SettingsDialog(self.root, self.theme_manager, self)
def show_settings(self):
"""Show settings dialog."""
self.settings_dialog.show()
def toggle_ambient_background(self, enabled: bool):
"""Toggle ambient background (called from settings)."""
try:
if enabled and not self.ambient_bg:
self.ambient_bg = AmbientBackground(
self.root,
tokens=self.theme_manager.get_current_theme_colors(),
enable_animation=True,
animation_speed="medium",
particle_count=30,
glow_effects=True
)
# Optional: Show toast notification
if hasattr(self, 'toast_notification'):
self.toast_notification.show("Ambient background enabled", type_="success")
elif not enabled and self.ambient_bg:
self.ambient_bg.destroy()
self.ambient_bg = None
if hasattr(self, 'toast_notification'):
self.toast_notification.show("Ambient background disabled", type_="info")
except Exception as e:
print(f"Error toggling ambient background: {e}")
Benefits of Settings Dialog Approach:
---
import tkinter as tk
from tkinter import ttk
from bravura.themes.theme_manager import ProfessionalThemeManager
from bravura.components import AmbientBackground
class Dashboard:
def __init__(self):
self.root = tk.Tk()
self.root.title("Professional Dashboard")
self.root.geometry("1200x800")
# Theme manager
self.theme_manager = ProfessionalThemeManager(self)
self.theme_manager.apply_theme("platinum")
# Ambient background
self.ambient_bg = AmbientBackground(
self.root,
width=1200,
height=800,
theme_tokens=self.theme_manager.get_current_theme_colors(),
particle_count=30
)
# UI
self.create_dashboard()
def create_dashboard(self):
# Header
header = ttk.Frame(self.root)
header.pack(fill="x", padx=20, pady=20)
ttk.Label(
header,
text="Enterprise Dashboard",
font=("Segoe UI", 24, "bold")
).pack(side="left")
# Theme selector
ttk.Button(
header,
text="Change Theme",
command=self.show_theme_selector
).pack(side="right")
# Content
content = ttk.Frame(self.root)
content.pack(fill="both", expand=True, padx=20, pady=20)
# Add your dashboard widgets here
ttk.Label(
content,
text="Widgets appear above ambient background",
font=("Segoe UI", 14)
).pack(pady=50)
def show_theme_selector(self):
# Simple theme selector
themes = ["wigley_site", "platinum", "black", "white"]
dialog = tk.Toplevel(self.root)
dialog.title("Select Theme")
dialog.geometry("300x200")
for theme in themes:
ttk.Button(
dialog,
text=theme.replace("_", " ").title(),
command=lambda t=theme: self.change_theme(t, dialog)
).pack(pady=5)
def change_theme(self, theme_name, dialog=None):
# Apply theme
self.theme_manager.apply_theme(theme_name)
# Recreate ambient background
self.ambient_bg.destroy()
self.ambient_bg = AmbientBackground(
self.root,
width=1200,
height=800,
theme_tokens=self.theme_manager.get_current_theme_colors(),
particle_count=30
)
if dialog:
dialog.destroy()
def run(self):
self.root.mainloop()
if __name__ == "__main__":
app = Dashboard()
app.run()
import tkinter as tk
from tkinter import ttk
from bravura.themes.theme_manager import ProfessionalThemeManager
from bravura.components import AmbientBackground
class DataViz:
def __init__(self):
self.root = tk.Tk()
self.root.title("Data Visualization")
self.root.geometry("1000x700")
# Theme
self.theme_manager = ProfessionalThemeManager(self)
self.theme_manager.apply_theme("wigley_site")
# Ambient background with performance settings
self.ambient_bg = AmbientBackground(
self.root,
theme_tokens=self.theme_manager.get_current_theme_colors(),
particle_count=15, # Lower count for data-heavy UI
animation_speed="slow" # Slow to not distract from data
)
self.create_ui()
def create_ui(self):
# Main canvas for charts
canvas = tk.Canvas(
self.root,
bg=self.theme_manager.get_current_theme_colors().get("background_primary"),
highlightthickness=0
)
canvas.pack(fill="both", expand=True, padx=10, pady=10)
# Add your data visualization widgets
canvas.create_text(
500, 350,
text="Your Charts Here",
font=("Segoe UI", 32),
fill=self.theme_manager.get_current_theme_colors().get("text_main")
)
def run(self):
self.root.mainloop()
if __name__ == "__main__":
app = DataViz()
app.run()
---
Initialize ambient background component. See "Configuration Options" above for full parameter list.
Create or recreate the background canvas. Usually called automatically.
Start particle animation (if enable_animation=True).
Stop particle animation.
Update theme colors instantly without recreating the component.
# Instant theme update - 10x faster than recreation
ambient_bg.set_theme_tokens(theme_manager.get_current_theme_colors())
Parameters:
tokens (dict): Theme token dictionary with colorsPerformance:
Clean up and remove the ambient background. Always call when disposing.
Show/hide optional grid overlay.
ambient_bg.set_grid_visibility(True) # Show grid
ambient_bg.set_grid_visibility(False) # Hide grid
The underlying tkinter Canvas widget.
The Frame containing the canvas (automatically lowered behind UI).
Boolean indicating if animation is enabled.
Boolean indicating if animation is currently running.
List of particle dictionaries (for advanced customization).
---
theme_tokens for theme-aware colorsset_theme_tokens() for theme changes - Instant updates without recreationset_theme_tokens() instead (10x faster).destroy() when done---
theme_tokens parameter provided?glow_effects=True enabled?.lower() on canvas?If issues persist:
Test/test_ambient_background_comprehensive.pyAMBIENT_BACKGROUND_THEME_COLORS.mdBravura Demos and Templates/---
---
Copyright © 2025 Wigley Studios LLC. All rights reserved.