Skip to content

Commit 0ce9af4

Browse files
committed
Improve GUI layout and interactions
1 parent c7ad863 commit 0ce9af4

2 files changed

Lines changed: 107 additions & 34 deletions

File tree

.github/workflows/build-and-release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44
push:
55
branches:
66
- main
7+
- dev
78
tags:
89
- "v*"
910
workflow_dispatch:

videomerge/gui.py

Lines changed: 106 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,11 @@ class VideoMergeGUI:
4848
def __init__(self) -> None:
4949
self.root = tk.Tk()
5050
self.root.title("VideoMergingTool")
51-
self.root.geometry("1320x760")
52-
self.root.minsize(1040, 640)
51+
self.root.geometry("1440x820")
52+
self.root.minsize(1180, 680)
5353
self.root.configure(bg=COLORS["bg"])
54+
self._last_resize_width = 0
55+
self.root.bind("<Configure>", self._on_resize)
5456

5557
self.events: queue.Queue[tuple[str, object]] = queue.Queue()
5658
self.input_dir: Path | None = None
@@ -89,8 +91,8 @@ def _build_styles(self) -> None:
8991
foreground=COLORS["text"],
9092
fieldbackground=COLORS["panel"],
9193
bordercolor=COLORS["border"],
92-
rowheight=30,
93-
font=("Segoe UI", 10),
94+
rowheight=28,
95+
font=("Consolas", 9),
9496
)
9597
style.configure(
9698
"Files.Treeview.Heading",
@@ -114,8 +116,8 @@ def _build_layout(self) -> None:
114116

115117
main = tk.Frame(self.root, bg=COLORS["bg"])
116118
main.pack(fill=tk.BOTH, expand=True)
117-
main.columnconfigure(0, weight=1)
118-
main.columnconfigure(1, weight=0, minsize=360)
119+
main.columnconfigure(0, weight=1, minsize=720)
120+
main.columnconfigure(1, weight=0, minsize=380)
119121
main.rowconfigure(0, weight=1)
120122

121123
self.left = tk.Frame(main, bg=COLORS["bg"], highlightthickness=1, highlightbackground=COLORS["border"])
@@ -147,17 +149,18 @@ def _build_header(self) -> None:
147149
header,
148150
bg=COLORS["panel"],
149151
fg=COLORS["secondary"],
150-
bd=1,
151-
relief=tk.SOLID,
152+
bd=0,
153+
highlightthickness=1,
154+
highlightbackground=COLORS["border"],
152155
padx=12,
153156
pady=4,
154157
font=("Segoe UI", 9),
155158
)
156159
self.ffmpeg_badge.pack(side=tk.RIGHT, padx=18)
157160

158161
def _build_left_pane(self) -> None:
159-
header = tk.Frame(self.left, bg=COLORS["bg"], height=76)
160-
header.pack(fill=tk.X, padx=16, pady=(14, 8))
162+
header = tk.Frame(self.left, bg=COLORS["bg"], height=78)
163+
header.pack(fill=tk.X, padx=18, pady=(14, 8))
161164
header.columnconfigure(0, weight=1)
162165

163166
tk.Label(header, text="Source Files", bg=COLORS["bg"], fg=COLORS["text"], font=("Segoe UI", 12, "bold")).grid(
@@ -178,7 +181,7 @@ def _build_left_pane(self) -> None:
178181
refresh_button.grid(row=0, column=2, rowspan=2, sticky="e")
179182

180183
table_frame = tk.Frame(self.left, bg=COLORS["panel"], highlightthickness=1, highlightbackground=COLORS["border"])
181-
table_frame.pack(fill=tk.BOTH, expand=True, padx=16, pady=(4, 12))
184+
table_frame.pack(fill=tk.BOTH, expand=True, padx=18, pady=(4, 12))
182185
columns = ("filename", "resolution", "codec", "fps", "duration", "status")
183186
self.files_tree = ttk.Treeview(table_frame, columns=columns, show="headings", style="Files.Treeview")
184187
headings = {
@@ -190,26 +193,29 @@ def _build_left_pane(self) -> None:
190193
"status": "STATUS",
191194
}
192195
widths = {
193-
"filename": 300,
196+
"filename": 360,
194197
"resolution": 130,
195198
"codec": 120,
196199
"fps": 90,
197200
"duration": 90,
198-
"status": 140,
201+
"status": 150,
199202
}
200203
for col in columns:
201204
self.files_tree.heading(col, text=headings[col])
202-
self.files_tree.column(col, width=widths[col], anchor=tk.W)
205+
anchor = tk.W if col == "filename" else tk.CENTER
206+
stretch = col == "filename"
207+
self.files_tree.column(col, width=widths[col], minwidth=70, anchor=anchor, stretch=stretch)
203208
scrollbar = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=self.files_tree.yview)
204209
self.files_tree.configure(yscrollcommand=scrollbar.set)
205210
self.files_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
206211
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
207212
self.files_tree.tag_configure("group", background=COLORS["bg"], foreground=COLORS["secondary"])
208213
self.files_tree.tag_configure("ok", foreground=COLORS["green"])
209214
self.files_tree.tag_configure("warn", foreground=COLORS["yellow"])
215+
self.files_tree.bind("<Configure>", self._resize_table_columns)
210216

211217
self.plan_frame = tk.Frame(self.left, bg=COLORS["panel"], highlightthickness=1, highlightbackground=COLORS["border"])
212-
self.plan_frame.pack(fill=tk.X, padx=16, pady=(0, 16))
218+
self.plan_frame.pack(fill=tk.X, padx=18, pady=(0, 16))
213219
self.plan_title = tk.Label(
214220
self.plan_frame,
215221
text="OPTIMAL MODE SELECTED",
@@ -236,10 +242,10 @@ def _build_right_pane(self) -> None:
236242
bg=COLORS["panel"],
237243
fg=COLORS["text"],
238244
font=("Segoe UI", 12, "bold"),
239-
).pack(anchor="w", padx=16, pady=(18, 24))
245+
).pack(anchor="w", padx=18, pady=(18, 24))
240246

241247
content = tk.Frame(self.right, bg=COLORS["panel"])
242-
content.pack(fill=tk.BOTH, expand=True, padx=16)
248+
content.pack(fill=tk.BOTH, expand=True, padx=18)
243249

244250
self._section_label(content, "MERGE STRATEGY")
245251
self.mode_cards: dict[str, tk.Frame] = {}
@@ -278,7 +284,7 @@ def _build_right_pane(self) -> None:
278284
self._build_console(content)
279285

280286
dock = tk.Frame(self.right, bg=COLORS["panel"], highlightthickness=1, highlightbackground=COLORS["border"])
281-
dock.pack(fill=tk.X, side=tk.BOTTOM, padx=16, pady=16)
287+
dock.pack(fill=tk.X, side=tk.BOTTOM, padx=18, pady=16)
282288
self.start_button = self._button(dock, "▷ START MERGE", self.start_merge, primary=True)
283289
self.start_button.pack(fill=tk.X, pady=8)
284290

@@ -315,14 +321,20 @@ def _section_label(self, parent: tk.Widget, text: str, pady: tuple[int, int] = (
315321
def _mode_card(self, parent: tk.Widget, value: str, title: str, badge: str, desc: str) -> None:
316322
frame = tk.Frame(parent, bg=COLORS["bg"], highlightthickness=1, highlightbackground=COLORS["border"], cursor="hand2")
317323
frame.pack(fill=tk.X, pady=5)
318-
frame.bind("<Button-1>", lambda _event, mode=value: self._set_mode(mode))
319324
top = tk.Frame(frame, bg=COLORS["bg"])
320325
top.pack(fill=tk.X, padx=12, pady=(10, 2))
321-
tk.Label(top, text=title, bg=COLORS["bg"], fg=COLORS["text"], font=("Segoe UI", 10, "bold")).pack(side=tk.LEFT)
322-
tk.Label(top, text=badge, bg=COLORS["panel_hover"], fg=COLORS["secondary"], font=("Segoe UI", 7, "bold")).pack(side=tk.RIGHT)
323-
tk.Label(frame, text=desc, bg=COLORS["bg"], fg=COLORS["secondary"], justify=tk.LEFT, wraplength=310, font=("Segoe UI", 9)).pack(
326+
title_label = tk.Label(top, text=title, bg=COLORS["bg"], fg=COLORS["text"], font=("Segoe UI", 10, "bold"))
327+
title_label.pack(side=tk.LEFT)
328+
badge_label = tk.Label(top, text=badge, bg=COLORS["panel_hover"], fg=COLORS["secondary"], font=("Segoe UI", 7, "bold"))
329+
badge_label.pack(side=tk.RIGHT)
330+
desc_label = tk.Label(frame, text=desc, bg=COLORS["bg"], fg=COLORS["secondary"], justify=tk.LEFT, wraplength=310, font=("Segoe UI", 9))
331+
desc_label.pack(
324332
anchor="w", padx=12, pady=(0, 10)
325333
)
334+
for widget in (frame, top, title_label, badge_label, desc_label):
335+
widget.bind("<Button-1>", lambda _event, mode=value: self._set_mode(mode))
336+
widget.bind("<Enter>", lambda _event, card=frame: self._hover_card(card, True))
337+
widget.bind("<Leave>", lambda _event, card=frame: self._hover_card(card, False))
326338
self.mode_cards[value] = frame
327339

328340
def _labeled_entry(self, parent: tk.Widget, label: str, variable: tk.StringVar) -> None:
@@ -339,9 +351,9 @@ def _labeled_option(self, parent: tk.Widget, label: str, variable: tk.StringVar,
339351

340352
def _check(self, parent: tk.Widget, title: str, desc: str, variable: tk.BooleanVar) -> None:
341353
row = tk.Frame(parent, bg=COLORS["panel"], highlightthickness=1, highlightbackground=COLORS["border"])
342-
row.pack(fill=tk.X, pady=5)
354+
row.pack(fill=tk.X, pady=5, ipady=2)
343355
text = tk.Frame(row, bg=COLORS["panel"])
344-
text.pack(side=tk.LEFT, padx=0, pady=8)
356+
text.pack(side=tk.LEFT, padx=8, pady=8)
345357
tk.Label(text, text=title, bg=COLORS["panel"], fg=COLORS["text"], font=("Segoe UI", 9)).pack(anchor="w")
346358
tk.Label(text, text=desc, bg=COLORS["panel"], fg=COLORS["muted"], font=("Segoe UI", 8)).pack(anchor="w")
347359
tk.Checkbutton(
@@ -361,26 +373,47 @@ def _button(
361373
primary: bool = False,
362374
secondary: bool = False,
363375
icon: bool = False,
364-
) -> tk.Button:
376+
) -> tk.Frame:
365377
bg = COLORS["text"] if primary else COLORS["bg"]
366378
fg = COLORS["bg"] if primary else COLORS["text"]
367379
if icon:
368380
fg = COLORS["secondary"]
369-
return tk.Button(
381+
if secondary:
382+
bg = COLORS["bg"]
383+
fg = COLORS["text"]
384+
frame = tk.Frame(
370385
parent,
386+
bg=bg,
387+
highlightthickness=0 if primary else 1,
388+
highlightbackground=COLORS["border"],
389+
cursor="hand2",
390+
)
391+
label = tk.Label(
392+
frame,
371393
text=text,
372-
command=command,
373394
bg=bg,
374395
fg=fg,
375-
activebackground=COLORS["panel_hover"],
376-
activeforeground=fg,
377-
relief=tk.FLAT if primary else tk.SOLID,
378-
bd=0 if primary else 1,
379-
padx=14 if not icon else 8,
380-
pady=8,
396+
padx=18 if not icon else 10,
397+
pady=10 if primary else 8,
381398
font=("Segoe UI", 9, "bold" if primary else "normal"),
382399
cursor="hand2",
383400
)
401+
label.pack(fill=tk.BOTH, expand=True)
402+
403+
def on_enter(_event: tk.Event) -> None:
404+
hover_bg = "#EDEDEA" if primary else COLORS["panel_hover"]
405+
frame.configure(bg=hover_bg)
406+
label.configure(bg=hover_bg)
407+
408+
def on_leave(_event: tk.Event) -> None:
409+
frame.configure(bg=bg)
410+
label.configure(bg=bg)
411+
412+
for widget in (frame, label):
413+
widget.bind("<Button-1>", lambda _event: command())
414+
widget.bind("<Enter>", on_enter)
415+
widget.bind("<Leave>", on_leave)
416+
return frame
384417

385418
def _set_mode(self, mode: str) -> None:
386419
self.mode_var.set(mode)
@@ -390,7 +423,25 @@ def _set_mode(self, mode: str) -> None:
390423
def _refresh_mode_cards(self) -> None:
391424
for value, frame in self.mode_cards.items():
392425
active = value == self.mode_var.get()
393-
frame.configure(highlightbackground=COLORS["red"] if active else COLORS["border"])
426+
frame.configure(
427+
bg=COLORS["bg"],
428+
highlightbackground=COLORS["red"] if active else COLORS["border"],
429+
highlightthickness=1,
430+
)
431+
self._set_descendant_bg(frame, COLORS["bg"])
432+
433+
def _hover_card(self, frame: tk.Frame, hovering: bool) -> None:
434+
if frame is self.mode_cards.get(self.mode_var.get()):
435+
return
436+
color = COLORS["panel_hover"] if hovering else COLORS["bg"]
437+
frame.configure(bg=color)
438+
self._set_descendant_bg(frame, color)
439+
440+
def _set_descendant_bg(self, widget: tk.Widget, color: str) -> None:
441+
for child in widget.winfo_children():
442+
if isinstance(child, (tk.Frame, tk.Label)):
443+
child.configure(bg=color)
444+
self._set_descendant_bg(child, color)
394445

395446
def select_folder(self) -> None:
396447
folder = filedialog.askdirectory(title="Select source video folder")
@@ -604,6 +655,27 @@ def _set_progress(self, value: int) -> None:
604655
self.progress.configure(value=value)
605656
self.progress_label.configure(text=f"{value}%")
606657

658+
def _on_resize(self, _event: tk.Event) -> None:
659+
width = self.root.winfo_width()
660+
if abs(width - self._last_resize_width) < 16:
661+
return
662+
self._last_resize_width = width
663+
self.root.after_idle(lambda: self._resize_table_columns(None))
664+
665+
def _resize_table_columns(self, _event: tk.Event | None) -> None:
666+
table_width = max(self.files_tree.winfo_width() - 24, 760)
667+
fixed = {
668+
"resolution": 132,
669+
"codec": 118,
670+
"fps": 88,
671+
"duration": 86,
672+
"status": 150,
673+
}
674+
filename_width = max(table_width - sum(fixed.values()), 260)
675+
self.files_tree.column("filename", width=filename_width)
676+
for column, width in fixed.items():
677+
self.files_tree.column(column, width=width)
678+
607679
def _log(self, message: str) -> None:
608680
stamp = time.strftime("%H:%M:%S")
609681
self.console.insert(tk.END, f"[{stamp}] {message}\n")

0 commit comments

Comments
 (0)