애드온 · 모드 자료실

전체보기

모바일 상단 메뉴

본문 페이지

[기능] D2R PathFinder (파일/폴더 바로가기)

아이콘 풍선
댓글: 1 개
조회: 270
추천: 4
2026-06-03 20:46:59
[구글 다운로드 링크] - 최신버전 D2R_PathFinder_v0.3.exe (26. 6. 4 3:00 최신)



모딩하다 경로 오가기 답답해서 AI로 만들어 쓰고 있는 패스파인더 앱입니다.
(물론 저만 고전적이게 폴더 이리저리 돌아다녔을 거 같긴 한데...)

[모더용]이라고 말머리를 달려다가 그냥 다른 용도로도 쓸 수 있다고 판단해서 그냥 올립니다.




* 이런식으로 간단하게 한쪽에 고정시켜두고 버튼 클릭하면 원하는 exe파일이나 폴더를 켜는 앱입니다.




* 최초 실행 시 창입니다. 상단의 설정(톱니바퀴) 버튼으로 버튼명과 경로를 편집하면 됩니다.




* 버튼 이름과 경로를 선택할 수 있고 항목을 추가하거나 X로 지울 수 있습니다.
  (항목이 추가/제거되면 자동으로 버튼 개수와 창 크기가 조정됩니다.)

* exe 파일의 경우 바로가기 생성 후 우클릭 - 속성 - 대상(T)의 경로를 붙여넣으시면 됩니다.
  (따옴표(" ")는 제거하세요.)




[상단바 기능]

① : 버튼 / 경로설정

② : 창 투명도 조절 (좌 : 불투명, 우 : 투명)

③ : 앱 종료

④ : 항상 위에 고정 (창 이동 및 종료버튼 비활성화)

드래그 : 창 이동


p.s 고정핀이 종료버튼 왼쪽에 있는 게 자연스러운데 자꾸 코드가 꼬여서 에라 모르겠다 했습니다 ㅜㅜ

※ 경로 수정시 설정 파일(config.json)이 자동 생성 되는 것이 정상이나, 오류 발생할 경우
폴더 상단 바로가기에 %APPDATA% 입력하셔서 D2RPathFinder 폴더를 직접 생성해주세요.

* 설정파일 저장 위치 : %APPDATA%D2RPathFinder

-----------------------------------------------------------------------------------------------------------------------------------------------
[패치 내역]
* D2R_PathFinder_v0.2 업데이트(26. 6. 4 02:00)
  - 간헐적으로 작업표시줄에 표시되지 않는 문제 수정

* D2R_PathFinder_v0.3 업데이트(26. 6. 4 03:00)
 
 - 앱 이름 클릭 시 접기/펼치기 기능 추가
-----------------------------------------------------------------------------------------------------------------------------------------------

보안 및 안전 안내
  • 100% 안전한 툴: 모더분들의 작업 동선을 줄이기 위해 제작된 단순 런처 프로그램입니다.
  • 소스코드 투명 공개: 혹시 모를 불안감이 있으신 분들을 위해 사용된 파이썬(Python) 소스코드를 하단에 그대로 첨부합니다. 의심스러우시다면 코드를 직접 검증하시거나 파이썬으로 직접 실행하셔도 좋습니다!
  • 미인증 개인 제작 파일 특성상 백신 프로그램이 오진할 수 있으나, 어떠한 악성 기능도 없는 청정 프로그램임을 보증합니다

▼ 클릭 시 펼쳐집니다.

Python Code 보기
"""
========================================================
  D2 Folder Launcher — Diablo II Modding Quick Access
========================================================
  폴더/앱 설정은 앱 내 ⚙ 설정 버튼에서 관리하세요.
  설정은 %%APPDATA%%D2RPathFinderconfig.json 에 저장됩니다.
"""

import tkinter as tk
from tkinter import messagebox, filedialog
import subprocess
import os
import json

# ──────────────────────────────────────────────────────
#  앱 설정
# ──────────────────────────────────────────────────────
APP_TITLE  = "D2R PathFinder v.0.3"
COLUMNS    = 2
BTN_WIDTH  = 17
BTN_HEIGHT = 1

CONFIG_DIR  = os.path.join(os.environ.get("APPDATA", os.path.expanduser("~")), "D2RPathFinder")
CONFIG_FILE = os.path.join(CONFIG_DIR, "config.json")

DEFAULT_FOLDERS = [
    {"label": "🗡  Diablo II Root",      "path": r"C:Program Files (x86)Diablo II"},
    {"label": "📦  Mod 폴더",             "path": r"C:Program Files (x86)Diablo IImods"},
    {"label": "🖼  MPQ / Assets",        "path": r"C:Program Files (x86)Diablo IIMPQs"},
    {"label": "📝  Excel / Data Tables", "path": r"C:D2ModsDataGlobalExcel"},
    {"label": "🎨  Sprites / DC6",       "path": r"C:D2ModsDataGlobalUI"},
    {"label": "🔊  Sound Files",         "path": r"C:D2ModsDataGlobalSound"},
    {"label": "⚙️  Patch_D2 Workspace",  "path": r"C:D2ModsPatch_D2"},
    {"label": "🛠  Tools",               "path": r"C:D2ModsTools"},
    {"label": "💾  Backups",             "path": r"C:D2ModsBackups"},
    {"label": "📁  My Documents",        "path": os.path.expanduser("~Documents")},
]

# ──────────────────────────────────────────────────────
#  다크 테마 색상
# ──────────────────────────────────────────────────────
COLORS = {
    "bg":            "#0e0e10",
    "surface":       "#1a1a1f",
    "surface2":      "#25252d",
    "border":        "#2e2e3a",
    "accent":        "#c8973a",
    "accent_hover":  "#e6b04a",
    "accent_press":  "#a07828",
    "text_primary":  "#e8e0d0",
    "text_secondary":"#7a7a8a",
    "titlebar_bg":   "#131316",
    "danger":        "#e05050",
}

FONT_TITLE = ("Segoe UI", 10, "bold")
FONT_BTN   = ("Segoe UI", 11, "bold")
FONT_UI    = ("Segoe UI", 10)
FONT_SMALL = ("Segoe UI", 9)


# ──────────────────────────────────────────────────────
#  config.json 로드 / 저장
# ──────────────────────────────────────────────────────
def load_config():
    if not os.path.exists(CONFIG_FILE):
        return [dict(f) for f in DEFAULT_FOLDERS]
    try:
        with open(CONFIG_FILE, "r", encoding="utf-8") as f:
            data = json.load(f)
        return data.get("folders", [dict(f) for f in DEFAULT_FOLDERS])
    except Exception:
        return [dict(f) for f in DEFAULT_FOLDERS]

def save_config(folders):
    os.makedirs(CONFIG_DIR, exist_ok=True)
    with open(CONFIG_FILE, "w", encoding="utf-8") as f:
        json.dump({"folders": folders}, f, ensure_ascii=False, indent=2)


# ──────────────────────────────────────────────────────
#  폴더/앱 열기
# ──────────────────────────────────────────────────────
def open_path(path: str):
    if not os.path.exists(path):
        messagebox.showwarning(
            "경로를 찾을 수 없음",
            f"아래 경로가 존재하지 않습니다:n{path}nn"
            "⚙ 설정에서 경로를 확인하세요."
        )
        return
    try:
        if os.path.isfile(path):
            subprocess.Popen([path])
        else:
            subprocess.Popen(["explorer", path])
    except Exception as e:
        messagebox.showerror("오류", str(e))


# ──────────────────────────────────────────────────────
#  호버 효과
# ──────────────────────────────────────────────────────
def on_enter(btn):
    btn.config(bg=COLORS["surface"], fg=COLORS["accent_hover"], relief="flat")

def on_leave(btn):
    btn.config(bg=COLORS["surface2"], fg=COLORS["text_primary"], relief="flat")

def on_press(btn):
    btn.config(bg=COLORS["accent_press"], fg=COLORS["bg"])

def on_release(btn, path):
    btn.config(bg=COLORS["surface2"], fg=COLORS["text_primary"])
    open_path(path)


# ──────────────────────────────────────────────────────
#  툴팁
# ──────────────────────────────────────────────────────
class _Tooltip:
    def __init__(self, widget, text):
        self.widget = widget
        self.text   = text
        self.tipwin = None
        widget.bind("<Enter>", self.show, add="+")
        widget.bind("<Leave>", self.hide, add="+")

    def show(self, _=None):
        if self.tipwin:
            return
        x = self.widget.winfo_rootx() + 20
        y = self.widget.winfo_rooty() + self.widget.winfo_height() + 4
        self.tipwin = tw = tk.Toplevel(self.widget)
        tw.wm_overrideredirect(True)
        tw.wm_geometry(f"+{x}+{y}")
        tk.Label(tw, text=self.text, font=("Segoe UI", 8),
                 fg=COLORS["text_secondary"], bg=COLORS["surface"],
                 relief="flat", bd=1, padx=8, pady=4).pack()

    def hide(self, _=None):
        if self.tipwin:
            self.tipwin.destroy()
            self.tipwin = None


# ──────────────────────────────────────────────────────
#  설정 창
# ──────────────────────────────────────────────────────
def open_config_window(parent, folders, on_save):
    win = tk.Toplevel(parent)
    win.overrideredirect(True)
    win.configure(bg=COLORS["bg"])
    win.resizable(False, False)
    win.grab_set()

    # ── 설정 창 타이틀바 ─────────────────────────────────
    _drag = {"x": 0, "y": 0}
    def drag_start(e): _drag["x"] = e.x; _drag["y"] = e.y
    def drag_move(e):
        win.geometry(f"+{win.winfo_x()+e.x-_drag['x']}+{win.winfo_y()+e.y-_drag['y']}")

    tb = tk.Frame(win, bg=COLORS["titlebar_bg"])
    tb.pack(fill="x")
    tb.bind("<ButtonPress-1>", drag_start)
    tb.bind("<B1-Motion>",     drag_move)

    lbl = tk.Label(tb, text="⚙  설정", font=FONT_TITLE,
                   fg=COLORS["accent"], bg=COLORS["titlebar_bg"], padx=10, pady=6)
    lbl.pack(side="left")
    lbl.bind("<ButtonPress-1>", drag_start)
    lbl.bind("<B1-Motion>",     drag_move)

    btn_x = tk.Label(tb, text="✕", font=("Consolas", 10, "bold"),
                     fg=COLORS["text_secondary"], bg=COLORS["titlebar_bg"],
                     padx=10, pady=6, cursor="hand2")
    btn_x.pack(side="right")
    btn_x.bind("<Enter>",    lambda e: btn_x.config(fg=COLORS["danger"]))
    btn_x.bind("<Leave>",    lambda e: btn_x.config(fg=COLORS["text_secondary"]))
    btn_x.bind("<Button-1>", lambda e: win.destroy())

    tk.Frame(win, bg=COLORS["accent"], height=2).pack(fill="x")

    # ── 스크롤 영역 ──────────────────────────────────────
    outer = tk.Frame(win, bg=COLORS["bg"])
    outer.pack(fill="both", expand=True, padx=10, pady=8)

    canvas = tk.Canvas(outer, bg=COLORS["bg"], highlightthickness=0, width=500, height=360)
    scrollbar = tk.Scrollbar(outer, orient="vertical", command=canvas.yview,
                             bg=COLORS["surface2"], troughcolor=COLORS["bg"],
                             activebackground=COLORS["accent"])
    canvas.configure(yscrollcommand=scrollbar.set)
    scrollbar.pack(side="right", fill="y")
    canvas.pack(side="left", fill="both", expand=True)

    inner = tk.Frame(canvas, bg=COLORS["bg"])
    inner_id = canvas.create_window((0, 0), window=inner, anchor="nw")

    def on_inner_configure(e):
        canvas.configure(scrollregion=canvas.bbox("all"))
    inner.bind("<Configure>", on_inner_configure)
    canvas.bind("<Configure>", lambda e: canvas.itemconfig(inner_id, width=e.width))
    canvas.bind("<MouseWheel>", lambda e: canvas.yview_scroll(-1*(1 if e.delta>0 else -1), "units"))

    # ── 항목 행 목록 ─────────────────────────────────────
    rows = []  # {"label": StringVar, "path": StringVar, "frame": Frame}

    def make_row(label_val="", path_val="", index=None):
        row_frame = tk.Frame(inner, bg=COLORS["surface2"], pady=4, padx=6)
        row_frame.pack(fill="x", pady=3)

        # 순서 번호
        idx_lbl = tk.Label(row_frame, text="", font=FONT_SMALL,
                           fg=COLORS["text_secondary"], bg=COLORS["surface2"], width=2)
        idx_lbl.pack(side="left", padx=(2, 4))

        # 이름 입력
        lv = tk.StringVar(value=label_val)
        name_entry = tk.Entry(row_frame, textvariable=lv, font=FONT_UI,
                              bg=COLORS["surface"], fg=COLORS["text_primary"],
                              insertbackground=COLORS["accent"],
                              relief="flat", bd=0, width=18)
        name_entry.pack(side="left", padx=(0, 6), ipady=4)

        # 경로 입력
        pv = tk.StringVar(value=path_val)
        path_entry = tk.Entry(row_frame, textvariable=pv, font=FONT_UI,
                              bg=COLORS["surface"], fg=COLORS["text_primary"],
                              insertbackground=COLORS["accent"],
                              relief="flat", bd=0, width=28)
        path_entry.pack(side="left", padx=(0, 4), ipady=4)

        # 탐색 버튼
        def browse(pvar=pv):
            chosen = filedialog.askdirectory(title="폴더 선택")
            if not chosen:
                # 폴더 선택 취소 시 파일도 시도
                chosen = filedialog.askopenfilename(title="파일 선택")
            if chosen:
                pvar.set(chosen.replace("/", ""))

        tk.Button(row_frame, text="📁", font=FONT_SMALL,
                  bg=COLORS["surface"], fg=COLORS["accent"],
                  activebackground=COLORS["border"], activeforeground=COLORS["accent_hover"],
                  relief="flat", bd=0, cursor="hand2", padx=4,
                  command=browse).pack(side="left", padx=(0, 4))

        # 삭제 버튼
        def delete_row(rf=row_frame):
            rows[:] = [r for r in rows if r["frame"] is not rf]
            rf.destroy()
            refresh_indices()

        tk.Button(row_frame, text="✕", font=FONT_SMALL,
                  bg=COLORS["surface"], fg=COLORS["danger"],
                  activebackground=COLORS["border"], activeforeground=COLORS["danger"],
                  relief="flat", bd=0, cursor="hand2", padx=4,
                  command=delete_row).pack(side="left")

        row_data = {"label": lv, "path": pv, "frame": row_frame, "idx_lbl": idx_lbl}
        if index is None:
            rows.append(row_data)
        else:
            rows.insert(index, row_data)
        refresh_indices()

    def refresh_indices():
        for i, r in enumerate(rows):
            r["idx_lbl"].config(text=str(i + 1))

    # 기존 항목 로드
    for item in folders:
        make_row(item["label"], item["path"])

    # ── 하단 버튼 ────────────────────────────────────────
    tk.Frame(win, bg=COLORS["border"], height=1).pack(fill="x", padx=10)

    bottom = tk.Frame(win, bg=COLORS["bg"], pady=8)
    bottom.pack(fill="x", padx=10)

    def add_row():
        make_row("🆕  새 버튼", "")
        canvas.update_idletasks()
        canvas.yview_moveto(1.0)

    tk.Button(bottom, text="+  항목 추가", font=FONT_SMALL,
              bg=COLORS["surface2"], fg=COLORS["accent"],
              activebackground=COLORS["surface"], activeforeground=COLORS["accent_hover"],
              relief="flat", bd=0, cursor="hand2", padx=10, pady=6,
              command=add_row).pack(side="left")

    def save_and_close():
        new_folders = [
            {"label": r["label"].get().strip(), "path": r["path"].get().strip()}
            for r in rows
            if r["label"].get().strip() or r["path"].get().strip()
        ]
        save_config(new_folders)
        on_save(new_folders)
        win.destroy()

    tk.Button(bottom, text="💾  저장", font=FONT_SMALL,
              bg=COLORS["accent"], fg=COLORS["bg"],
              activebackground=COLORS["accent_hover"], activeforeground=COLORS["bg"],
              relief="flat", bd=0, cursor="hand2", padx=14, pady=6,
              command=save_and_close).pack(side="right")

    tk.Frame(win, bg=COLORS["accent"], height=2).pack(fill="x")

    # 화면 중앙
    win.update_idletasks()
    sw = win.winfo_screenwidth()
    sh = win.winfo_screenheight()
    w  = win.winfo_width()
    h  = win.winfo_height()
    win.geometry(f"+{(sw-w)//2}+{(sh-h)//2}")


# ──────────────────────────────────────────────────────
#  메인 GUI
# ──────────────────────────────────────────────────────
def build_gui():
    folders = load_config()

    root = tk.Tk()
    root.overrideredirect(True)
    root.configure(bg=COLORS["bg"])
    root.resizable(False, False)
    root.attributes("-topmost", False)
    root.attributes("-alpha", 1.0)
    root.title(APP_TITLE)

    # ── 작업표시줄 / Alt+Tab 등록 (Windows) ─────────────
    def register_taskbar():
        try:
            import ctypes
            GWL_EXSTYLE      = -20
            WS_EX_APPWINDOW  = 0x00040000
            WS_EX_TOOLWINDOW = 0x00000080
            GA_ROOT          = 2
            user32 = ctypes.windll.user32
            hwnd = user32.GetAncestor(root.winfo_id(), GA_ROOT)
            style = user32.GetWindowLongW(hwnd, GWL_EXSTYLE)
            style = (style & ~WS_EX_TOOLWINDOW) | WS_EX_APPWINDOW
            user32.SetWindowLongW(hwnd, GWL_EXSTYLE, style)
            # 현재 위치 저장 후 숨겼다가 복원
            x, y = root.winfo_x(), root.winfo_y()
            root.withdraw()
            def restore():
                root.geometry(f"+{x}+{y}")
                root.deiconify()
            root.after(50, restore)
        except Exception:
            pass

    root.after(100, register_taskbar)

    # ── 드래그 ───────────────────────────────────────────
    _drag = {"x": 0, "y": 0, "moved_x": 0, "moved_y": 0}

    def drag_start(e):
        if pin_state["on"]: return
        _drag["x"] = e.x; _drag["y"] = e.y
        _drag["moved_x"] = 0; _drag["moved_y"] = 0

    def drag_move(e):
        if pin_state["on"]: return
        dx = e.x - _drag["x"]
        dy = e.y - _drag["y"]
        _drag["moved_x"] = dx; _drag["moved_y"] = dy
        root.geometry(f"+{root.winfo_x()+dx}+{root.winfo_y()+dy}")

    # ── 타이틀바 ─────────────────────────────────────────
    titlebar = tk.Frame(root, bg=COLORS["titlebar_bg"], height=28)
    titlebar.pack(fill="x")
    titlebar.bind("<ButtonPress-1>", drag_start)
    titlebar.bind("<B1-Motion>",     drag_move)

    lbl_title = tk.Label(titlebar, text=APP_TITLE, font=FONT_TITLE,
                         fg=COLORS["accent"], bg=COLORS["titlebar_bg"], padx=10, pady=4)
    lbl_title.pack(side="left")
    lbl_title.bind("<ButtonPress-1>", drag_start)
    lbl_title.bind("<B1-Motion>",     drag_move)

    # 핀 버튼
    PIN_SIZE  = 20
    pin_canvas = tk.Canvas(titlebar, width=PIN_SIZE, height=PIN_SIZE,
                           bg=COLORS["titlebar_bg"], highlightthickness=0, cursor="hand2")
    pin_canvas.pack(side="right", padx=(0, 6), pady=4)
    pin_state = {"on": False}

    def draw_pin(active):
        pin_canvas.delete("all")
        if active:
            pin_canvas.create_oval(1, 1, PIN_SIZE-2, PIN_SIZE-2,
                                   fill=COLORS["accent"], outline=COLORS["accent_hover"], width=1)
            pin_canvas.create_text(PIN_SIZE//2, PIN_SIZE//2+1, text="📌",
                                   font=("Segoe UI Emoji", 9))
        else:
            pin_canvas.create_oval(1, 1, PIN_SIZE-2, PIN_SIZE-2,
                                   fill="", outline=COLORS["border"], width=1)
            pin_canvas.create_text(PIN_SIZE//2, PIN_SIZE//2+1, text="📌",
                                   font=("Segoe UI Emoji", 9), fill=COLORS["text_secondary"])

    draw_pin(False)

    def toggle_pin(_=None):
        pin_state["on"] = not pin_state["on"]
        root.attributes("-topmost", pin_state["on"])
        draw_pin(pin_state["on"])
        if pin_state["on"]:
            btn_close.config(fg=COLORS["border"], cursor="arrow")
        else:
            btn_close.config(fg=COLORS["text_secondary"], cursor="hand2")

    pin_canvas.bind("<Button-1>", toggle_pin)

    # 종료 버튼
    btn_close = tk.Label(titlebar, text="✕", font=("Consolas", 10, "bold"),
                         fg=COLORS["text_secondary"], bg=COLORS["titlebar_bg"],
                         padx=10, pady=4, cursor="hand2")
    btn_close.pack(side="right")
    btn_close.bind("<Enter>",    lambda e: btn_close.config(fg=COLORS["danger"]) if not pin_state["on"] else None)
    btn_close.bind("<Leave>",    lambda e: btn_close.config(fg=COLORS["text_secondary"]) if not pin_state["on"] else None)
    btn_close.bind("<Button-1>", lambda e: root.destroy() if not pin_state["on"] else None)

    # 투명도 슬라이더 라벨
    tk.Label(titlebar, text="◑", font=("Segoe UI", 9),
             fg=COLORS["text_secondary"], bg=COLORS["titlebar_bg"], padx=2).pack(side="right")

    # 투명도 슬라이더
    alpha_var = tk.DoubleVar(value=1.0)
    scale = tk.Scale(titlebar, from_=1.0, to=0.3, resolution=0.05,
                     orient="horizontal", variable=alpha_var,
                     command=lambda v: root.attributes("-alpha", float(v)),
                     length=90, showvalue=False,
                     bg=COLORS["titlebar_bg"], fg=COLORS["accent"],
                     troughcolor="#3a3a4a", activebackground=COLORS["accent_hover"],
                     highlightthickness=1, highlightcolor=COLORS["border"],
                     highlightbackground="#3a3a4a", bd=0,
                     sliderlength=12, sliderrelief="flat", cursor="hand2")
    scale.pack(side="right", padx=(0, 4), pady=3)

    # 설정 버튼
    btn_cfg = tk.Label(titlebar, text="⚙", font=("Segoe UI", 11),
                       fg=COLORS["text_secondary"], bg=COLORS["titlebar_bg"],
                       padx=8, pady=4, cursor="hand2")
    btn_cfg.pack(side="right")
    btn_cfg.bind("<Enter>", lambda e: btn_cfg.config(fg=COLORS["accent_hover"]))
    btn_cfg.bind("<Leave>", lambda e: btn_cfg.config(fg=COLORS["text_secondary"]))

    # 금빛 구분선
    accent_top = tk.Frame(root, bg=COLORS["accent"], height=2)
    accent_top.pack(fill="x")

    # ── 버튼 그리드 ──────────────────────────────────────
    btn_frame = tk.Frame(root, bg=COLORS["bg"], padx=12, pady=8)
    btn_frame.pack()

    def rebuild_buttons(folder_list):
        for w in btn_frame.winfo_children():
            w.destroy()
        for i, item in enumerate(folder_list):
            row = i // COLUMNS
            col = i % COLUMNS
            container = tk.Frame(btn_frame, bg=COLORS["border"], padx=1, pady=1)
            container.grid(row=row, column=col, padx=5, pady=4, sticky="nsew")
            btn = tk.Button(container, text=item["label"], font=FONT_BTN,
                            fg=COLORS["text_primary"], bg=COLORS["surface2"],
                            activeforeground=COLORS["accent"],
                            activebackground=COLORS["surface"],
                            relief="flat", bd=0, width=BTN_WIDTH, height=BTN_HEIGHT,
                            cursor="hand2", anchor="w", padx=12)
            btn.pack()
            btn.bind("<Enter>",          lambda e, b=btn: on_enter(b))
            btn.bind("<Leave>",          lambda e, b=btn: on_leave(b))
            btn.bind("<ButtonPress-1>",  lambda e, b=btn: on_press(b))
            btn.bind("<ButtonRelease-1>",lambda e, b=btn, p=item["path"]: on_release(b, p))
            _Tooltip(btn, item["path"])
        for c in range(COLUMNS):
            btn_frame.columnconfigure(c, weight=1)
        root.update_idletasks()

    rebuild_buttons(folders)

    def on_config_save(new_folders):
        nonlocal folders
        folders = new_folders
        rebuild_buttons(folders)

    btn_cfg.bind("<Button-1>", lambda e: open_config_window(root, folders, on_config_save))

    # 하단 장식선
    accent_bottom = tk.Frame(root, bg=COLORS["accent"], height=2)
    accent_bottom.pack(fill="x")

    # ── 접기 / 펼치기 ────────────────────────────────────
    collapsed = {"on": False}
    body_widgets = [accent_top, btn_frame, accent_bottom]

    def toggle_collapse(_=None):
        # 드래그 직후 클릭 무시 (5px 이상 움직였으면 드래그로 판단)
        if abs(_drag.get("moved_x", 0)) > 5 or abs(_drag.get("moved_y", 0)) > 5:
            return
        collapsed["on"] = not collapsed["on"]
        if collapsed["on"]:
            for w in body_widgets:
                w.pack_forget()
            lbl_title.config(fg=COLORS["text_secondary"])
        else:
            accent_top.pack(fill="x", after=titlebar)
            btn_frame.pack(after=accent_top)
            accent_bottom.pack(fill="x", after=btn_frame)
            lbl_title.config(fg=COLORS["accent"])
        root.update_idletasks()

    lbl_title.config(cursor="hand2")
    lbl_title.bind("<ButtonRelease-1>", toggle_collapse)

    # 화면 중앙
    root.update_idletasks()
    sw = root.winfo_screenwidth()
    sh = root.winfo_screenheight()
    root.geometry(f"+{(sw-root.winfo_width())//2}+{(sh-root.winfo_height())//2}")

    root.mainloop()


if __name__ == "__main__":
    build_gui()

모바일 게시판 하단버튼

댓글

새로고침
새로고침

모바일 게시판 하단버튼

지금 뜨는 인벤

더보기+

모바일 게시판 리스트

모바일 게시판 하단버튼

글쓰기

모바일 게시판 페이징

최근 HOT한 콘텐츠

  • 게임
  • IT
  • 유머
  • 연예
AD