Python Tkinter Image Zoom

How To Create an Image Zooming and Pan App in Python Using Tkinter



How To Create an Image Zooming and Pan App in Python Using Tkinter




In this Python Tutorial we will see How to Create an image viewer with zoom, pan, and drag functionality Using Python and Tkinter library.

What We're Building?
Our image viewer will include:
- Image loading with file dialog.
- Zoom in/out functionality.
- Pan and drag.
- Fit-to-window.
- Keyboard shortcuts.
- Status bar with image information.

What We Are Gonna Use In This Project:

- Python Programming Language.
- Tkinter (GUI).
- VS Editor.






Project Source Code:




import tkinter as tk
from tkinter import filedialog, messagebox
from PIL import Image, ImageTk
import os


class ImageZoomApp:
def __init__(self, root):
"""Initialize the image viewer application"""
# Set up the main window
self.root = root
self.root.title("Simple Image Zoom App")
self.root.geometry("1000x700") # Width x Height
self.root.configure(bg='#2c3e50') # Dark blue background
# Variables to store image data
self.original_image = None # The original image file
self.display_image = None # The image we show on screen
self.zoom_level = 1.0 # How zoomed in we are (1.0 = normal size)
# Variables for dragging the image around
self.drag_start_x = 0
self.drag_start_y = 0
self.image_x = 0 # Where the image is positioned
self.image_y = 0
# Create all the parts of our interface
self.create_buttons()
self.create_canvas()
self.create_status_bar()
self.setup_keyboard_shortcuts()



def create_buttons(self):
"""Create the button toolbar at the top"""
# Frame to hold all our buttons
button_frame = tk.Frame(self.root, bg='#34495e', height=60)
button_frame.pack(fill=tk.X, padx=10, pady=10)
button_frame.pack_propagate(False) # Keep the frame height fixed
# Open Image button
open_btn = tk.Button(
button_frame,
text="Open Image",
command=self.open_image,
bg='#3498db', # Blue background
fg='white', # White text
font=('Arial', 12, 'bold'),
padx=20,
pady=8,
cursor='hand2' # Hand cursor when hovering
)
open_btn.pack(side=tk.LEFT, padx=5, pady=10)

# Zoom In button
zoom_in_btn = tk.Button(
button_frame,
text="Zoom In (+)",
command=self.zoom_in,
bg='#27ae60', # Green background
fg='white',
font=('Arial', 10),
padx=15,
pady=8,
cursor='hand2'
)
zoom_in_btn.pack(side=tk.LEFT, padx=5, pady=10)
# Zoom Out button
zoom_out_btn = tk.Button(
button_frame,
text="Zoom Out (-)",
command=self.zoom_out,
bg='#e74c3c', # Red background
fg='white',
font=('Arial', 10),
padx=15,
pady=8,
cursor='hand2'
)
zoom_out_btn.pack(side=tk.LEFT, padx=5, pady=10)
# Reset Size button
reset_btn = tk.Button(
button_frame,
text="Reset Size",
command=self.reset_size,
bg='#9b59b6', # Purple background
fg='white',
font=('Arial', 10),
padx=15,
pady=8,
cursor='hand2'
)
reset_btn.pack(side=tk.LEFT, padx=5, pady=10)
# Fit to Window button
fit_btn = tk.Button(
button_frame,
text="Fit to Window",
command=self.fit_to_window,
bg='#f39c12', # Orange background
fg='white',
font=('Arial', 10),
padx=15,
pady=8,
cursor='hand2'
)
fit_btn.pack(side=tk.LEFT, padx=5, pady=10)
# Zoom level display
self.zoom_label = tk.Label(
button_frame,
text="100%",
bg='#34495e',
fg='#ecf0f1',
font=('Arial', 12, 'bold'),
padx=20
)
self.zoom_label.pack(side=tk.RIGHT, pady=10)
def create_canvas(self):
"""Create the canvas where we display the image"""
# Frame to hold the canvas and scrollbars
canvas_frame = tk.Frame(self.root, bg='#2c3e50')
canvas_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
# Create the canvas
self.canvas = tk.Canvas(
canvas_frame,
bg='#1a252f', # Dark background for the image area
highlightthickness=0, # No border
cursor='crosshair' # Crosshair cursor
)

# Create scrollbars for when image is larger than window
v_scrollbar = tk.Scrollbar(canvas_frame, orient=tk.VERTICAL,
command=self.canvas.yview)
h_scrollbar = tk.Scrollbar(canvas_frame, orient=tk.HORIZONTAL,
command=self.canvas.xview)
# Connect scrollbars to canvas
self.canvas.configure(yscrollcommand=v_scrollbar.set,
xscrollcommand=h_scrollbar.set)
# Position everything in the frame
v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
h_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# Set up mouse interactions
self.setup_mouse_events()
def create_status_bar(self):
"""Create the status bar at the bottom"""
status_frame = tk.Frame(self.root, bg='#34495e', height=40)
status_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
status_frame.pack_propagate(False)
# Status text (left side)
self.status_text = tk.Label(
status_frame,
text="Ready - Click 'Open Image' to start",
bg='#34495e',
fg='#bdc3c7',
font=('Arial', 10),
anchor=tk.W # Align text to the left
)
self.status_text.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=10, pady=10)
# Image info (right side)
self.image_info = tk.Label(
status_frame,
text="",
bg='#34495e',
fg='#3498db',
font=('Arial', 10, 'bold')
)
self.image_info.pack(side=tk.RIGHT, padx=10, pady=10)

def setup_mouse_events(self):
# Mouse dragging for moving the image around
self.canvas.bind("<Button-1>", self.start_drag) # Left click
self.canvas.bind("<B1-Motion>", self.drag_image) # Drag while holding left click
self.canvas.bind("<ButtonRelease-1>", self.end_drag) # Release left click
# Double-click to fit image to window
self.canvas.bind("<Double-Button-1>", lambda e: self.fit_to_window())


def setup_keyboard_shortcuts(self):
"""Set up keyboard shortcuts"""
# Ctrl+O to open image
self.root.bind("<Control-o>", lambda e: self.open_image())
# Plus key to zoom in
self.root.bind("<Control-plus>", lambda e: self.zoom_in())
# Minus key to zoom out
self.root.bind("<Control-minus>", lambda e: self.zoom_out())
# Ctrl+0 to reset size
self.root.bind("<Control-0>", lambda e: self.reset_size())





def open_image(self):
"""Open a file dialog to select an image"""
# Define what types of files we can open
file_types = [
("All Images", "*.png *.jpg *.jpeg *.gif *.bmp"),
("PNG files", "*.png"),
("JPEG files", "*.jpg *.jpeg"),
("GIF files", "*.gif"),
("BMP files", "*.bmp"),
("All files", "*.*")
]
# Show the file selection dialog
filename = filedialog.askopenfilename(
title="Choose an image file",
filetypes=file_types
)
# If user selected a file, try to load it
if filename:
try:
self.load_image(filename)
except Exception as e:
# Show error message if something goes wrong
messagebox.showerror("Error", f"Could not open image:\n{str(e)}")


def load_image(self, filename):
"""Load an image file and display it"""
try:
# Open the image file
self.original_image = Image.open(filename)

# Reset zoom to normal size
self.zoom_level = 1.0
# Center the image in the window
self.center_image()
# Update the display
self.update_display()

# Update status information
self.update_status(filename)

except Exception as e:
raise Exception(f"Failed to load image: {str(e)}")

def center_image(self):
"""Center the image in the canvas"""
# Wait a moment for the window to update its size
self.root.update_idletasks()
# Get the canvas size
canvas_width = self.canvas.winfo_width()
canvas_height = self.canvas.winfo_height()
# Put the image in the center
if canvas_width > 1 and canvas_height > 1:
self.image_x = canvas_width // 2
self.image_y = canvas_height // 2

def update_display(self):
"""Update what we see on the screen"""
if not self.original_image:
return # Nothing to show if no image is loaded
try:
# Calculate the new size based on zoom level
new_width = int(self.original_image.width * self.zoom_level)
new_height = int(self.original_image.height * self.zoom_level)
# Make sure size is at least 1 pixel
new_width = max(1, new_width)
new_height = max(1, new_height)
# Resize the image for display
if self.zoom_level == 1.0:
# Use original image if no zoom
display_image = self.original_image
else:
# Resize the image
display_image = self.original_image.resize((new_width, new_height),
Image.LANCZOS)
# Convert to format Tkinter can display
self.display_image = ImageTk.PhotoImage(display_image)
# Clear the canvas and show the new image
self.canvas.delete("all")
self.canvas.create_image(
self.image_x, self.image_y,
anchor=tk.CENTER,
image=self.display_image
)
# Update the scroll area
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
# Update the zoom percentage display
zoom_percent = int(self.zoom_level * 100)
self.zoom_label.config(text=f"{zoom_percent}%")

except Exception as e:
self.status_text.config(text=f"Error displaying image: {str(e)}")

def update_status(self, filename):
"""Update the status bar with image information"""
if self.original_image:
# Get just the filename without the full path
basename = os.path.basename(filename)
# Get image dimensions
width = self.original_image.width
height = self.original_image.height
# Get image format
image_format = getattr(self.original_image, 'format', 'Unknown')
# Update status displays
self.status_text.config(text=f"Loaded: {basename}")
self.image_info.config(text=f"{width} × {height}{image_format}")


def zoom_in(self):
"""Make the image bigger"""
if self.original_image and self.zoom_level < 10.0: # Max zoom: 1000%
self.zoom_level *= 1.2 # Increase by 20%
self.update_display()
def zoom_out(self):
"""Make the image smaller"""
if self.original_image and self.zoom_level > 0.1: # Min zoom: 10%
self.zoom_level /= 1.2 # Decrease by 20%
self.update_display()
def reset_size(self):
"""Reset image to its original size (100%)"""
if self.original_image:
self.zoom_level = 1.0
self.center_image()
self.update_display()
def fit_to_window(self):
"""Resize image to fit in the window"""
if not self.original_image:
return
# Make sure window size is updated
self.root.update_idletasks()
# Get canvas size
canvas_width = self.canvas.winfo_width()
canvas_height = self.canvas.winfo_height()
# Only proceed if canvas has valid size
if canvas_width > 50 and canvas_height > 50:
# Calculate how much we need to zoom to fit
zoom_x = (canvas_width - 20) / self.original_image.width #Leave 20px margin
zoom_y = (canvas_height - 20) / self.original_image.height
# Use the smaller zoom so image fits completely
self.zoom_level = min(zoom_x, zoom_y, 1.0) # Don't zoom larger than 100%
# Center the image
self.center_image()
self.update_display()

def start_drag(self, event):
"""Start dragging the image"""
self.drag_start_x = event.x
self.drag_start_y = event.y
self.canvas.configure(cursor='fleur') # Four-arrow cursor
def drag_image(self, event):
"""Move the image while dragging"""
if self.original_image:
# Calculate how far the mouse moved
dx = event.x - self.drag_start_x
dy = event.y - self.drag_start_y
# Move the image by that amount
self.image_x += dx
self.image_y += dy
# Update starting position for next movement
self.drag_start_x = event.x
self.drag_start_y = event.y
# Update the display
self.update_display()
def end_drag(self, event):
"""Stop dragging the image"""
self.canvas.configure(cursor='crosshair') # Back to normal cursor



def main():
"""Create and run the image viewer application"""
# Create the main window
root = tk.Tk()
# Create our image viewer
app = ImageZoomApp(root)
# Start the application
root.mainloop()

# Run the program
if __name__ == "__main__":
main()



The Final Result:













Java - Modern Toggle Buttons in Java Swing

How to Create Animated Toggle Buttons in Java Netbeans

How to Create Animated Toggle Buttons in Java Netbeans


In this Java Tutorial we will see How To Create Toggle Buttons with five different styles: Neon, Gradient, Minimal, Cyberpunk, and Neumorphic In Java Using Netbeans.

What We Are Gonna Use In This Project:

- Java Programming Language.
- NetBeans Editor.





Project Source Code:



/**
 *
 * @author 1BestCsharp
 */
public class ModernToggleButtons extends JFrame {

    public ModernToggleButtons(){
        
        // Set the title of the window
        setTitle("Modern Toggle Buttons");
        // Make the application close when the window is closed
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // Set the initial size of the window
        setSize(500, 600);
        // Center the window on the screen
        setLocationRelativeTo(null);
        
        // Create the main panel that will hold all components
        JPanel mainPanel = new JPanel();
        // Set the layout to stack components vertically
        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
        // Set the background color to dark gray (RGB: 26, 26, 26)
        mainPanel.setBackground(new Color(26, 26, 26));
        // Add a 20-pixel empty border around all sides of the panel
        mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
        
        // Add toggle buttons with labels and vertical spacing
        mainPanel.add(createTogglePanel("Neon", new NeonToggleButton()));
        mainPanel.add(Box.createVerticalStrut(30));
        mainPanel.add(createTogglePanel("Gradient", new GradientToggleButton()));
        mainPanel.add(Box.createVerticalStrut(30));
        mainPanel.add(createTogglePanel("Minimal", new MinimalToggleButton()));
        mainPanel.add(Box.createVerticalStrut(30));
        mainPanel.add(createTogglePanel("Cyberpunk", new CyberpunkToggleButton()));
        mainPanel.add(Box.createVerticalStrut(30));
        mainPanel.add(createTogglePanel("Neumorphic", new NeumorphicToggleButton()));
        
        
        // Add the main panel to the JFrame window
        add(mainPanel);
        
    }
    
    
    
    // Method that creates a panel containing a label, toggle button, and status label
    private JPanel createTogglePanel(String label, JToggleButton toggleButton){
        
        // Create a new panel to hold the components
        JPanel panel = new JPanel();
        // Use FlowLayout to arrange components horizontally with 20-pixel horizontal gaps
        panel.setLayout(new FlowLayout(FlowLayout.CENTER, 20, 0));
        // Set the panel's background color to dark gray
        panel.setBackground(new Color(26, 26, 26));
        
        // Create a label with the specified text
        JLabel nameLabel = new JLabel(label);
        // Set the label's text color to white
        nameLabel.setForeground(Color.WHITE);
        // Set the font to Segoe UI, normal style, 24-point size
        nameLabel.setFont(new Font("Segoe UI", Font.PLAIN, 24));
        // Set a fixed size for the label
        nameLabel.setPreferredSize(new Dimension(150, 30));
        
         // Create a label that will show "ON" or "OFF" status
        JLabel statusLabel = new JLabel("OFF");
        // Set the status label's text color to white
        statusLabel.setForeground(Color.WHITE);
        // Set the font to Segoe UI, normal style, 20-point size
        statusLabel.setFont(new Font("Segoe UI", Font.PLAIN, 20));
        // Set a fixed size for the status label
        statusLabel.setPreferredSize(new Dimension(70, 30));
        
        // Check if the toggle button is an instance of our custom BaseToggleButton class
         if (toggleButton instanceof BaseToggleButton) {
             
            // Cast the toggle button to BaseToggleButton to access its specific methods
            BaseToggleButton baseToggle = (BaseToggleButton) toggleButton;
            // Set a callback function that updates the status label when the toggle state changes
            baseToggle.setStatusUpdateCallback(isOn -> {
                
                // Update the text to show "ON" or "OFF"
                statusLabel.setText(isOn ? "ON" : "OFF");
                // Change the text color to green when ON, white when OFF
                statusLabel.setForeground(isOn ? new Color(0, 255, 136) : Color.WHITE);
                
            });
             
         }

        // Add the components to the panel
        panel.add(nameLabel);
        panel.add(toggleButton);
        panel.add(statusLabel);
        
        return panel;
        
    }
    
    
    
    
    // Abstract base class for all custom toggle buttons
    private abstract class BaseToggleButton extends JToggleButton {
        
        // Value between 0.0 (OFF) and 1.0 (ON) for animation progress
        protected float animationProgress = 0.0f;
        // Timer object to handle the animation
        protected Timer animationTimer;
        // Duration of the animation in milliseconds
        protected final int ANIMATION_DURATION = 200;
        // Number of steps in the animation
        protected final int ANIMATION_STEPS = 20;
       // Callback function that will be called when the toggle state changes
        protected Runnable statusUpdateCallback;
        
        public BaseToggleButton(){
            
            // Set the size of the button
            setPreferredSize(new Dimension(120, 60));
            // Remove the default styling
            setBorderPainted(false);
            setFocusPainted(false);
            setContentAreaFilled(false);
            setCursor(new Cursor(Cursor.HAND_CURSOR));
            
            // Add a listener to detect when the button is clicked
            addItemListener(e -> {
                startAnimation(e.getStateChange() == ItemEvent.SELECTED);
            });
            
        }
        
        // Method to set the callback function that updates the status label
        public void setStatusUpdateCallback(Runnable callback) {
            this.statusUpdateCallback = callback;
        }
        
        // Method to start the animation when the button state changes
        protected void startAnimation(boolean targetState){
            
            // Stop any existing animation
            if (animationTimer != null && animationTimer.isRunning()) {
                animationTimer.stop();
            }
            
            // Calculate animation parameters
            final float targetProgress = targetState ? 1.0f : 0.0f;
            final float startProgress = animationProgress;
            final float progressStep = (targetProgress - startProgress) / ANIMATION_STEPS;
            final int delay = ANIMATION_DURATION / ANIMATION_STEPS;
            
            // Create a new timer for the animation
            animationTimer = new Timer(delay, null);
            animationTimer.addActionListener(new ActionListener() {
                
                private int currentStep = 0;
                
                @Override
                public void actionPerformed(ActionEvent e) {
                
                    currentStep++;
                    
                     if (currentStep <= ANIMATION_STEPS) {
                         
                        animationProgress = startProgress + (progressStep * currentStep);
                        repaint();
                         
                     }
                     else{
                        animationProgress = targetProgress;
                        animationTimer.stop();
                         if (statusUpdateCallback != null) {
                            statusUpdateCallback.run(targetState);
                        }
                     }
                    
                }
                
            });
            
            // Start the animation timer
            animationTimer.start();
            
        }
        
        // Interface for the status update callback
        public interface Runnable {
            void run(boolean isOn);
        }
        
    }
    
    
    // Neon-style toggle button implementation
    private class NeonToggleButton extends BaseToggleButton {
        
        @Override
        protected void paintComponent(Graphics g) {
            
            Graphics2D g2 = (Graphics2D) g.create();
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            
            // Define colors for OFF and ON states
            Color bgOffColor = new Color(51, 51, 51);
            Color bgOnColor = new Color(0, 255, 136);
            Color currentBgColor = interpolateColor(bgOffColor, bgOnColor, animationProgress);
            
            // Draw the rounded rectangle background
            g2.setColor(currentBgColor);
            g2.fill(new RoundRectangle2D.Float(0, 0, getWidth(), getHeight(), 60, 60));
            
            // Draw the white circle that moves
            g2.setColor(Color.WHITE);
            int diameter = getHeight() - 8;
            float offX = 4;
            float onX = getWidth() - diameter - 4;
            float currentX = offX + (onX - offX) * animationProgress;
            g2.fillOval(Math.round(currentX), 4, diameter, diameter);
            
            // Add glow effect that increases with animation progress
            int alpha = Math.round(50 * animationProgress);
            if (alpha > 0) {
                g2.setColor(new Color(0, 255, 136, alpha));
                g2.setStroke(new BasicStroke(6));
                g2.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 60, 60);
            }
            
            g2.dispose();
            
        }
        
    }
    
    
   // Gradient-style toggle button implementation
    private class GradientToggleButton extends BaseToggleButton {
        
        @Override
        protected void paintComponent(Graphics g) {
            
            Graphics2D g2 = (Graphics2D) g.create();
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            
            // Define colors for OFF and ON states
            Color offStartColor = new Color(255, 107, 107);
            Color offEndColor = new Color(254, 202, 87);
            Color onStartColor = new Color(72, 52, 212);
            Color onEndColor = new Color(104, 109, 224);
            
            // Calculate the current colors based on animation progress
            Color currentStartColor = interpolateColor(offStartColor, onStartColor, animationProgress);
            Color currentEndColor = interpolateColor(offEndColor, onEndColor, animationProgress);
            
            // Create a gradient and apply it
            GradientPaint gradient = new GradientPaint(0, 0, currentStartColor,
                                      getWidth(), getHeight(), currentEndColor);
            g2.setPaint(gradient);
            g2.fill(new RoundRectangle2D.Float(0, 0, getWidth(), getHeight(), 60, 60));
            
            // Draw the white circle that moves
            g2.setColor(Color.WHITE);
            int diameter = getHeight() - 8;
            float offX = 4;
            float onX = getWidth() - diameter - 4;
            float currentX = offX + (onX - offX) * animationProgress;
            g2.fillOval(Math.round(currentX), 4, diameter, diameter);
            
            g2.dispose();
        }
        
    }
    
    
     // Minimal-style toggle button implementation
     private class MinimalToggleButton extends BaseToggleButton {  
         
         @Override
         protected void paintComponent(Graphics g) {
             
            Graphics2D g2 = (Graphics2D) g.create();
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            
            // Define colors for OFF and ON states
            Color offColor = Color.WHITE;
            Color onColor = new Color(0, 255, 136);
            Color currentColor = interpolateColor(offColor, onColor, animationProgress); 
            
            // Draw the border of the toggle button
            g2.setColor(currentColor);
            g2.setStroke(new BasicStroke(3));
            g2.draw(new RoundRectangle2D.Float(1, 1, getWidth() - 3, getHeight() - 3, 60, 60));
            
            // Draw the circle that moves
            g2.setColor(currentColor);
            int diameter = getHeight() - 12;
            float offX = 6;
            float onX = getWidth() - diameter - 6;
            float currentX = offX + (onX - offX) * animationProgress;
            g2.fillOval(Math.round(currentX), 6, diameter, diameter);
            
            g2.dispose();
            
         }
         
     }
    
    
     
     // Cyberpunk-style toggle button implementation
     private class CyberpunkToggleButton extends BaseToggleButton {
         
         @Override
         protected void paintComponent(Graphics g) {
             
            Graphics2D g2 = (Graphics2D) g.create();
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            
            // Draw the dark background with less rounded corners
            g2.setColor(new Color(45, 52, 54));
            g2.fill(new RoundRectangle2D.Float(0, 0, getWidth(), getHeight(), 10, 10));
            
             // Define colors for OFF and ON states
            Color offColor = new Color(255, 0, 255); // Magenta
            Color onColor = new Color(0, 255, 255);  // Cyan
            Color currentColor = interpolateColor(offColor, onColor, animationProgress);
            
             // Draw a thin border around the toggle
            g2.setColor(currentColor);
            g2.setStroke(new BasicStroke(2));
            g2.draw(new RoundRectangle2D.Float(1, 1, getWidth() - 3, getHeight() - 3, 10, 10));
            
            // Create a gradient for the slider
            GradientPaint gradient = new GradientPaint(0, 0, new Color(255, 0, 255),
                                      getWidth(), getHeight(), new Color(0, 255, 255));
            g2.setPaint(gradient);
            
            // Draw the slider as a rounded rectangle
            float offX = 4;
            float onX = getWidth() - 52;
            float currentX = offX + (onX - offX) * animationProgress;
            g2.fill(new RoundRectangle2D.Float(currentX, 4, 48, getHeight() - 8, 5, 5));
            
            g2.dispose();
             
         }
         
     }
     
     
     // Neumorphic-style toggle button implementation
     private class NeumorphicToggleButton extends BaseToggleButton {
         
         @Override
         protected void paintComponent(Graphics g) {
             
            Graphics2D g2 = (Graphics2D) g.create();
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            
            // Store dimensions
            int width = getWidth();
            int height = getHeight();
            int cornerRadius = 60;
            
            // Define colors for the neumorphic effect
            Color baseColor = new Color(26, 26, 26);
            Color lightShadow = new Color(40, 40, 40);
            Color darkShadow = new Color(10, 10, 10);
            
            // Draw the base shape
            g2.setColor(baseColor);
            g2.fill(new RoundRectangle2D.Float(0, 0, width, height, cornerRadius, cornerRadius));
            
            // Draw the lighter edge on top-left
            g2.setColor(lightShadow);
            g2.setStroke(new BasicStroke(2f));
            g2.draw(new RoundRectangle2D.Float(1, 1, width-2, height-2, cornerRadius, cornerRadius));
            
            // Draw the darker edge on bottom-right
            g2.setColor(darkShadow);
            g2.setStroke(new BasicStroke(2f));
            g2.draw(new RoundRectangle2D.Float(3, 3, width-6, height-6, cornerRadius, cornerRadius));
            
            // Draw the inner background
            g2.setColor(new Color(22, 22, 22));
            g2.fill(new RoundRectangle2D.Float(4, 4, width-8, height-8, cornerRadius-8, cornerRadius-8));
            
            // Define colors for OFF and ON states
            Color offStartColor = new Color(30, 30, 30);
            Color offEndColor = new Color(34, 34, 34);
            Color onStartColor = new Color(0, 255, 136);
            Color onEndColor = new Color(0, 204, 106);
            
            // Calculate the current colors based on animation progress
            Color currentStartColor = interpolateColor(offStartColor, onStartColor, animationProgress);
            Color currentEndColor = interpolateColor(offEndColor, onEndColor, animationProgress);
            
            // Create a gradient and apply it
            GradientPaint gradient = new GradientPaint(0, 0, currentStartColor,
                                      width, height, currentEndColor);
            
            // Position and draw the toggle circle
            g2.setPaint(gradient);
            int diameter = height - 16;
            float offX = 8;
            float onX = width - diameter - 8;
            float currentX = offX + (onX - offX) * animationProgress;
            
            // Draw the circle shadow with slight offset
            g2.setColor(darkShadow);
            g2.fillOval(Math.round(currentX)+2, 6, diameter, diameter);
            
            // Draw the main circle with gradient
            g2.setPaint(gradient);
            g2.fillOval(Math.round(currentX), 8, diameter, diameter);
            
            // Add highlight to the circle
            g2.setColor(new Color(255, 255, 255, 30));
            g2.fillArc(Math.round(currentX), 8, diameter, diameter, 45, 180);
            
            g2.dispose();
             
         }
         
     }
     
     
     
     
    // Method to blend between two colors based on a ratio (0.0 to 1.0)
    private Color interpolateColor(Color c1, Color c2, float ratio) {
        
        int red = Math.round(c1.getRed() * (1 - ratio) + c2.getRed() * ratio);
        int green = Math.round(c1.getGreen() * (1 - ratio) + c2.getGreen() * ratio);
        int blue = Math.round(c1.getBlue() * (1 - ratio) + c2.getBlue() * ratio);
        
        return new Color(red, green, blue);
        
    }
    
    
    
    public static void main(String[] args) {
     
        SwingUtilities.invokeLater(() ->{
        
          try {
                // Set the application's look and feel to match the OS
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch (Exception e) {
                e.printStackTrace();
            }
          
          // Create and display the frame
          new ModernToggleButtons().setVisible(true);
            
        });
        
    }
    
}


  


The Final Result:



Modern Toggle Buttons in Java Swing (OFF)

Modern Toggle Buttons in Java Swing (ON)