Zoom to Fit, with a toolbar icon beside the zoom controls
Fits the whole painted sheet in the visible canvas and scrolls it into view, from the View menu, Ctrl/Cmd+9, or the four-corners button after the zoom percentage. The ladder gains 25% and 33% at the bottom, so a wide sheet has somewhere to fit into. Fit is the one scale that is not on the ladder -- a sheet fits at whatever scale it fits at -- so zoom_step now takes the first level above or below the scale in force rather than the neighbour of the nearest rung. The sheet is measured from item coordinates rather than canvas.bbox(), which is inflated by the outline stroke: a stroke is the same two pixels at every zoom, so a bbox divided back to 1:1 reports a slightly smaller sheet the further in it is zoomed, and fitting an already fitted sheet crept from 288% to 291% instead of standing still. Anything painted left of or above the canvas origin is measured from the origin, since no amount of scrolling brings it into view. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -176,6 +176,34 @@ def do_zoom_reset(event=None):
|
||||
return "break"
|
||||
|
||||
|
||||
def do_zoom_fit(event=None):
|
||||
"""Zoom so the whole sheet is visible, and scroll it into view.
|
||||
|
||||
The fitted scale is not one of the ladder's, so the percentage on the toolbar is
|
||||
where it is read off; stepping in or out from there rejoins the ladder.
|
||||
"""
|
||||
canvas = app_state.canvas
|
||||
level = wiresheet.fit_zoom_level()
|
||||
bounds = wiresheet.sheet_content_bounds()
|
||||
|
||||
if level is None or bounds is None:
|
||||
app_state.set_status("Nothing to fit — open a sheet with components first.")
|
||||
return "break"
|
||||
|
||||
applied = wiresheet.set_zoom(level)
|
||||
update_zoom_label()
|
||||
|
||||
# Bring the sheet's own top-left corner to the top-left of the view, since a fitted
|
||||
# sheet is measured from there rather than from the canvas origin.
|
||||
region = str(canvas.cget("scrollregion")).split()
|
||||
if len(region) == 4:
|
||||
span_x = float(region[2]) or 1.0
|
||||
span_y = float(region[3]) or 1.0
|
||||
canvas.xview_moveto(max(0.0, (bounds[0] * applied - wiresheet.FIT_PADDING) / span_x))
|
||||
canvas.yview_moveto(max(0.0, (bounds[1] * applied - wiresheet.FIT_PADDING) / span_y))
|
||||
return "break"
|
||||
|
||||
|
||||
def on_canvas_wheel_zoom(event):
|
||||
"""Ctrl + wheel over the sheet, the gesture every other editor uses for zoom."""
|
||||
if not event.delta:
|
||||
@@ -1251,6 +1279,8 @@ SHORTCUTS = [
|
||||
("KP_Subtract", do_zoom_out),
|
||||
("0", do_zoom_reset),
|
||||
("KP_0", do_zoom_reset),
|
||||
("9", do_zoom_fit),
|
||||
("KP_9", do_zoom_fit),
|
||||
]
|
||||
for key, handler in SHORTCUTS:
|
||||
window.bind_all(f"<Command-{key}>", handler)
|
||||
@@ -1313,6 +1343,7 @@ menu_view.add_separator()
|
||||
menu_view.add_command(label="Zoom In", command=do_zoom_in, accelerator=f"{accel_mod}++")
|
||||
menu_view.add_command(label="Zoom Out", command=do_zoom_out, accelerator=f"{accel_mod}+-")
|
||||
menu_view.add_command(label="Zoom 100%", command=do_zoom_reset, accelerator=f"{accel_mod}+0")
|
||||
menu_view.add_command(label="Zoom to Fit", command=do_zoom_fit, accelerator=f"{accel_mod}+9")
|
||||
menu_view.add_separator()
|
||||
menu_view.add_command(label="Tidy Layout", command=do_tidy_layout, accelerator="Ctrl+W")
|
||||
menu_bar.add_cascade(label="View", menu=menu_view)
|
||||
@@ -1357,6 +1388,9 @@ zoom_label = ttk.Label(toolbar, text="100%", width=6, anchor="center")
|
||||
zoom_label.pack(side=tk.LEFT, padx=1)
|
||||
ttk.Button(toolbar, text="+", width=3, command=do_zoom_in).pack(side=tk.LEFT)
|
||||
|
||||
# U+26F6, the four-corners frame every other editor marks "fit to window" with.
|
||||
ttk.Button(toolbar, text="⛶", width=3, command=do_zoom_fit).pack(side=tk.LEFT, padx=(3, 0))
|
||||
|
||||
paned_window = ttk.PanedWindow(window, orient=tk.HORIZONTAL)
|
||||
paned_window.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user