#PyFletRakugaki.py キャンバスに落書き

# flet                   0.80.0
# flet-desktop           0.80.0
# flet-video             0.80.0

import flet as ft
import flet.canvas as cv
import pyautogui
import threading
import io
import sys
import win32clipboard
import time
import os
import shutil


def main(page: ft.Page):
    page.title = 'PyFletRakugaki V1.2'
    
### class ############################
    class ten: #マウスの座標点
        x = 0; y = 0
    
    class sen: #線の太さと色
        futosa = 3
        iro = ft.Colors.BLACK
        
    class kaisi: #イメージロードの有無
        non = 0 #背景画像ロードなしで開始したら1にする
        load = 0 #背景画像ロードして開始したら1にする
    
    class file: #ファイル名
        path = ''
        
    class kaz: #遅延回復で使うtemp画像に付ける連番
        zobun = 0
        
    class myFile: #画像保存でつかうファイル名
        path = ''
        
    class majin: #スクリーンショットを取る範囲のwindow全体からの余白マージン
        #left = 17; top = 160; width = 36; height = 175
        left = 19; top = 162; width = 40; height = 180

### スクリーンショットエリア設定 ###############################################
    def eriaSetei():
        return pyautogui.screenshot(region=
            (int(page.window.left + majin.left), int(page.window.top + majin.top),
                int(page.window.width - majin.width), int(page.window.height - majin.height)))
        

### color button 線の色を変更するボタン##########################################################
    def color_clicked(e):
        e.control.selected = not e.control.selected
        sen.iro = e.control.icon_color
        
    colors = [ft.Colors.BLACK, ft.Colors.BLUE, ft.Colors.BROWN, ft.Colors.CYAN, ft.Colors.GREEN,
        ft.Colors.GREY, ft.Colors.INDIGO, ft.Colors.LIME, ft.Colors.ORANGE, ft.Colors.PINK,
        ft.Colors.PURPLE, ft.Colors.RED, ft.Colors.TEAL, ft.Colors.WHITE, ft.Colors.YELLOW]

    icon_buttons = [
        ft.IconButton(
            tooltip = '線の色',
            icon = ft.Icons.FAVORITE,
            icon_color = color,
            style = ft.ButtonStyle(bgcolor = {'': ft.Colors.GREY_100}),
            on_click = color_clicked ) 
        for color in colors ]
    
### slider 線の太さを変更するスライダー###############################################################    
    slider = ft.Slider(
        min = 1, max = 20, divisions = 20, label = '{value}', value = 3, expand = True, tooltip = '線の太さ')
    
### start and update マウスドラッグの開始とドラッグ##################################################
    def start(e: ft.DragStartEvent): #マウスドラッグ開始
        ten.x = e.local_position.x; ten.y = e.local_position.y
    
    def update(e: ft.DragUpdateEvent): #マウスドラッグ中
        area.shapes.append(            
            cv.Line(ten.x, ten.y, e.local_position.x, e.local_position.y, paint = ft.Paint(
                stroke_width = slider.value, color = sen.iro, stroke_cap = ft.StrokeCap.ROUND))) 
        try: area.update()
        except: pass
        #page.update()
        ten.x = e.local_position.x; ten.y = e.local_position.y

### area 線を描くエリア###########################################################################
    area = cv.Canvas(        
        #[cv.Color(color = ft.Colors.WHITE)], #←これを指定すると背景が白い色になる、指定しなければ背景は透明。
        content = ft.GestureDetector(on_pan_start = start, on_pan_update = update, drag_interval = 20),
        expand = True)

### undo 描画をもとに戻す#######################################################################
    def undo_last_shape(e):
        try:area.shapes.pop() #描画エリアでドラッグした線を1ステップ戻す
        except: pass
        page.update()

    undo_button = ft.Button('描画をもとに戻す', on_click=undo_last_shape)
    
### スクリーンショットの画像をクリップボードへコピーする ##################################################
    def copy_to_clipboard():
        def handle_close(e):            
            page.pop_dialog() #最前面のダイアログを閉じる
      
        copyShot = eriaSetei()
        original_image = copyShot #Image.open('screenshot.png') #スクリーンショット画像を開く        
        output = io.BytesIO()
        original_image.convert('RGB').save(output, 'BMP') #メモリストリームにBMP形式で保存してから読み出す
        data = output.getvalue()[14:]
        output.close()
        #クリップボードをクリアして、データをセットする
        win32clipboard.OpenClipboard();
        win32clipboard.EmptyClipboard(); win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
        win32clipboard.CloseClipboard()
    
        dlg = ft.AlertDialog(modal=True, title=ft.Text('PyFletVideo'),
            content=ft.Text(f'画像をクリップボードへコピーしました。'),
            actions=[ft.TextButton("閉じる", on_click = handle_close)])        
        page.show_dialog(dlg)
        page.update()    
    
    copy_button = ft.Button('画像コピー', on_click = copy_to_clipboard, tooltip = 'スクリーンショットをクリップボードへコピーする')

### 遅延回復処理 ####################################################
    #cv.Canvasのshapes.appendしていくうちに処理がだんだん重くなってくるので、いったんスクリーン画像をファイルにセーブしてから
    #shapes.clear()したら、セーブしておいたスクリーン画像を表示させて、ふたたび遅延なく描画できるようにする。    
    def tien_kaifuku():
        tempShot = eriaSetei()
        try: os.makedirs('temp', exist_ok=True) #PyFletRakugakiの実行カレントフォルダ配下に'temp'フォルダを作成する
        except: pass
        image_path = 'temp/temp_screenshot' + str(kaz.zobun) + '.png' #画像保存ファイル名に連番を付加する
        try: os.remove(image_path) #前の画像保存ファイルを削除する
        except: pass
        kaz.zobun += 1 #連番を1増やす
        image_path = 'temp/temp_screenshot' + str(kaz.zobun) + '.png' #連番が1増えたファイル名
        tempShot.save(image_path) #連番が1増えたファイル名で画像を保存する
        area.shapes.clear() #キャンバスが増え続けたappendで重くなっているので、クリアして軽くする
        try: page.controls.pop(2) #pageコントロールの「描画エリア」を削除する
        except: pass        
        page.add(ft.Stack([ft.Image(src = image_path), area], expand=True)) #連番が1増えたファイル名の画像を表示させる
        page.update()
        
    kaifuku_button = ft.Button('遅延回復', on_click = tien_kaifuku, tooltip = '処理が重くなった場合にクリックしてください')      

### 白紙のスクリーンショットを撮って'canvas_screenshot.png'というファイル名でカレントディレクトリに保存#####
    def white_screenshot():
        scShot = eriaSetei()
        myFile.path = 'white_screenshot.png'
        scShot.save(myFile.path)
        myFile.path = 'canvas_screenshot.png'

### screenshot スクリーンショットを撮って'canvas_screenshot.png'というファイル名でカレントディレクトリに保存する#####
    def save_screenshot():        
        global screenshot        
        def handle_close(e):            
            page.pop_dialog() # 最前面のダイアログを閉じる
        screenshot = eriaSetei()
        screenshot.save(myFile.path)
        dlg = ft.AlertDialog(modal=True, title=ft.Text('PyFletVideo'),
            content=ft.Text(f'画像が {myFile.path} に保存されました。'),
            actions=[ft.TextButton("閉じる", on_click = handle_close)])        
        page.show_dialog(dlg)
        page.update()
        
    shot_button = ft.Button('スクリーンショット', on_click = lambda e: threading.Thread(target=save_screenshot()).start())

### image load カレントディレクトリの'open_image.png'画像を読み込んでエリアに表示する############    
    def image_load(e):        
        white_screenshot()
        try: page.controls.pop(2) #pageコントロールの「描画エリア」を削除する
        except: pass
        my_image = ft.Image(src = 'open_image.png')
        page.add(ft.Stack([my_image, area], expand=True))
        
    load_button = ft.Button('画像ロードして始める', on_click=image_load)

### non_load 背景画像読み込み無しで新規画像エリアを準備する ########################################
    def non_load(e):
        white_screenshot()
        try: page.controls.pop(2) #pageコントロールの「描画エリア」を削除する
        except: pass
        #print(f'mae 現在のページコントロール: {page.controls}') #チェック用           
        page.add(area)
        #print(f'ato 現在のページコントロール: {page.controls}') #チェック用
        
    non_load_button = ft.Button('画像ロードなしで始める', on_click = non_load)

### clear_canvas 描画エリアの線をクリアする######################################################
    def clear_canvas(e):
        try: area.shapes.clear(); area.update()            
        except: pass
        try: page.controls.pop(2) #pageコントロールの「描画エリア」を削除する
        except: pass
        try:
            my_image = ft.Image(src = 'white_screenshot.png')
            page.add(ft.Stack([my_image, area], expand=True))
        except:pass
        page.update()
        
    clear_button = ft.Button('描画をクリアする', on_click = clear_canvas)        

### page.add ページにボタンを配置する
    # 描画エリアクリア、背景画像なしの新規エリア、背景画像読み込みの新規エリア、描画を元に戻す、画像コピー、スクリーンショット、遅延回復
    # 線の太さスライダー、色を選ぶカラーボタン
    page.add(
        ft.Row([clear_button, non_load_button, load_button, undo_button, copy_button, shot_button, kaifuku_button]),
        ft.Row([ft.Container(content = slider, width = 200, padding = 10), (ft.Row(controls = icon_buttons, wrap = True))]))
    
    if page.window.visible:
        pass
        #ここにwindowが開始されたすぐの処理を書く ###将来用・未使用###
        #print("Window is now shown (visible)")

    def window_on_close(e):
        try:shutil.rmtree('temp') #遅延回復のために作成したtempフォルダを削除する
        except: pass
    
### app アプリケーションの開始#######################################################################  
    page.on_close = window_on_close
ft.app(target=main)
