import tkinter as tk
from PIL import Image, ImageTk, ImageDraw, ImageFilter
# ========================================================================
# Base class for all radio button styles
# This class defines common functionality that all radio button styles will share
# ========================================================================
class RadioButton(tk.Canvas):
def __init__(self, parent, width=30, height=30, group=None,
value=None, command=None):
# Initialize canvas with specified dimensions and appearance
# A canvas is a widget where we can draw shapes, images, etc.
super().__init__(parent, width=width, height=height,
highlightthickness=0, bd=0, bg="#1A1A1A")
# Store parameters for later use
self.width = width # Width of the radio button
self.height = height # Height of the radio button
self.group = group # RadioGroup this button belongs to (for grouped behavior)
self.value = value # Value associated with this button (returned when selected)
self.command = command # Function to call when button is selected
# State tracking variables
self.is_selected = False # Tracks if this button is currently selected
self.hover = False # Tracks if mouse is hovering over this button
self.animation_progress = 0.0 # Animation state (0.0 = off, 1.0 = fully on)
self.animation_running = False # Tracks if animation is currently running
# Animation configuration - controls how smooth transitions look
self.animation_duration = 0.2 # Total time for animation in seconds
self.animation_steps = 15 # Number of steps in animation
# Time per step
self.animation_step_time = self.animation_duration / self.animation_steps
# Bind mouse events - these detect when user interacts with the button
self.bind("<ButtonPress-1>", self.on_click) # Detect mouse clicks
self.bind("<Enter>", self.on_enter) # Detect when mouse enters button area
self.bind("<Leave>", self.on_leave) # Detect when mouse leaves button area
# Register with group if provided - this allows for radio button group behavior
# where only one button in a group can be selected
if self.group is not None:
self.group.add_radio(self)
# Initial draw - show the button in its starting state
self.draw()
def on_click(self, event=None):
# Handle click event - select this button
if self.group is not None:
# If button is part of a group, let the group handle selection logic
self.group.select(self)
else:
# Otherwise, handle selection directly
self.select()
def on_enter(self, event):
# Mouse entered button area - show hover effect
self.hover = True
self.draw() # Redraw with hover effect
def on_leave(self, event):
# Mouse left button area - remove hover effect
self.hover = False
self.draw() # Redraw without hover effect
def select(self):
# Select this button if it's not already selected
if not self.is_selected:
self.is_selected = True # Mark as selected
self.start_animation() # Begin animation to selected state
if self.command:
# Call command function with button's value if one was provided
self.command(self.value)
def deselect(self):
# Deselect this button if it's currently selected
if self.is_selected:
self.is_selected = False # Mark as deselected
self.start_animation() # Begin animation to deselected state
def start_animation(self):
# Begin animating between states (selected/deselected)
# If animation is already running, do nothing
if self.animation_running:
return
self.animation_running = True
# Determine target state and setup animation
# If selected, animate toward 1.0, otherwise toward 0.0
target_progress = 1.0 if self.is_selected else 0.0
start_progress = self.animation_progress # Store current progress
step = 0 # Initialize step counter
# This nested function will be called repeatedly to animate the button
def animate():
nonlocal step # Use the step variable from outer function
if step < self.animation_steps:
# Calculate current progress in animation
progress_delta = (target_progress-start_progress)/self.animation_steps
self.animation_progress = start_progress + (progress_delta * step)
step += 1
self.draw() # Redraw with current animation state
# Schedule next animation step using Tkinter's after method
# This creates a smooth animation by spacing out the updates
self.after(int(self.animation_step_time * 1000), animate)
else:
# Animation complete - set final state
self.animation_progress = target_progress
self.draw()
self.animation_running = False
# Start animation by calling the animate function
animate()
def interpolate_color(self, color1, color2, ratio):
# Blend two hex colors based on ratio (0.0 to 1.0)
# This creates smooth transitions between colors
# Extract RGB components from hex color strings
r1, g1, b1 = int(color1[1:3], 16), int(color1[3:5], 16), int(color1[5:7], 16)
r2, g2, b2 = int(color2[1:3], 16), int(color2[3:5], 16), int(color2[5:7], 16)
# Calculate interpolated RGB values
r = int(r1 * (1 - ratio) + r2 * ratio)
g = int(g1 * (1 - ratio) + g2 * ratio)
b = int(b1 * (1 - ratio) + b2 * ratio)
# Return the new color as a hex string
return f'#{r:02x}{g:02x}{b:02x}'
def create_circle(self, x, y, radius, **kwargs):
# Function to create a circle on the canvas
# A circle is actually an oval with equal width and height in Tkinter
return self.create_oval(x-radius, y-radius, x+radius, y+radius, **kwargs)
def create_smooth_image(self, width, height, draw_func):
# Create a high-quality anti-aliased image using PIL (Python Imaging Library)
# This gives smoother graphics than Tkinter's built-in drawing
# Create image at higher resolution
scale_factor = 2 # Create at 2x size and scale down for better quality
img_width = width * scale_factor
img_height = height * scale_factor
# Create transparent image - RGBA mode allows for transparency
img = Image.new('RGBA', (img_width, img_height), (0, 0, 0, 0))
draw = ImageDraw.Draw(img) # Create drawing object
# Call custom drawing function (passed as a parameter)
# This function will draw the specific style elements
draw_func(draw, img_width, img_height)
# Apply blur for smoothing and resize to target dimensions
# Add slight blur for anti-aliasing
img = img.filter(ImageFilter.GaussianBlur(radius=0.5))
img = img.resize((width, height), Image.LANCZOS) # Resize to target dimensions
# Convert to Tkinter-compatible format
return ImageTk.PhotoImage(img)
def draw(self):
# Abstract method - to be implemented by subclasses
# Each radio button style will override this with its own drawing code
pass
# ========================================================================
# Manages a group of radio buttons ensuring only one is selected at a time
# This implements the classic radio button behavior
# ========================================================================
class RadioGroup:
def __init__(self, command=None):
self.radios = [] # List of radio buttons in this group
self.selected = None # Currently selected radio button
self.command = command # Function to call when selection changes
def add_radio(self, radio):
# Add a radio button to this group
self.radios.append(radio)
def select(self, radio):
# Select a radio button in this group
# If it's different from the currently selected one
if self.selected != radio:
# Deselect previous selection if any
if self.selected is not None:
self.selected.deselect()
# Update selection and notify
self.selected = radio
radio.select()
# Call group command if provided
if self.command:
self.command(radio.value)
# ========================================================================
# Neon style radio button with a glowing effect
# ========================================================================
class NeonRadioButton(RadioButton):
def draw(self):
self.delete("all") # Clear the canvas before redrawing
# Define colors for this style
bg_off_color = "#333333" # Background color when off (dark gray)
bg_on_color = "#00FF88" # Background color when on (bright green)
# Apply hover effect - brighten slightly when hovered
hover_boost = 0.2 if self.hover else 0.0
# Calculate current state based on animation and hover
# This combines hover and selection state for smooth transitions
current_progress = min(1.0, self.animation_progress + hover_boost)
# Get color between off and on colors based on current progress
current_bg_color = self.interpolate_color(bg_off_color, bg_on_color,
current_progress)
# Calculate positions for drawing
center_x = self.width / 2
center_y = self.height / 2
outer_radius = min(center_x, center_y) - 2 # Slightly smaller than canvas
# Draw main circle - the base of the radio button
self.create_circle(center_x, center_y, outer_radius,
fill=current_bg_color, outline="")
# Draw inner dot when selected - appears as animation progresses
if self.animation_progress > 0:
dot_color = "#FFFFFF" # White dot
# Dot size grows with animation progress
dot_radius = outer_radius * 0.6 * self.animation_progress
self.create_circle(center_x, center_y, dot_radius,
fill=dot_color, outline="")
# Draw glow effect when selected - creates the "neon" look
if self.animation_progress > 0:
glow_color = "#00FF88" # Green glow
# Glow width increases with animation progress
glow_width = int(3 * self.animation_progress)
if glow_width > 0:
self.create_circle(center_x, center_y, outer_radius,
fill="", outline=glow_color, width=glow_width)
# ========================================================================
# Gradient style radio button with color transitions
# ========================================================================
class GradientRadioButton(RadioButton):
def draw(self):
self.delete("all") # Clear the canvas before redrawing
# Define colors for gradient
unchecked_start_color = (255, 107, 107) # Red start (RGB values)
unchecked_end_color = (254, 202, 87) # Yellow end (RGB values)
checked_start_color = (72, 52, 212) # Purple start (RGB values)
checked_end_color = (104, 109, 224) # Blue end (RGB values)
# Apply hover effect - brighten slightly when hovered
hover_boost = 0.1 if self.hover else 0.0
effective_progress = min(1.0, self.animation_progress + hover_boost)
# Calculate current gradient colors based on state
# This interpolates between unchecked and checked colors
# For the start color of the gradient:
start_r = int(unchecked_start_color[0] * (1 - effective_progress) +
checked_start_color[0] * effective_progress)
start_g = int(unchecked_start_color[1] * (1 - effective_progress) +
checked_start_color[1] * effective_progress)
start_b = int(unchecked_start_color[2] * (1 - effective_progress) +
checked_start_color[2] * effective_progress)
# For the end color of the gradient:
end_r = int(unchecked_end_color[0] * (1 - effective_progress) +
checked_end_color[0] * effective_progress)
end_g = int(unchecked_end_color[1] * (1 - effective_progress) +
checked_end_color[1] * effective_progress)
end_b = int(unchecked_end_color[2] * (1 - effective_progress) +
checked_end_color[2] * effective_progress)
# Function to draw gradient circle - this is passed to create_smooth_image
def draw_gradient_circle(draw, width, height):
center_x = width / 2
center_y = height / 2
radius = min(center_x, center_y)
# Draw concentric circles with color gradient
# This creates a radial gradient effect
for r in range(int(radius)):
ratio = r / radius # Position in gradient (0 to 1)
# Calculate color at this position in the gradient
r_color = int(start_r * (1 - ratio) + end_r * ratio)
g_color = int(start_g * (1 - ratio) + end_g * ratio)
b_color = int(start_b * (1 - ratio) + end_b * ratio)
color = (r_color, g_color, b_color, 255) # RGBA color
# Draw a circle outline at this radius with calculated color
draw.ellipse([center_x - r, center_y - r,
center_x + r, center_y + r],
outline=color)
# Create gradient image and add to canvas
gradient_img = self.create_smooth_image(self.width, self.height,
draw_gradient_circle)
# Keep reference to prevent garbage collection
self._gradient_img = gradient_img
self.create_image(self.width//2, self.height//2, image=gradient_img)
# Draw inner dot when selected
if self.animation_progress > 0:
center_x = self.width / 2
center_y = self.height / 2
# Dot size increases with animation progress
dot_radius = min(center_x, center_y) * 0.4 * self.animation_progress
# Draw white inner dot
self.create_circle(center_x, center_y, dot_radius,
fill="#FFFFFF", outline="")
# ========================================================================
# Minimal style radio button with simple design
# ========================================================================
class MinimalRadioButton(RadioButton):
def draw(self):
self.delete("all") # Clear the canvas before redrawing
# Define colors for this style
outline_off_color = "#AAAAAA" # Gray outline when off
outline_on_color = "#00FF88" # Green outline when on
# Apply hover effect - brighten slightly when hovered
hover_boost = 0.2 if self.hover else 0.0
effective_progress = min(1.0, self.animation_progress + hover_boost)
# Calculate current outline color based on state
current_outline_color = self.interpolate_color(
outline_off_color, outline_on_color, effective_progress)
# Calculate positions for drawing
center_x = self.width / 2
center_y = self.height / 2
outer_radius = min(center_x, center_y) - 2 # Slightly smaller than canvas
# Calculate outline width based on state - gets thicker when selected
outline_width = 1 + int(2 * effective_progress)
# Draw circle outline - just the border, not filled
self.create_circle(center_x, center_y, outer_radius,
fill="", outline=current_outline_color, width=outline_width)
# Draw inner dot when selected
if self.animation_progress > 0:
# Dot color transitions from gray to green
dot_color = self.interpolate_color(
"#AAAAAA", "#00FF88", self.animation_progress)
# Dot size increases with animation progress
dot_radius = outer_radius * 0.5 * self.animation_progress
# Draw the inner dot
self.create_circle(center_x, center_y, dot_radius,
fill=dot_color, outline="")
# ========================================================================
# Methods for angle calculations (used by CyberpunkRadioButton)
# These convert between degrees and the radians that math functions use
# ========================================================================
def cos_deg(angle):
import math
return math.cos(math.radians(angle))
def sin_deg(angle):
import math
return math.sin(math.radians(angle))
# ========================================================================
# Cyberpunk style radio button with tech-inspired design
# ========================================================================
class CyberpunkRadioButton(RadioButton):
def draw(self):
self.delete("all") # Clear the canvas before redrawing
# Calculate positions for drawing
center_x = self.width / 2
center_y = self.height / 2
outer_radius = min(center_x, center_y) - 2 # Slightly smaller than canvas
# Draw dark metallic background - base of the button
self.create_circle(center_x, center_y, outer_radius,
fill="#151820", outline="")
# Cyberpunk color scheme - neon magenta and cyan
unchecked_color = "#FF00FF" # Magenta
checked_color = "#00FFFF" # Cyan
# Apply hover effect - brighten slightly when hovered
hover_boost = 0.2 if self.hover else 0.0
effective_progress = min(1.0, self.animation_progress + hover_boost)
# Calculate current border color based on state
border_color = self.interpolate_color(
unchecked_color, checked_color, effective_progress)
# Add hexagon pattern to simulate tech/circuit pattern
num_segments = 6 # Number of segments in the hexagon
segment_angle = 360 / num_segments # Angle of each segment
segment_radius = outer_radius * 0.85 # Size of hexagon
# Draw hexagon segments - creates a tech/circuit looking pattern
for i in range(num_segments):
angle1 = i * segment_angle # Start angle of segment
angle2 = (i + 1) * segment_angle # End angle of segment
# Calculate points for segment using trigonometry
x1 = center_x + segment_radius * 0.98 * cos_deg(angle1)
y1 = center_y + segment_radius * 0.98 * sin_deg(angle1)
x2 = center_x + segment_radius * cos_deg(angle2)
y2 = center_y + segment_radius * sin_deg(angle2)
# Create hexagon segment - alternate segments are colored
if i % 2 == 0: # Only color even-numbered segments
seg_color = self.interpolate_color("#151820", border_color,
0.3 * effective_progress)
self.create_line(center_x, center_y, x1, y1, x2, y2,
fill=seg_color, width=1, smooth=True)
# Add techno border with animated width - gets thicker when selected
border_width = 1.5 + int(effective_progress * 2.5)
self.create_circle(center_x, center_y, outer_radius,
fill="", outline=border_color, width=border_width)
# Add inner tech effect when selected/animated
if self.animation_progress > 0:
inner_radius = outer_radius * 0.6
# Function to draw cyberpunk center with power button symbol
def draw_cyber_center(draw, width, height):
center_x = width / 2
center_y = height / 2
radius = min(center_x, center_y) * 0.9
# Draw active element with pulse effect - opacity based on animation
pulse_opacity = int(255 * self.animation_progress)
# Draw circular power button symbol - the outer ring
ring_radius = radius * 0.8
draw.ellipse([center_x - ring_radius, center_y - ring_radius,
center_x + ring_radius, center_y + ring_radius],
outline=(0, 255, 255, pulse_opacity),
width=int(width * 0.08))
# Draw power symbol line - the vertical line in power symbol
line_length = ring_radius * 0.6
line_width = int(width * 0.12)
draw.line([center_x, center_y - line_length * 0.5,
center_x, center_y + line_length * 0.5],
fill=(255, 0, 255, pulse_opacity),
width=line_width)
# Add HUD elements - small accents around the button
# These are small rectangles at the top, right, bottom, left
for i in range(4):
angle = i * 90 # 0, 90, 180, 270 degrees
hud_size = radius * 0.3
# Calculate position using trigonometry
x = center_x + (radius - hud_size/2) * cos_deg(angle)
y = center_y + (radius - hud_size/2) * sin_deg(angle)
# Small tech accent elements
draw.rectangle([x - hud_size/2, y - hud_size/8,
x + hud_size/2, y + hud_size/8],
fill=(0, 255, 255, pulse_opacity//3),
outline=(0, 255, 255, pulse_opacity))
# Create center image with power button and add to canvas
center_img = self.create_smooth_image(int(inner_radius*2),
int(inner_radius*2), draw_cyber_center)
# Keep reference to prevent garbage collection
self._center_img = center_img
self.create_image(center_x, center_y, image=center_img)
# Add external circuit elements when hovered or selected
# These are lines radiating outward from the button
if self.hover or self.animation_progress > 0:
circuit_color = self.interpolate_color(
unchecked_color, checked_color, effective_progress)
# Create multiple circuit lines radiating outward at 45° angles
for angle in [45, 135, 225, 315]:
line_length = outer_radius * 0.4
# Start point on the circle
x1 = center_x + outer_radius * cos_deg(angle)
y1 = center_y + outer_radius * sin_deg(angle)
# End point extends outward
x2 = center_x + (outer_radius + line_length) * cos_deg(angle)
y2 = center_y + (outer_radius + line_length) * sin_deg(angle)
# Draw circuit line
self.create_line(x1, y1, x2, y2, fill=circuit_color, width=1)
# Add circuit node (small circle) at end of line
node_size = 2 + self.animation_progress * 2
self.create_circle(x2, y2, node_size, fill=circuit_color, outline="")
# ========================================================================
# Neumorphic style radio button - soft UI with shadow effects
# ========================================================================
class NeumorphicRadioButton(RadioButton):
def __init__(self, parent, width=36, height=36, group=None,
value=None, command=None):
# Call the parent class constructor with our parameters
# The parent RadioButton class handles the basic functionality
super().__init__(parent, width=width, height=height,
group=group, value=value, command=command)
# Define the color scheme for our neumorphic effect
# These colors create the 3D shadow effect that makes the button look raised
self.base_color = "#262626" # Main button color (dark gray)
self.light_shadow = "#3A3A3A" # Light shadow for the 3D effect (lighter gray)
self.dark_shadow = "#121212" # Dark shadow for the 3D effect (darker gray)
self.accent_color = "#00A3FF" # Blue color shown when the button is selected
def draw(self):
"""
Draw the radio button with neumorphic effects.
This method is called whenever the button needs to be redrawn
(initialization, state changes, etc.)
"""
# Clear any existing drawings on the canvas
self.delete("all")
# Calculate the center point of our button
center_x = self.width / 2
center_y = self.height / 2
# Define the outer radius
# (slightly smaller than max possible for better spacing)
outer_radius = min(center_x, center_y) - 3
# Calculate visual effects based on button state
# These values will be used to adjust shadows and colors
# Effect when button is selected
pressed_effect = 0.3 if self.is_selected else 0
# Effect when mouse hovers over button
hover_effect = 0.15 if self.hover else 0
# Use the stronger of the two effects
combined_effect = max(pressed_effect, hover_effect)
# This inner function will be passed to create_smooth_image
# to draw the main button
def draw_neumorphic_circle(draw, width, height):
"""
Draw a neumorphic circle with proper shadows.
The 'draw' parameter is a PIL drawing context.
"""
# Calculate center and radius for this drawing context
center_x = width / 2
center_y = height / 2
# Slightly smaller for better appearance
radius = min(center_x, center_y) * 0.95
# Set the base color, making it slightly darker when pressed/hovered
base_color_rgb = (38, 38, 38) # Default RGB color
if combined_effect > 0:
# Darken the color slightly based on pressed/hover state
base_color_rgb = (int(38 - 5 * combined_effect),
int(38 - 5 * combined_effect),
int(38 - 5 * combined_effect))
# Draw the main circle with the base color
# Parameters are [left, top, right, bottom] coordinates of bounding box
draw.ellipse([center_x - radius, center_y - radius,
center_x + radius, center_y + radius],
fill=base_color_rgb + (255,)) # Add alpha channel (fully opaque)
# Define shadow properties
shadow_width = int(radius * 0.25) # How wide the shadows should be
shadow_layers = 12 # More layers = smoother gradient
# Create multiple shadow layers with decreasing opacity for a smooth effect
for i in range(shadow_layers):
# Calculate progress through the shadow layers (0.0 to 1.0)
progress = i / shadow_layers
# Shadow strength decreases when button is pressed
shadow_strength = 1.0 - (combined_effect * 0.7)
# Calculate radius for this shadow layer
r = radius - progress * radius * 0.08
# Calculate opacity for this layer (fades out as we move outward)
opacity = int(200 * (1 - progress) * shadow_strength)
# Calculate shadow width (thinner as we move outward)
shadow_width = max(1, int(3 * (1 - progress)))
# Draw dark shadow in top-left quadrant (135° to 315°)
# This simulates light coming from bottom-right
draw.arc([center_x - r, center_y - r,
center_x + r, center_y + r],
135, 315, fill=(18, 18, 18, opacity), width=shadow_width)
# Draw light shadow in bottom-right quadrant (-45° to 135°)
# This completes the 3D effect
draw.arc([center_x - r, center_y - r,
center_x + r, center_y + r],
-45, 135, fill=(58, 58, 58, opacity), width=shadow_width)
# Add a subtle highlight along the edge for extra depth
edge_r = radius * 0.97
edge_opacity = int(120 * (1 - combined_effect * 0.5))
draw.arc([center_x - edge_r, center_y - edge_r,
center_x + edge_r, center_y + edge_r],
-30, 150, fill=(75, 75, 75, edge_opacity), width=2)
# Create the main button image using our drawing function
# create_smooth_image handles anti-aliasing for smooth edges
circle_img = self.create_smooth_image(self.width, self.height,
draw_neumorphic_circle)
self._circle_img = circle_img # Store reference to prevent garbage collection
self.create_image(center_x, center_y, image=circle_img) # Add image to canvas
# Calculate size for inner effect (shadows inside the button)
inner_effect_size = outer_radius * 0.7
# This function draws the inner shadows and highlights
def draw_inner_effect(draw, width, height):
"""
Draw inner shadows and highlights based on button state.
Different effects for selected vs hovered states.
"""
center_x = width / 2
center_y = height / 2
radius = min(center_x, center_y) * 0.9
# Different effects based on whether the button is selected or just hovered
if self.is_selected:
# For selected state: create a recessed circular area effect
# Calculate opacity for shadows based on animation progress
# animation_progress gradually increases when selection changes
shadow_opacity = int(180 * self.animation_progress)
highlight_opacity = int(140 * self.animation_progress)
# Draw inner circle with darker base color to create impression of depth
inner_color = (int(38 - 12 * self.animation_progress),
int(38 - 10 * self.animation_progress),
int(38 - 8 * self.animation_progress),
int(255 * self.animation_progress))
draw.ellipse([center_x - radius, center_y - radius,
center_x + radius, center_y + radius],
fill=inner_color)
# Calculate radius for inner highlights
inner_r = radius * 0.92
# Draw bottom-right inner highlight
draw.arc([center_x - inner_r, center_y - inner_r,
center_x + inner_r, center_y + inner_r],
-45, 135, fill=(50, 50, 50, highlight_opacity), width=2)
# Draw top-left inner shadow
draw.arc([center_x - inner_r, center_y - inner_r,
center_x + inner_r, center_y + inner_r],
135, 315, fill=(15, 15, 15, shadow_opacity), width=2)
elif self.hover:
# For hover state: create a subtle glow effect
glow_opacity = int(180)
# Create a subtle radial gradient
# by drawing progressively smaller circles
for i in range(8):
progress = i / 8
r = radius * (1 - progress * 0.2)
opacity = int(glow_opacity * (1 - progress))
# Draw outline circle with decreasing opacity
draw.ellipse([center_x - r, center_y - r,
center_x + r, center_y + r],
outline=(200, 200, 200, opacity), width=1)
# Create inner effect image if the button is selected or hovered
if self.is_selected or self.hover:
inner_img = self.create_smooth_image(
int(inner_effect_size * 2), # Width
int(inner_effect_size * 2), # Height
draw_inner_effect
)
self._inner_img = inner_img # Store reference to prevent garbage collection
self.create_image(center_x, center_y, image=inner_img) # Add to canvas
# Draw the indicator dot when selected, with a nice glow effect
if self.animation_progress > 0:
def draw_indicator(draw, width, height):
"""
Draw the center indicator dot with a glow effect.
This shows the button is selected.
"""
center_x = width / 2
center_y = height / 2
# Extract RGB components from accent color (#00A3FF)
r, g, b = 0, 163, 255
# Calculate dot size based on animation progress
dot_radius = width * 0.25 * self.animation_progress
opacity = int(255 * self.animation_progress)
# Draw the central indicator dot
draw.ellipse([center_x - dot_radius, center_y - dot_radius,
center_x + dot_radius, center_y + dot_radius],
fill=(r, g, b, opacity))
# Add a glow effect around the dot using concentric circles
for i in range(20):
glow_progress = i / 20
# Each circle gets progressively larger
glow_radius = dot_radius * (1 + glow_progress * 0.8)
# Each circle gets progressively more transparent
glow_opacity = int(150*(1 - glow_progress)*self.animation_progress)
# Draw glow circle (outline only)
draw.ellipse([center_x - glow_radius, center_y - glow_radius,
center_x + glow_radius, center_y + glow_radius],
outline=(r, g, b, glow_opacity), width=1)
# Create indicator with glow effect
indicator_size = outer_radius * 0.8
indicator_img = self.create_smooth_image(
int(indicator_size * 2), # Width
int(indicator_size * 2), # Height
draw_indicator
)
self._indicator_img = indicator_img # Store reference
self.create_image(center_x, center_y, image=indicator_img) # Add to canvas
# Main application that showcases all radio button styles
class ModernRadioButtonsApp(tk.Tk):
def __init__(self):
super().__init__()
# Setup main window
self.title("Radio Button Collection")
self.geometry("600x700")
self.configure(bg="#1A1A1A")
# Create main frame
main_frame = tk.Frame(self, bg="#1A1A1A", padx=15, pady=15)
main_frame.pack(fill=tk.BOTH, expand=True)
# Add title
title_label = tk.Label(main_frame, text="Custom Radio Button Styles",
fg="#FFFFFF", bg="#1A1A1A", font=("Segoe UI", 24))
title_label.pack(pady=(0, 20))
# Add each radio button style with its own group
self.create_radio_group(main_frame, "Neon Style", NeonRadioButton)
self.create_spacer(main_frame, 15)
self.create_radio_group(main_frame, "Gradient Style", GradientRadioButton)
self.create_spacer(main_frame, 15)
self.create_radio_group(main_frame, "Minimal Style", MinimalRadioButton)
self.create_spacer(main_frame, 15)
self.create_radio_group(main_frame, "Cyberpunk Style", CyberpunkRadioButton)
self.create_spacer(main_frame, 15)
self.create_radio_group(main_frame, "Neumorphic Style", NeumorphicRadioButton)
# Add status label to show selection
self.status_label = tk.Label(main_frame, text="No selection",
fg="#FFFFFF", bg="#1A1A1A",
font=("Segoe UI", 16))
self.status_label.pack(pady=10)
def create_radio_group(self, parent, label_text, radio_class):
# Create a frame for the radio group and label
group_frame = tk.Frame(parent, bg="#1A1A1A", pady=10)
group_frame.pack(fill=tk.X)
# Add group label
label = tk.Label(group_frame, text=label_text,
fg="#FFFFFF", bg="#1A1A1A",
font=("Segoe UI", 16))
label.pack(anchor=tk.W, pady=(0, 10))
# Create frame for radio buttons
radio_frame = tk.Frame(group_frame, bg="#1A1A1A")
radio_frame.pack(fill=tk.X)
# Create radio group with callback
radio_group = RadioGroup(command=self.update_status)
# Add radio buttons to the group
for i in range(3):
option_frame = tk.Frame(radio_frame, bg="#1A1A1A")
option_frame.pack(side=tk.LEFT, padx=20)
# Create radio button
radio = radio_class(option_frame, group=radio_group, value=f"Option {i+1}")
radio.pack(side=tk.LEFT)
# Add option label
option_label = tk.Label(option_frame, text=f"Option {i+1}",
fg="#FFFFFF", bg="#1A1A1A",
font=("Segoe UI", 12))
option_label.pack(side=tk.LEFT, padx=10)
def create_spacer(self, parent, height):
# Create a vertical spacer with specified height
spacer = tk.Frame(parent, bg="#1A1A1A", height=height)
spacer.pack(fill=tk.X)
def update_status(self, value):
# Update status label with currently selected value
self.status_label.config(text=f"Selected: {value}")
# Run the application
if __name__ == "__main__":
app = ModernRadioButtonsApp()
app.mainloop()