@@ -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