import flet as ft
from typing import Any, Optional, cast, Callable
import asyncio
from dataclasses import field
@ft.control
class BaseCanvas(ft.GestureDetector):
max_scale: float = 3
scale_step: float = 0.25
min_scale: float = 0.25
def init(self):
self.cumulative_offset_x = self.cumulative_offset_y = 0
self.current_scale = 1
self.window_container_size = None
self.scaled_canvas_size = ()
self.scale_offset_x = 0
self.scale_offset_y = 0
self.canvas_container = ft.Container(
bgcolor=ft.Colors.BLUE_100,
content=ft.Stack(
controls=[
self.module_container((50, 50)),
self.module_container((50, 600)),
self.module_container((1000, 50)),
self.module_container((1000, 600)),
],
),
)
# self.content = ft.Container(
# expand=True,
# bgcolor=ft.Colors.AMBER,
# clip_behavior=ft.ClipBehavior.HARD_EDGE,
# content=self.canvas_container,
# on_size_change=lambda e: self.get_window_container_size(e),
# )
# ft.Stack
self.content = ft.Container(
expand=True,
bgcolor=ft.Colors.AMBER,
clip_behavior=ft.ClipBehavior.HARD_EDGE,
content=ft.Stack([self.canvas_container]),
on_size_change=lambda e: self.get_window_container_size(e),
)
self.on_scroll = self.canvas_zoom_event
self.on_pan_update = self.drag_canvas_event
self.on_hover = self.mouse_hover_event
def module_container(self, pos):
module = ft.Container(
bgcolor=ft.Colors.GREEN,
width=70,
content=ft.Column(
[
ft.Container(width=70, content=ft.Text(f"项目 {j}"))
for j in range(10)
]
),
)
return ft.GestureDetector(
mouse_cursor=ft.MouseCursor.MOVE,
drag_interval=5,
left=pos[0],
top=pos[1],
content=module,
)
def get_window_container_size(self, e):
self.window_container_size = (e.width, e.height)
print(self.window_container_size)
def canvas_zoom_event(self, e: ft.ScrollEvent[ft.GestureDetector]):
"""Zoom centered on mouse position"""
canvas_width, canvas_height = (
self.window_container_size if self.window_container_size else (500, 600)
)
x_center = canvas_width / 2
y_center = canvas_height / 2
x = (self.mouse_pos_canvas[0] - x_center) / x_center
y = (self.mouse_pos_canvas[1] - y_center) / y_center
if e.scroll_delta.y > 0: # zoom in
if self.current_scale + self.scale_step <= self.max_scale:
self.current_scale += self.scale_step
else:
return
else: # zoom out
if self.current_scale - self.scale_step >= self.min_scale:
self.current_scale -= self.scale_step
else:
return
self.canvas_container.scale = ft.Scale(
alignment=ft.Alignment(x, y), scale=self.current_scale
)
self.scale_offset_x = (self.current_scale - 1) * (
x_center - self.mouse_pos_canvas[0]
)
self.scale_offset_y = (self.current_scale - 1) * (
y_center - self.mouse_pos_canvas[1]
)
mouse_container_x, mouse_container_y = self.mouse_pos_container
mouse_canvas_x, mouse_canvas_y = self.mouse_pos_canvas
self.cumulative_offset_x = (
mouse_container_x - mouse_canvas_x
) / canvas_width
self.cumulative_offset_y = (
mouse_container_y - mouse_canvas_y
) / canvas_height
self.canvas_container.offset = ft.Offset(
self.cumulative_offset_x, self.cumulative_offset_y
)
self.canvas_container.update()
print(f"当前缩放级别:{self.current_scale}")
print(f"当前缩放中心:{(x, y)}")
print(f"当前缩放偏移:{self.scale_offset_x, self.scale_offset_y}")
def drag_canvas_event(self, e: ft.DragUpdateEvent[ft.GestureDetector]):
"""Handle canvas dragging, update offset"""
if e.local_delta is None:
return
self.cumulative_offset_x += (
e.local_delta.x / self.window_container_size[0]
if self.window_container_size
else 500
)
self.cumulative_offset_y += (
e.local_delta.y / self.window_container_size[1]
if self.window_container_size
else 600
)
self.canvas_top_left_pos = self.mouse_pos_convert(
(0, 0), convert_type="canvas_to_container"
)
print(f"当前画布左上角在容器中的位置:{self.canvas_top_left_pos}")
self.canvas_container.offset = ft.Offset(
self.cumulative_offset_x, self.cumulative_offset_y
)
print(f"当前偏移量:{self.cumulative_offset_x, self.cumulative_offset_y}")
self.canvas_container.update()
def mouse_hover_event(self, e: ft.HoverEvent[ft.GestureDetector]) -> None:
"""Handle mouse hover, compute canvas coordinates"""
if e.local_delta is None:
return
mouse_container_x = e.local_position.x
mouse_container_y = e.local_position.y
self.mouse_pos_container = (mouse_container_x, mouse_container_y)
self.mouse_pos_canvas = self.mouse_pos_convert(
self.mouse_pos_container, convert_type="container_to_canvas"
)
def mouse_pos_convert(self, pos, convert_type="container_to_canvas"):
x, y = pos
canvas_width, canvas_height = (
self.window_container_size if self.window_container_size else (500, 600)
)
scaled_width = float(canvas_width) * self.current_scale
scaled_height = float(canvas_height) * self.current_scale
actual_left = (
(self.cumulative_offset_x * canvas_width)
+ self.scale_offset_x
+ (canvas_width - scaled_width) / 2
)
actual_top = (
(self.cumulative_offset_y * canvas_height)
+ self.scale_offset_y
+ (canvas_height - scaled_height) / 2
)
if convert_type == "container_to_canvas":
new_x = (x - actual_left) / self.current_scale
new_y = (y - actual_top) / self.current_scale
elif convert_type == "canvas_to_container":
new_x = x * self.current_scale + actual_left
new_y = y * self.current_scale + actual_top
converted_pos = (new_x, new_y)
return converted_pos
def expand_canvas(self):
canvas_width, canvas_height = (
self.window_container_size if self.window_container_size else (500, 600)
)
self.canvas_container.width = canvas_width + 200
self.canvas_container.height = canvas_height + 200
print(f"扩张前{canvas_width, canvas_height}")
print(f"扩张后{self.canvas_container.width, self.canvas_container.height}")
self.window_container_size=(self.canvas_container.width, self.canvas_container.height)
self.canvas_container.update()
if __name__ == "__main__":
def main(page: ft.Page):
canvas = BaseCanvas()
page.add(
ft.Button("+chicun", on_click=lambda e: canvas.expand_canvas()),
ft.Container(expand=True, content=canvas),
)
ft.run(main)
Duplicate Check
Describe the bug
I originally wanted to make an infinite canvas, I have implemented the canvas drag, zoom, offset and other functions, and the last function is missing, that is, to expand the size of the canvas, but I found that when I used the method of self.canvas_container.width=xxx, I found that it was not feasible when I changed the size, why is that? , I remember that there was no bug in the previous version
Code sample
Code
To reproduce
step:
(A screen recording of the reproduction is attached)
Expected behavior
No response
Screenshots / Videos
Captures
Operating System
Windows
Operating system details
11
Flet version
0.84
Regression
Yes, it used to work in a previous Flet version (please specify the version in additional details)
Suggestions
No response
Logs
Logs
[Paste your logs here]Additional details
No response