Skip to content

Commit 71eff08

Browse files
Merge pull request #26 from ZylosCore/perfo-fixing
Perfo fixing
2 parents cca59f3 + 94419a8 commit 71eff08

6 files changed

Lines changed: 134 additions & 189 deletions

File tree

project_info.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<project>
33
<name>BulkFolder</name>
4-
<version>1.7.12</version>
4+
<version>1.7.13</version>
55
<description>The safest and fastest bulk organizer and renamer.</description>
66
<author>Achraf KHABAR / Open Source Community</author>
77
<repository>https://github.com/Ashraf-Khabar/bulkfolder</repository>

src/bulkfolder/ui/app.py

Lines changed: 32 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import customtkinter as ctk
44

55
from pathlib import Path
6-
from PIL import Image, ImageTk, ImageDraw
6+
from PIL import Image, ImageTk
77

88
# --- Imports: Theme, State, and Logic ---
99
from .theme import DR_BG, DR_TEXT, DR_MUTED, DR_SURFACE, DR_BORDER, DR_PURPLE
@@ -35,9 +35,6 @@
3535

3636

3737
class SplashScreen(ctk.CTkToplevel):
38-
"""
39-
A temporary loading screen that appears before the main application window is ready.
40-
"""
4138
def __init__(self, master, logo_path):
4239
super().__init__(master)
4340
self.overrideredirect(True)
@@ -71,13 +68,21 @@ def __init__(self, master, logo_path):
7168

7269

7370
class App(ctk.CTk):
74-
"""
75-
The main application window with multithreaded operations support.
76-
"""
7771
def __init__(self):
72+
# OPTIMISATION 1: Force le mode de rendu haute performance sur Windows
73+
try:
74+
if sys.platform.startswith("win"):
75+
import ctypes
76+
ctypes.windll.shcore.SetProcessDpiAwareness(1)
77+
except Exception:
78+
pass
79+
7880
super().__init__()
7981
self.withdraw()
8082

83+
# Variable pour bloquer le rendu intensif durant le mouvement
84+
self._resize_after_id = None
85+
8186
self.settings: AppSettings = load_settings()
8287

8388
ctk.set_appearance_mode("dark")
@@ -95,6 +100,9 @@ def __init__(self):
95100
self.minsize(1020, 700)
96101
self.configure(fg_color=DR_BG)
97102

103+
# OPTIMISATION 2: Capture l'événement de redimensionnement
104+
self.bind("<Configure>", self._smart_resize)
105+
98106
self.logo_path = Path(__file__).resolve().parent.parent.parent / "assets" / "logo.png"
99107
self._ensure_logo_exists(self.logo_path)
100108

@@ -137,7 +145,6 @@ def __init__(self):
137145
self.content_area.grid_rowconfigure(1, weight=1)
138146
self.content_area.grid_rowconfigure(2, weight=0)
139147

140-
# The Topbar handles status and loading animations
141148
self.topbar_view = TopbarView(self.content_area, on_toggle_sidebar=self.toggle_terminal)
142149
self.topbar_view.grid(row=0, column=0, sticky="ew", padx=18, pady=(18, 8))
143150

@@ -146,12 +153,10 @@ def __init__(self):
146153
self.page_container.grid_columnconfigure(0, weight=1)
147154
self.page_container.grid_rowconfigure(0, weight=1)
148155

149-
# Global Terminal
150156
self.logs_view = LogsView(self.content_area, on_close=self.toggle_terminal)
151157
self.logs_view.grid(row=2, column=0, sticky="ew", padx=18, pady=(0, 18))
152158
self._terminal_visible = True
153159

154-
# Link log view to state for global access
155160
self.ui_state.log_view = self.logs_view
156161

157162
self.pages: dict[str, ctk.CTkFrame] = {}
@@ -176,40 +181,34 @@ def __init__(self):
176181

177182
self.after(2000, self._show_main_window)
178183

184+
def _smart_resize(self, event):
185+
"""Évite le recalcul saccadé en temps réel."""
186+
if event.widget == self:
187+
if self._resize_after_id:
188+
self.after_cancel(self._resize_after_id)
189+
# Attend 50ms de stabilité avant de rafraîchir
190+
self._resize_after_id = self.after(50, self._do_refresh)
191+
192+
def _do_refresh(self):
193+
"""Effectue le rendu final de manière fluide."""
194+
self.update_idletasks()
195+
self._resize_after_id = None
196+
179197
@staticmethod
180198
def _ensure_logo_exists(png_path: Path):
181199
ico_path = png_path.with_suffix(".ico")
182-
183-
if not png_path.exists():
184-
return
185-
200+
if not png_path.exists(): return
186201
try:
187-
# On ouvre ton PNG
188202
img = Image.open(png_path).convert("RGBA")
189-
190-
# ÉTAPE CLÉ : On enlève les bordures vides si ton PNG en a
191203
bbox = img.getbbox()
192-
if bbox:
193-
img = img.crop(bbox)
194-
195-
# On crée une liste des tailles standards Windows
196-
# La taille 256 est celle du bureau / La taille 32-48 est celle de la barre des tâches
204+
if bbox: img = img.crop(bbox)
197205
sizes = [256, 128, 64, 48, 32, 16]
198206
layers = []
199-
200207
for s in sizes:
201-
# On redimensionne proprement pour chaque taille
202208
resized = img.resize((s, s), Image.Resampling.LANCZOS)
203209
layers.append(resized)
204-
205-
# On sauvegarde l'ICO en incluant TOUTES ces couches
206-
# C'est ce qui garantit que l'icône "match" le PNG partout
207210
layers[0].save(ico_path, format="ICO", append_images=layers[1:])
208-
209-
print("ICO synchronisé avec le PNG (taille max).")
210-
except Exception as e:
211-
print(f"Erreur : {e}")
212-
211+
except Exception: pass
213212

214213
def _show_main_window(self):
215214
try: self.splash.destroy()
@@ -318,9 +317,9 @@ def switch_page(self, page_name: str) -> None:
318317
self.pages[page_name].grid(row=0, column=0, sticky="nsew")
319318
self._current_page = page_name
320319
self.topbar_view.set_title(page_name)
320+
self.update_idletasks()
321321

322322
def toggle_terminal(self) -> None:
323-
"""Shows or hides the bottom terminal console."""
324323
if self._terminal_visible:
325324
self.logs_view.grid_forget()
326325
self._terminal_visible = False
@@ -329,13 +328,9 @@ def toggle_terminal(self) -> None:
329328
self._terminal_visible = True
330329

331330
def set_status(self, s: str) -> None:
332-
"""Updates the status in the top bar."""
333331
self.topbar_view.set_status(s)
334332

335333
def log(self, s: str, level: str = "INFO") -> None:
336-
"""
337-
Global logging method. Thread-safe log through UIState.
338-
"""
339334
self.ui_state.log(s, level=level)
340335

341336
@staticmethod

src/bulkfolder/ui/views/dashboard.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ def render(self, scanned, mapping: dict[str, str]) -> None:
130130
self.top_list.insert("end", f"{i:>2}. {f.path.name}{self._human_bytes(f.size)}\n")
131131

132132
def _donut_figure(self, title: str, center_text: str, labels: list[str], values: list[float]) -> Figure:
133-
fig = Figure(figsize=(5, 3.2), dpi=100)
133+
# OPTIMISATION: DPI réduit à 75 pour un rendu plus fluide et rapide
134+
fig = Figure(figsize=(5, 3.2), dpi=75)
134135
ax = fig.add_subplot(111)
135136

136137
if not values or sum(values) <= 0:

src/bulkfolder/ui/views/logs.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
class LogsView(ctk.CTkFrame):
77
"""
88
A terminal-like log console with a black background and colored output.
9+
Optimized for fluidity.
910
"""
1011
def __init__(self, master, on_close=None, **kwargs):
1112
super().__init__(master, fg_color="#000000", corner_radius=8, border_width=1, border_color=DR_BORDER, **kwargs)
@@ -58,8 +59,14 @@ def __init__(self, master, on_close=None, **kwargs):
5859
self.text_area.configure(state="disabled")
5960

6061
def log(self, message: str, level: str = "INFO") -> None:
61-
"""Appends a new log entry."""
62+
"""Appends a new log entry with buffer management for performance."""
6263
self.text_area.configure(state="normal")
64+
65+
# OPTIMISATION: Limiter le nombre de lignes pour maintenir la fluidité
66+
line_count = int(self.text_area.index('end-1c').split('.')[0])
67+
if line_count > 500:
68+
self.text_area.delete("1.0", "2.0")
69+
6370
timestamp = datetime.now().strftime("%H:%M:%S")
6471
level = level.upper()
6572

0 commit comments

Comments
 (0)