""" ピンチ操作ハンドラー アプリ全体でピンチイン・ズーム機能を提供する共通コンポーネント """ import flet as ft from typing import Optional, Callable class PinchHandler: """ピンチ操作ハンドラー""" def __init__(self, page: ft.Page): self.page = page self.zoom_level = 1.0 self.min_zoom = 0.6 self.max_zoom = 2.0 self.zoom_step = 0.2 # コールバック関数 self.on_zoom_change: Optional[Callable[[float], None]] = None self.on_tap: Optional[Callable] = None self.on_double_tap: Optional[Callable] = None self.on_long_press: Optional[Callable] = None # 状態管理 self.last_distance = 0 self.is_zooming = False def set_callbacks(self, on_zoom_change: Optional[Callable[[float], None]] = None, on_tap: Optional[Callable] = None, on_double_tap: Optional[Callable] = None, on_long_press: Optional[Callable] = None): """コールバック関数設定""" self.on_zoom_change = on_zoom_change self.on_tap = on_tap self.on_double_tap = on_double_tap self.on_long_press = on_long_press def create_gesture_detector(self, content: ft.Control, on_click: Optional[Callable] = None) -> ft.GestureDetector: """ジェスチャー検出付きコンテナ作成""" return ft.GestureDetector( content=content, on_tap=on_click ) def _handle_tap(self, e): """タップ処理""" if self.on_tap: self.on_tap(e) def _handle_double_tap(self, e): """ダブルタップ処理""" self.zoom_in() if self.on_double_tap: self.on_double_tap(e) def _handle_long_press(self, e): """長押し処理""" if self.on_long_press: self.on_long_press(e) def _handle_tap_update(self, e): """タップ位置更新(ピンチ検出用)""" # TODO: 実際のピンチ検出ロジック # Fletの制限により、現在はボタンでのズームをメインに pass def zoom_in(self): """ズームイン""" if self.zoom_level < self.max_zoom: self.zoom_level += self.zoom_step self._notify_zoom_change() def zoom_out(self): """ズームアウト""" if self.zoom_level > self.min_zoom: self.zoom_level -= self.zoom_step self._notify_zoom_change() def set_zoom(self, level: float): """ズームレベル設定""" self.zoom_level = max(self.min_zoom, min(self.max_zoom, level)) self._notify_zoom_change() def reset_zoom(self): """ズームリセット""" self.zoom_level = 1.0 self._notify_zoom_change() def _notify_zoom_change(self): """ズーム変更通知""" if self.on_zoom_change: self.on_zoom_change(self.zoom_level) def get_zoom_controls(self) -> ft.Row: """ズームコントロールUI作成""" return ft.Row([ ft.IconButton( ft.Icons.ZOOM_OUT, icon_size=20, tooltip="縮小", on_click=lambda _: self.zoom_out() ), ft.Text(f"{int(self.zoom_level * 100)}%", size=12), ft.IconButton( ft.Icons.ZOOM_IN, icon_size=20, tooltip="拡大", on_click=lambda _: self.zoom_in() ), ft.IconButton( ft.Icons.REFRESH, icon_size=20, tooltip="リセット", on_click=lambda _: self.reset_zoom() ) ], spacing=5) def apply_zoom_to_size(self, base_size: float) -> float: """ズームをサイズに適用""" return base_size * self.zoom_level def apply_zoom_to_text_size(self, base_size: float) -> float: """ズームをテキストサイズに適用""" return base_size * self.zoom_level class ZoomableContainer: """ズーム対応コンテナ""" def __init__(self, pinch_handler: PinchHandler): self.pinch_handler = pinch_handler self.base_width = 100 self.base_height = 100 self.base_text_size = 12 self.base_padding = 10 def create_zoomable_card(self, icon: str, title: str, subtitle: str, color: ft.Colors, on_click: Optional[Callable] = None) -> ft.Container: """ズーム対応カード作成""" # ズーム適用 width = self.pinch_handler.apply_zoom_to_size(self.base_width) height = self.pinch_handler.apply_zoom_to_size(self.base_height) text_size = self.pinch_handler.apply_zoom_to_text_size(self.base_text_size) padding = self.pinch_handler.apply_zoom_to_size(self.base_padding) card = ft.Container( content=ft.Column([ ft.Container( content=ft.Text(icon, size=text_size * 2), width=width * 0.5, height=height * 0.5, bgcolor=color, alignment=ft.alignment.Alignment(0, 0), border_radius=padding ), ft.Text(title, size=text_size, weight=ft.FontWeight.BOLD), ft.Text(subtitle, size=text_size * 0.8, color=ft.Colors.GREY_600) ], spacing=5), width=width, height=height, padding=padding, bgcolor=ft.Colors.WHITE, border_radius=padding, shadow=ft.BoxShadow( spread_radius=1, blur_radius=5, color=ft.Colors.with_opacity(0.2, ft.Colors.GREY), offset=ft.Offset(0, 2) ), on_click=on_click ) return self.pinch_handler.create_gesture_detector(card)