- Edited
About a week ago, I was trying to create a scalable TextureRect (as canvas) using the mouse wheel towards the cursor position, like those in Photoshop, Krita, etc.
I was able to set the scaling and its position in relation to the canvas, and as for the scroll bars, I was able to adjust the size, but until now There is no proper way to adjust the position in proportion to the position of the canvas
the setup of scene tree on GitHub
extends Control
@onready var canvas: TextureRect = $Canvas
@onready var h_scroll_bar: HScrollBar = $HScrollBar
@onready var v_scroll_bar: VScrollBar = $VScrollBar
var min_zoom = 1.0
var max_zoom = 15.0
var zoom_step = 0.1
var zoom = 1.0
var original_canvas_width: int
var original_canvas_height: int
func _ready():
original_canvas_width = canvas.size.x
original_canvas_height = canvas.size.y
# Initialize the scrollbars when the scene starts
update_scrollbars()
func _input(event):
# Handle zooming with mouse wheel
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
zoom_canvas(event.global_position, zoom_step)
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
zoom_canvas(event.global_position, -zoom_step)
func zoom_canvas(mouse_position: Vector2, zoom_delta: float):
var new_zoom = clamp(zoom + zoom_delta, min_zoom, max_zoom)
if new_zoom == zoom:
return
var scale_factor = new_zoom / zoom
zoom = new_zoom
# Calculate the new scale
var old_size = canvas.size
var new_size = old_size * scale_factor
# Adjust the position so that zooming centers around the mouse
var offset = mouse_position - canvas.position
var zoom_offset = offset * (1 - scale_factor)
canvas.size = new_size
canvas.position += zoom_offset
# Update scrollbars
update_scrollbars()
func update_scrollbars():
var canvas_size_x_ratio: float = (original_canvas_width/ size.x)
var canvas_size_y_ratio: float = (original_canvas_height/ size.y)
h_scroll_bar.page = ( canvas_size_x_ratio / zoom ) * h_scroll_bar.max_value
v_scroll_bar.page = ( canvas_size_y_ratio / zoom ) * v_scroll_bar.max_value
var x = 0.5
var y = 0.5
h_scroll_bar.value = (h_scroll_bar.max_value-h_scroll_bar.page) - (x * (h_scroll_bar.max_value-h_scroll_bar.page))
v_scroll_bar.value = (v_scroll_bar.max_value-v_scroll_bar.page) - (y * (v_scroll_bar.max_value-v_scroll_bar.page))extends Control
@onready var canvas: TextureRect = $Canvas
@onready var h_scroll_bar: HScrollBar = $HScrollBar
@onready var v_scroll_bar: VScrollBar = $VScrollBar
var min_zoom = 1.0
var max_zoom = 15.0
var zoom_step = 0.1
var zoom = 1.0
var original_canvas_width: int
var original_canvas_height: int
func _ready():
original_canvas_width = canvas.size.x
original_canvas_height = canvas.size.y
# Initialize the scrollbars when the scene starts
update_scrollbars()
func _input(event):
# Handle zooming with mouse wheel
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
zoom_canvas(event.global_position, zoom_step)
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
zoom_canvas(event.global_position, -zoom_step)
func zoom_canvas(mouse_position: Vector2, zoom_delta: float):
var new_zoom = clamp(zoom + zoom_delta, min_zoom, max_zoom)
if new_zoom == zoom:
return
var scale_factor = new_zoom / zoom
zoom = new_zoom
# Calculate the new scale
var old_size = canvas.size
var new_size = old_size * scale_factor
# Adjust the position so that zooming centers around the mouse
var offset = mouse_position - canvas.position
var zoom_offset = offset * (1 - scale_factor)
canvas.size = new_size
canvas.position += zoom_offset
# Update scrollbars
update_scrollbars()
func update_scrollbars():
var canvas_size_x_ratio: float = (original_canvas_width/ size.x)
var canvas_size_y_ratio: float = (original_canvas_height/ size.y)
h_scroll_bar.page = ( canvas_size_x_ratio / zoom ) * h_scroll_bar.max_value
v_scroll_bar.page = ( canvas_size_y_ratio / zoom ) * v_scroll_bar.max_value
var x = 0.5
var y = 0.5
h_scroll_bar.value = (h_scroll_bar.max_value-h_scroll_bar.page) - (x * (h_scroll_bar.max_value-h_scroll_bar.page))
v_scroll_bar.value = (v_scroll_bar.max_value-v_scroll_bar.page) - (y * (v_scroll_bar.max_value-v_scroll_bar.page))
The equations for the _scroll_bar.value seem correct to me, but it is necessary to find the appropriate relative value for both x and y so that the positions of the scroll bars are proportional to the scaling position.
Update: I have added additional elements to the code:
extends Control
@onready var canvas: TextureRect = $Canvas
@onready var h_scroll_bar: HScrollBar = $HScrollBar
@onready var v_scroll_bar: VScrollBar = $VScrollBar
var min_zoom = 1.0
var max_zoom = 15.0
var zoom_step = 0.1
var zoom = 1.0
var original_canvas_width: int
var original_canvas_height: int
var virtual_space_h: int
var virtual_space_v: int
var virtual_space_pos: Vector2
var last_mouse_pos: Vector2 = Vector2.ZERO # Store the last mouse position
#var last_mouse_pos: Vector2 = Vector2(size.x/2, size.y/2) # Store the last mouse position
func _ready():
original_canvas_width = canvas.size.x
original_canvas_height = canvas.size.y
virtual_space_h = size.x
virtual_space_v = size.y
virtual_space_pos = Vector2(0,0)
update_scrollbars()
func _input(event):
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
zoom_canvas(event.global_position, zoom_step)
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
zoom_canvas(event.global_position, -zoom_step)
func zoom_canvas(mouse_position: Vector2, zoom_delta: float):
var new_zoom = clamp(zoom + zoom_delta, min_zoom, max_zoom)
if new_zoom == zoom:
return
var scale_factor = new_zoom / zoom
zoom = new_zoom
# Get mouse position relative to canvas before scaling
var mouse_offset = mouse_position - canvas.position
var old_size = canvas.size
var old_space = size
var new_size = old_size * scale_factor
var new_space = old_space * scale_factor
# Adjust the position based on zoom around the mouse cursor
var zoom_offset = mouse_offset * (1 - scale_factor)
canvas.size = new_size
canvas.position += zoom_offset
virtual_space_h = new_space.x
virtual_space_v = new_space.y
virtual_space_pos += zoom_offset
# Update the scrollbars to match the new canvas size and zoom
update_scrollbars()
# Store the last mouse position (relative to the canvas)
last_mouse_pos = mouse_position
func update_scrollbars():
# Calculate the ratio of the canvas size relative to the control size
var canvas_size_x_ratio: float = (original_canvas_width / size.x)
var canvas_size_y_ratio: float = (original_canvas_height / size.y)
# Update the scrollbar page values based on zoom level
h_scroll_bar.page = (canvas_size_x_ratio / zoom) * h_scroll_bar.max_value
v_scroll_bar.page = (canvas_size_y_ratio / zoom) * v_scroll_bar.max_value
# Calculate the relative position adjustments based on the last mouse position
#var x = (last_mouse_pos.x - canvas.position.x) / canvas.size.x
#var y = (last_mouse_pos.y - canvas.position.y) / canvas.size.y
var x = (last_mouse_pos.x - virtual_space_pos.x) / virtual_space_h
var y = (last_mouse_pos.y - virtual_space_pos.y) / virtual_space_v
# Update the scrollbar values based on mouse position relative to canvas
h_scroll_bar.value = (h_scroll_bar.max_value - h_scroll_bar.page) - (x * (h_scroll_bar.max_value - h_scroll_bar.page))
v_scroll_bar.value = (v_scroll_bar.max_value - v_scroll_bar.page) - (y * (v_scroll_bar.max_value - v_scroll_bar.page))extends Control
@onready var canvas: TextureRect = $Canvas
@onready var h_scroll_bar: HScrollBar = $HScrollBar
@onready var v_scroll_bar: VScrollBar = $VScrollBar
var min_zoom = 1.0
var max_zoom = 15.0
var zoom_step = 0.1
var zoom = 1.0
var original_canvas_width: int
var original_canvas_height: int
var virtual_space_h: int
var virtual_space_v: int
var virtual_space_pos: Vector2
var last_mouse_pos: Vector2 = Vector2.ZERO # Store the last mouse position
#var last_mouse_pos: Vector2 = Vector2(size.x/2, size.y/2) # Store the last mouse position
func _ready():
original_canvas_width = canvas.size.x
original_canvas_height = canvas.size.y
virtual_space_h = size.x
virtual_space_v = size.y
virtual_space_pos = Vector2(0,0)
update_scrollbars()
func _input(event):
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
zoom_canvas(event.global_position, zoom_step)
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
zoom_canvas(event.global_position, -zoom_step)
func zoom_canvas(mouse_position: Vector2, zoom_delta: float):
var new_zoom = clamp(zoom + zoom_delta, min_zoom, max_zoom)
if new_zoom == zoom:
return
var scale_factor = new_zoom / zoom
zoom = new_zoom
# Get mouse position relative to canvas before scaling
var mouse_offset = mouse_position - canvas.position
var old_size = canvas.size
var old_space = size
var new_size = old_size * scale_factor
var new_space = old_space * scale_factor
# Adjust the position based on zoom around the mouse cursor
var zoom_offset = mouse_offset * (1 - scale_factor)
canvas.size = new_size
canvas.position += zoom_offset
virtual_space_h = new_space.x
virtual_space_v = new_space.y
virtual_space_pos += zoom_offset
# Update the scrollbars to match the new canvas size and zoom
update_scrollbars()
# Store the last mouse position (relative to the canvas)
last_mouse_pos = mouse_position
func update_scrollbars():
# Calculate the ratio of the canvas size relative to the control size
var canvas_size_x_ratio: float = (original_canvas_width / size.x)
var canvas_size_y_ratio: float = (original_canvas_height / size.y)
# Update the scrollbar page values based on zoom level
h_scroll_bar.page = (canvas_size_x_ratio / zoom) * h_scroll_bar.max_value
v_scroll_bar.page = (canvas_size_y_ratio / zoom) * v_scroll_bar.max_value
# Calculate the relative position adjustments based on the last mouse position
#var x = (last_mouse_pos.x - canvas.position.x) / canvas.size.x
#var y = (last_mouse_pos.y - canvas.position.y) / canvas.size.y
var x = (last_mouse_pos.x - virtual_space_pos.x) / virtual_space_h
var y = (last_mouse_pos.y - virtual_space_pos.y) / virtual_space_v
# Update the scrollbar values based on mouse position relative to canvas
h_scroll_bar.value = (h_scroll_bar.max_value - h_scroll_bar.page) - (x * (h_scroll_bar.max_value - h_scroll_bar.page))
v_scroll_bar.value = (v_scroll_bar.max_value - v_scroll_bar.page) - (y * (v_scroll_bar.max_value - v_scroll_bar.page))
Scaling the canvas to the mouse position is smooth and works well, but the scroll bars still don't sync well with it.