Python Forum

Full Version: Help with Zoom Python
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Here is my code, and I want when I scale my grid, it will scale where the cursor is and smoothly move to the middle of the screen. Similar to how AutoCAD does it. Basically I want the cursor to be my zoom focus. For now it scales by making the center of the canvas the zoom focus.

import tkinter as tk
import json


def create_grid(canvas, grid_size, color="black"):
    """Draws a grid on the canvas."""
    canvas.delete("grid")
    max_width = max_columns * grid_size
    max_height = max_rows * grid_size

    for x in range(0, max_width + 1, grid_size):
        canvas.create_line(x, 0, x, max_height, fill=color, dash=(1, 3), tags="grid")
    for y in range(0, max_height + 1, grid_size):
        canvas.create_line(0, y, max_width, y, fill=color, dash=(1, 3), tags="grid")

    canvas.config(scrollregion=(0, 0, max_width, max_height))  # Limit scrolling


def add_text_to_grid(canvas, row, col, text, font=("Arial", 12)):
    """Adds a text element aligned to the grid, within limits."""
    if 0 <= row < max_rows and 0 <= col < max_columns:
        x = col * grid_size
        y = row * grid_size
        item = canvas.create_text(x, y, text=text, font=font, tags="grid_text", anchor="center")
        text_items.append({"item": item, "row": row, "col": col})
    else:
        print("Cannot place text outside the grid limits.")


def update_text_positions():
    """Updates text positions when the grid size changes."""
    for text in text_items:
        row, col = text["row"], text["col"]
        if 0 <= row < max_rows and 0 <= col < max_columns:
            x = col * grid_size
            y = row * grid_size
            canvas.coords(text["item"], x, y)


def on_mouse_wheel(event):
    """Zooms in or out with the mouse wheel, centered at the cursor."""
    global grid_size, offset_x, offset_y

    # Get cursor position relative to canvas
    cursor_x = canvas.canvasx(event.x)
    cursor_y = canvas.canvasy(event.y)

    # Calculate zoom factor
    if event.delta > 0:  # Scroll up
        new_grid_size = max(grid_size - 5, 10)  # Minimum grid size is 10
    else:  # Scroll down
        new_grid_size = min(grid_size + 5, 191)  # Maximum grid size is 191

    # Zoom adjustment ratio
    if new_grid_size != grid_size:
        zoom_ratio = new_grid_size / grid_size

        # Adjust offsets to keep the cursor centered
        offset_x = cursor_x - (cursor_x - offset_x) * zoom_ratio
        offset_y = cursor_y - (cursor_y - offset_y) * zoom_ratio

        grid_size = new_grid_size

    create_grid(canvas, grid_size)
    update_text_positions()

    # Update canvas view
    canvas.xview_moveto(offset_x / (max_columns * grid_size))
    canvas.yview_moveto(offset_y / (max_rows * grid_size))


def on_mouse_drag_start(event):
    """Start dragging the canvas."""
    global start_x, start_y
    start_x = event.x
    start_y = event.y


def on_mouse_drag(event):
    """Handle dragging the canvas."""
    global start_x, start_y, offset_x, offset_y

    dx = start_x - event.x
    dy = start_y - event.y

    # Update offsets with boundaries
    max_width = max_columns * grid_size - canvas.winfo_width()
    max_height = max_rows * grid_size - canvas.winfo_height()

    offset_x = max(0, min(offset_x + dx, max_width))
    offset_y = max(0, min(offset_y + dy, max_height))

    canvas.xview_moveto(offset_x / (max_columns * grid_size))
    canvas.yview_moveto(offset_y / (max_rows * grid_size))

    # Update starting position for smooth dragging
    start_x = event.x
    start_y = event.y


def save_settings():
    """Saves the grid size to a settings file."""
    data = {"grid_size": grid_size}
    with open("settings.json", "w") as f:
        json.dump(data, f)


def load_settings():
    """Loads the grid size from a settings file."""
    try:
        with open("settings.json", "r") as f:
            data = json.load(f)
            return data.get("grid_size", 100)  # Default grid size is 100
    except FileNotFoundError:
        return 100


# Initialize variables
grid_size = load_settings()
text_items = []
start_x = start_y = 0
offset_x = offset_y = 0

# Define maximum rows and columns for the grid
max_rows = 101  # Maximum number of rows
max_columns = 192  # Maximum number of columns

# Create the main window and canvas
window = tk.Tk()
window.title("Zoomable Grid with Cursor-Centered Zoom")

# Set the window to start maximized
window.state("zoomed")

canvas = tk.Canvas(window, bg="white")
canvas.pack(fill="both", expand=True)

# Create grid and add elements
create_grid(canvas, grid_size)
add_text_to_grid(canvas, 4, 5, "Center")  # Aligned to row 4, column 5
add_text_to_grid(canvas, 2, 1, "Random")  # Aligned to row 2, column 1

# Bind events
window.protocol("WM_DELETE_WINDOW", lambda: (save_settings(), window.destroy()))
canvas.bind("<Control-MouseWheel>", on_mouse_wheel)
canvas.bind("<ButtonPress-1>", on_mouse_drag_start)
canvas.bind("<B1-Motion>", on_mouse_drag)

window.mainloop()
        # Adjust offsets to keep the cursor centered
        offset_x = offset_x + cursor_x * (zoom_ratio - 1)
        offset_y = offset_y + cursor_y * (zoom_ratio - 1)
Note that this stops working when shrinking the grid moves a corner of the inside the view window.