# -*- coding: utf-8 -*-
"""画图板 超级版 v5.1 —— 修好"能画"的完整版"""
import tkinter as tk
from tkinter import ttk, colorchooser, filedialog, messagebox, simpledialog
import math, copy, os

try:
    from PIL import Image, ImageDraw, ImageFont
    _PIL = True
except Exception:
    _PIL = False


def _bind(root, seq, cb):
    try:
        root.bind_all(seq, cb)
    except tk.TclError:
        root.bind(seq, cb)


TOOLS = ["select", "pen", "line", "rect", "rrect", "oval", "arrow",
         "star", "dash", "text", "eraser", "eyed", "pan"]
ICON = dict(select="↖", pen="✎", line="╱", rect="▭", rrect="▢", oval="◯",
            arrow="→", star="★", dash="┈", text="T", eraser="⌫", eyed="💧", pan="✋")
TIP = dict(select="选择/移动(V)", pen="画笔(B)", line="直线", rect="矩形(R)",
           rrect="圆角矩形", oval="椭圆(O)", arrow="箭头", star="星形",
           dash="虚线", text="文字(T)", eraser="橡皮", eyed="取色(I)", pan="平移(H)")
PAL = ["#000", "#FF3B30", "#FF9500", "#FFCC00", "#4CD964",
       "#5AC8FA", "#007AFF", "#5856D6", "#FF2D92", "#8E8E93", "#FFF"]


class Sh:
    def __init__(self, k, x1, y1, x2, y2, **kw):
        self.k = k; self.x1 = x1; self.y1 = y1
        self.x2 = x2; self.y2 = y2
        self.out = kw.get("out", "#222")
        self.fill = kw.get("fill", "")
        self.w = kw.get("w", 3)
        self.r = kw.get("r", 16)
        self.pts = kw.get("pts", 5)
        self.ratio = kw.get("ratio", 0.5)
        self.txt = kw.get("txt", "")
        self.ff = kw.get("ff", "Microsoft YaHei")
        self.fsz = kw.get("fsz", 18)
        self.vis = True; self.lock = False
        self.path = kw.get("path", None)     # 画笔用

    def bbox(self):
        return (min(self.x1, self.x2), min(self.y1, self.y2),
                max(self.x1, self.x2), max(self.y1, self.y2))

    def move(self, dx, dy):
        self.x1 += dx; self.y1 += dy; self.x2 += dx; self.y2 += dy

    def hit(self, x, y):
        a, b, c, d = self.bbox()
        return a - 6 <= x <= c + 6 and b - 6 <= y <= d + 6

    def clone(self):
        return copy.deepcopy(self)


class Layer:
    def __init__(self, name):
        self.name = name; self.shapes = []; self.vis = True


class DrawingPad(tk.Frame):
    def __init__(self, master):
        super().__init__(master, bg="#1e1e2e")
        self.pack(fill=tk.BOTH, expand=True)
        self.m = master
        self.C = dict(bg="#1e1e2e", pan="#181825", fg="#cdd6f4",
                      ac="#89b4fa", it="#45475a")
        self.tool = "pen"; self.color = "#007AFF"; self.fillc = "#FFCC00"
        self.sw = 3; self.rr = 16
        self.zoom = 1.0; self.hx = self.hy = 0
        self.cw, self.ch = 1600, 1000
        self.down = False; self.drag = False
        self.sx = self.sy = 0; self.tmp = None
        self.sel = []; self.before = []
        self.pan = False; self.space = False; self._old = "pen"
        self.layers = [Layer("背景"), Layer("图层 1")]; self.cl = 1
        self.undo_s = []; self.redo_s = []; self.clip = []
        self._bgimg = None

        self._menu(); self._bar(); self._prop(); self._cv()
        self._right(); self._stat(); self._wire()
        self._set_tool("pen")
        self.zoom_fit()

    # ---------------- 界面 ----------------
    def _menu(self):
        mb = tk.Menu(self.m); self.m.config(menu=mb)
        f = tk.Menu(mb, tearoff=0)
        f.add_command(label="打开底图…", command=self._open_img)
        f.add_separator()
        f.add_command(label="导出 PNG", command=lambda: self._export("PNG"))
        f.add_command(label="导出 JPG", command=lambda: self._export("JPG"))
        mb.add_cascade(label="文件", menu=f)
        e = tk.Menu(mb, tearoff=0)
        e.add_command(label="撤销", command=self.undo)
        e.add_command(label="重做", command=self.redo)
        e.add_separator()
        e.add_command(label="复制", command=self.copy)
        e.add_command(label="粘贴", command=self.paste)
        e.add_command(label="删除", command=self.del_sel)
        mb.add_cascade(label="编辑", menu=e)

    def _bar(self):
        b = tk.Frame(self, bg=self.C["pan"], width=56)
        b.pack(side=tk.LEFT, fill=tk.Y); b.pack_propagate(False)
        self._btns = {}
        for t in TOOLS:
            l = tk.Label(b, text=ICON[t], bg=self.C["it"], fg="white",
                         font=("Segoe UI Emoji", 15), width=3, cursor="hand2")
            l.pack(pady=1)
            l.bind("<1>", lambda e, x=t: self._set_tool(x))
            l.bind("<Enter>", lambda e, x=TIP[t]: self._say(x))
            l.bind("<Leave>", lambda e: self._say("就绪"))
            self._btns[t] = l

    def _prop(self):
        p = tk.Frame(self, bg=self.C["pan"], width=196)
        p.pack(side=tk.LEFT, fill=tk.Y); p.pack_propagate(False)
        tk.Label(p, text="属性", bg=self.C["pan"], fg=self.C["fg"]).pack(pady=3)
        self._ob = tk.Button(p, bg=self.color, width=4, command=lambda: self._pick("out"))
        self._ob.pack(pady=2)
        self._fb = tk.Button(p, bg=self.fillc, width=4, command=lambda: self._pick("fill"))
        self._fb.pack(pady=2)
        self._wv = tk.IntVar(value=3); self._rv = tk.IntVar(value=16)
        tk.Spinbox(p, from_=1, to=40, textvariable=self._wv, width=5).pack(pady=1)
        tk.Spinbox(p, from_=0, to=80, textvariable=self._rv, width=5).pack(pady=1)
        self._fv = tk.StringVar(value="Microsoft YaHei")
        ttk.Combobox(p, textvariable=self._fv, width=14, state="readonly",
                     values=["Microsoft YaHei", "SimHei", "KaiTi", "Arial"]).pack(pady=4)
        pal = tk.Frame(p, bg=self.C["pan"]); pal.pack()
        for i, c in enumerate(PAL):
            tk.Button(pal, bg=c, width=2,
                      command=lambda x=c: self._setc(x)).grid(row=i // 6, column=i % 6, padx=1, pady=1)

    def _cv(self):
        f = tk.Frame(self, bg="#3a3a4a")
        f.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=3, pady=3)
        self.canvas = tk.Canvas(f, bg="#ffffff", highlightthickness=0)
        self.canvas.pack(fill=tk.BOTH, expand=True)

    def _right(self):
        r = tk.Frame(self, bg=self.C["pan"], width=172)
        r.pack(side=tk.LEFT, fill=tk.Y); r.pack_propagate(False)
        tk.Label(r, text="图层", bg=self.C["pan"], fg=self.C["fg"]).pack(pady=3)
        self._ll = tk.Listbox(r, bg=self.C["it"], fg=self.C["fg"], selectbackground=self.C["ac"])
        self._ll.pack(fill=tk.X, padx=4)
        self._ll.bind("<<ListboxSelect>>", self._onlayer)
        tk.Button(r, text="+ 新建图层", command=self._nl).pack(fill=tk.X, padx=4, pady=1)
        tk.Button(r, text="− 删除图层", command=self._dl).pack(fill=tk.X, padx=4, pady=1)
        tk.Label(r, text="背景", bg=self.C["pan"], fg=self.C["fg"]).pack(pady=(8, 2))
        self._bg = tk.StringVar(value="solid")
        for t, v in [("纯色", "solid"), ("网格", "grid"), ("底图", "img")]:
            tk.Radiobutton(r, text=t, variable=self._bg, value=v, bg=self.C["pan"],
                           fg=self.C["fg"], selectcolor=self.C["pan"],
                           command=self._onbg).pack(anchor=tk.W, padx=8)
        self._hist = tk.Listbox(r, bg=self.C["it"], fg=self.C["fg"])
        self._hist.pack(fill=tk.BOTH, expand=True, padx=4, pady=6)

    def _stat(self):
        self._st = tk.Label(self, text="就绪 · v5.1", bg=self.C["it"],
                            fg=self.C["fg"], anchor=tk.W)
        self._st.pack(side=tk.BOTTOM, fill=tk.X)

    def _wire(self):
        c = self.canvas
        c.bind("<1>", self._bd); c.bind("<B1-Motion>", self._mv)
        c.bind("<ButtonRelease-1>", self._up); c.bind("<Double-1>", self._dbl)
        c.bind("<Motion>", self._mo); c.bind("<MouseWheel>", self._wh)
        c.bind("<Configure>", lambda e: self._redraw())
        for seq, fn in [("<Control-z>", self.undo), ("<Control-y>", self.redo),
                        ("<Delete>", self.del_sel), ("<Escape>", self._deselect),
                        ("<KeyPress-space>", lambda e: self._hold(True)),
                        ("<KeyRelease-space>", lambda e: self._hold(False))]:
            _bind(self.m, seq, fn)
        for k, t in [("v", "select"), ("b", "pen"), ("r", "rect"), ("o", "oval"),
                     ("t", "text"), ("i", "eyed"), ("h", "pan")]:
            _bind(self.m, f"<{k}>", lambda e, x=t: self._set_tool(x))

    # ---------------- 工具 ----------------
    def _set_tool(self, t):
        self.tool = t
        for k, b in self._btns.items():
            b.config(bg=self.C["ac"] if k == t else self.C["it"])
        cur = {"select": "arrow", "pan": "fleur", "text": "xterm",
               "eraser": "dotbox"}.get(t, "crosshair")
        self.canvas.config(cursor=cur)

    def _setc(self, c): self.color = c; self._ob.config(bg=c)
    def _pick(self, w):
        _, h = colorchooser.askcolor(initialcolor=self.color)
        if h:
            if w == "out": self.color = h; self._ob.config(bg=h)
            else: self.fillc = h; self._fb.config(bg=h)
    def _say(self, s): self._st.config(text=s)

    # ---------------- 坐标 ----------------
    def _w2s(self, x, y): return x * self.zoom + self.hx, y * self.zoom + self.hy
    def _s2w(self, x, y): return (x - self.hx) / self.zoom, (y - self.hy) / self.zoom

    def zoom_fit(self):
        w = self.canvas.winfo_width() or 1200
        h = self.canvas.winfo_height() or 800
        self.zoom = min(w / self.cw, h / self.ch) * 0.94
        self.hx = (w - self.cw * self.zoom) / 2
        self.hy = (h - self.ch * self.zoom) / 2
        self._redraw()

    # ---------------- 绘制 ----------------
    def _redraw(self):
        self.canvas.delete("all")
        for L in self.layers:
            if not L.vis: continue
            for s in L.shapes:
                if s.vis: self._draw(s)
        self._draw_sel(); self._refresh_layers()

    def _draw(self, s):
        x1, y1 = self._w2s(s.x1, s.y1); x2, y2 = self._w2s(s.x2, s.y2)
        x1, x2 = sorted([x1, x2]); y1, y2 = sorted([y1, y2])
        w = max(1, s.w * self.zoom)
        if s.k == "rect":
            self.canvas.create_rectangle(x1, y1, x2, y2, outline=s.out, width=w)
        elif s.k == "rrect":
            r = max(2, s.r * self.zoom)
            pts = []
            for cx, cy, st in [(x2-r, y2-r, 0), (x1+r, y2-r, 90),
                               (x1+r, y1+r, 180), (x2-r, y1+r, 270)]:
                for a in range(st, st + 91, 12):
                    rd = math.radians(a)
                    pts += [cx + r * math.cos(rd), cy + r * math.sin(rd)]
            self.canvas.create_polygon(pts, fill=s.fill or "", outline=s.out, width=w, smooth=True)
        elif s.k == "oval":
            self.canvas.create_oval(x1, y1, x2, y2, outline=s.out, width=w)
        elif s.k in ("line", "dash"):
            self.canvas.create_line(x1, y1, x2, y2, fill=s.out, width=w,
                                    dash=(7, 4) if s.k == "dash" else ())
        elif s.k == "arrow":
            self.canvas.create_line(x1, y1, x2, y2, fill=s.out, width=w, capstyle=tk.ROUND)
            ang = math.atan2(y2 - y1, x2 - x1)
            hd = max(8, min(30 * self.zoom, math.hypot(x2 - x1, y2 - y1) * 0.3))
            for a in (ang - 0.5, ang + 0.5):
                self.canvas.create_line(x2, y2, x2 - hd * math.cos(a), y2 - hd * math.sin(a),
                                        fill=s.out, width=w)
        elif s.k == "star":
            cx, cy = (x1 + x2) / 2, (y1 + y2) / 2
            R = math.hypot(x2 - x1, y2 - y1) / 2; p = []; n = max(3, int(s.pts))
            for i in range(2 * n):
                rr = R if i % 2 == 0 else R * s.ratio
                a = -math.pi / 2 + i * math.pi / n
                p += [cx + rr * math.cos(a), cy + rr * math.sin(a)]
            self.canvas.create_polygon(p, fill=s.fill or "", outline=s.out, width=w)
        elif s.k == "pen":
            if s.path and len(s.path) > 1:
                pts = [v for px, py in s.path for v in self._w2s(px, py)]
                self.canvas.create_line(*pts, fill=s.out, width=w, capstyle=tk.ROUND, smooth=True)
        elif s.k == "text":
            self.canvas.create_text(x1, y1, text=s.txt, fill=s.out,
                                    font=(s.ff, max(6, int(s.fsz * self.zoom))), anchor=tk.NW)

    def _draw_sel(self):
        for s in self.sel:
            x1, y1 = self._w2s(*s.bbox()[:2]); x2, y2 = self._w2s(*s.bbox()[2:])
            x1, x2 = sorted([x1, x2]); y1, y2 = sorted([y1, y2])
            self.canvas.create_rectangle(x1 - 3, y1 - 3, x2 + 3, y2 + 3,
                                         outline="#007AFF", width=1, dash=(4, 3))

    # ---------------- 鼠标（修好能画的核心） ----------------
    def _bd(self, e):
        x, y = self._s2w(e.x, e.y)
        if self.space or self.tool == "pan":
            self.pan = True; self._ps = (e.x, e.y, self.hx, self.hy); return
        if self.tool == "text":
            t = simpledialog.askstring("文字", "输入：")
            if t:
                self.layers[self.cl].shapes.append(
                    Sh("text", x, y, x, y, txt=t, out=self.color,
                       ff=self._fv.get(), fsz=self._wv.get() * 4))
                self._snap("文字"); self._redraw()
            return
        if self.tool == "eyed":
            h = self._hit(x, y)
            if h: self._setc(h.out)
            return
        if self.tool == "select":
            h = self._hit(x, y)
            if h:
                self.sel = [h]; self.drag = True; self.sx, self.sy = x, y
                self.before = [s.clone() for s in self.sel]
            else:
                self.sel = []
            self._redraw(); return
        if self.tool == "eraser":
            h = self._hit(x, y)
            if h:
                self._lof(h).shapes.remove(h)
                if h in self.sel: self.sel.remove(h)
                self._redraw()
            return
        # ★ 开始画：临时图形立即挂进当前图层
        self.down = True; self.sx, self.sy = x, y
        kw = dict(out=self.color, fill=self.fillc, w=self._wv.get(), r=self._rv.get())
        if self.tool == "pen":
            self.tmp = Sh("pen", x, y, x, y, path=[(x, y)], **kw)
        else:
            self.tmp = Sh(self.tool, x, y, x, y, **kw)
        self.layers[self.cl].shapes.append(self.tmp)
        self._redraw()

    def _mv(self, e):
        if self.pan:
            self.hx = self._ps[2] + (e.x - self._ps[0])
            self.hy = self._ps[3] + (e.y - self._ps[1])
            self._redraw(); return
        x, y = self._s2w(e.x, e.y)
        if self.drag and self.sel:
            dx, dy = x - self.sx, y - self.sy
            for s in self.sel: s.move(dx, dy)
            self.sx, self.sy = x, y; self._redraw(); return
        if self.down and self.tmp:
            if self.tmp.k == "pen":
                self.tmp.path.append((x, y))
            self.tmp.x2, self.tmp.y2 = x, y
            self._redraw()

    def _up(self, e):
        self.pan = False
        if self.down and self.tmp:
            tiny = (abs(self.tmp.x2 - self.tmp.x1) < 3 and
                    abs(self.tmp.y2 - self.tmp.y1) < 3 and self.tmp.k != "pen")
            if tiny:
                self.layers[self.cl].shapes.remove(self.tmp)
            else:
                self.sel = [self.tmp]; self._snap("绘制")
            self.tmp = None; self.down = False
        self.drag = False; self._redraw()

    def _dbl(self, e):
        x, y = self._s2w(e.x, e.y); h = self._hit(x, y)
        if h and h.k == "text":
            t = simpledialog.askstring("编辑", "修改：", initialvalue=h.txt)
            if t is not None: h.txt = t; self._snap("编辑"); self._redraw()

    def _mo(self, e):
        x, y = self._s2w(e.x, e.y)
        self._st.config(text=f"x:{int(x)}  y:{int(y)}   {TIP.get(self.tool,'')}")

    def _wh(self, e):
        self.zoom = max(0.1, min(10, self.zoom * (1.12 if e.delta > 0 else 1 / 1.12)))
        self._redraw()

    def _hit(self, x, y):
        for L in reversed(self.layers):
            for s in reversed(L.shapes):
                if s.vis and not s.lock and s.hit(x, y): return s
        return None

    def _lof(self, s):
        for L in self.layers:
            if s in L.shapes: return L

    # ---------------- 图层 / 历史 ----------------
    def _nl(self):
        self.layers.append(Layer(f"图层 {len(self.layers) + 1}"))
        self.cl = len(self.layers) - 1; self._refresh_layers()
    def _dl(self):
        if len(self.layers) <= 1: return
        self.layers.pop(self.cl); self.cl = max(0, self.cl - 1); self._redraw()
    def _onlayer(self, e):
        if self._ll.curselection(): self.cl = self._ll.curselection()[0]
    def _refresh_layers(self):
        self._ll.delete(0, tk.END)
        for i, L in enumerate(self.layers):
            self._ll.insert(tk.END, f"{L.name}  ({len(L.shapes)})")
            self._ll.itemconfig(i, bg=self.C["ac"] if i == self.cl else self.C["it"])
    def _onbg(self):
        self._redraw()

    def _snap(self, name):
        snap = [[copy.deepcopy(s) for s in L.shapes] for L in self.layers]
        self.undo_s.append((name, snap))
        if len(self.undo_s) > 60: self.undo_s.pop(0)
        self.redo_s.clear()

    def undo(self):
        if not self.undo_s: return
        name, snap = self.undo_s.pop(); self.redo_s.append((name, snap))
        for i, L in enumerate(self.layers):
            L.shapes = [s.clone() for s in (snap[i] if i < len(snap) else [])]
        self.sel = []; self._redraw()
    def redo(self):
        if not self.redo_s: return
        name, snap = self.redo_s.pop(); self.undo_s.append((name, snap))
        for i, L in enumerate(self.layers):
            L.shapes = [s.clone() for s in (snap[i] if i < len(snap) else [])]
        self.sel = []; self._redraw()

    def copy(self): self.clip = [s.clone() for s in self.sel]
    def paste(self):
        if not self.clip: return
        self.sel = []
        for s in self.clip:
            n = s.clone(); n.move(14, 14)
            self.layers[self.cl].shapes.append(n); self.sel.append(n)
        self._snap("粘贴"); self._redraw()
    def del_sel(self):
        for s in list(self.sel):
            L = self._lof(s)
            if L and s in L.shapes: L.shapes.remove(s)
        self.sel = []; self._redraw()
    def _deselect(self): self.sel = []; self._redraw()
    def _hold(self, d):
        self.space = d
        if d: self._old = self.tool; self._set_tool("pan")
        else: self._set_tool(self._old)

    # ---------------- 文件 ----------------
    def _open_img(self):
        p = filedialog.askopenfilename(filetypes=[("图片", "*.png *.jpg *.jpeg")])
        if not p or not _PIL: return
        self._bgimg = Image.open(p).convert("RGB"); self._bg = tk.StringVar(value="img")
        self._onbg()

    def _export(self, fmt):
        if not _PIL: messagebox.showinfo("提示", "先 pip install pillow"); return
        p = filedialog.asksaveasfilename(defaultextension=f".{fmt.lower()}")
        if not p: return
        sc = 2.0; w, h = int(self.cw * sc), int(self.ch * sc)
        img = Image.new("RGBA", (w, h), (255, 255, 255, 255)); d = ImageDraw.Draw(img)
        for L in self.layers:
            for s in L.shapes:
                x1, y1, x2, y2 = s.x1 * sc, s.y1 * sc, s.x2 * sc, s.y2 * sc
                x1, x2 = sorted([x1, x2]); y1, y2 = sorted([y1, y2])
                wd = max(1, int(s.w * sc))
                if s.k == "rect": d.rectangle([x1, y1, x2, y2], outline=s.out, width=wd)
                elif s.k == "oval": d.ellipse([x1, y1, x2, y2], outline=s.out, width=wd)
                elif s.k == "pen" and s.path and len(s.path) > 1:
                    d.line([(px * sc, py * sc) for px, py in s.path],
                           fill=s.out, width=wd, joint="curve")
                elif s.k == "text":
                    try: f = ImageFont.truetype(f"{s.ff}.ttf", max(8, int(s.fsz * sc)))
                    except Exception: f = None
                    d.text((x1, y1), s.txt, fill=s.out, font=f)
        img.save(p); messagebox.showinfo("✓", f"已保存 {os.path.basename(p)}")


def main():
    root = tk.Tk()
    root.title("画图板 超级版 v5.1")
    root.geometry("1400x880"); root.minsize(1050, 680); root.config(bg="#1e1e2e")
    DrawingPad(root)
    root.mainloop()

if __name__ == "__main__":
    main()