fix: improve redraw speed

This commit is contained in:
2026-04-14 14:26:10 -04:00
parent 66e7ca4fc7
commit 603af4f378
2 changed files with 144 additions and 45 deletions
+134 -43
View File
@@ -1639,6 +1639,14 @@ class SteamWorkshopGUI:
self.merge_btn = None self.merge_btn = None
self.is_rimworld_valid = False self.is_rimworld_valid = False
self._panel_shells = [] self._panel_shells = []
self._resize_after_id = None
self._bg_image_id = None
self._main_content_window_id = None
self._last_background_size = None
self._last_main_content_geometry = None
self._header_render_after_id = None
self._last_header_requested_size = None
self._last_header_render_size = None
# Store references for responsive layout # Store references for responsive layout
self.left_frame = None self.left_frame = None
@@ -1810,16 +1818,28 @@ class SteamWorkshopGUI:
if hasattr(self, 'right_frame') and self.right_frame: if hasattr(self, 'right_frame') and self.right_frame:
self.create_output_section(self.right_frame) self.create_output_section(self.right_frame)
def _update_main_background(self): def _update_main_background(self, width=None, height=None):
"""Update the main window background image""" """Update the main window background image"""
width = self.root.winfo_width() or 1920 width = width or self.root.winfo_width() or 1920
height = self.root.winfo_height() or 1080 height = height or self.root.winfo_height() or 1080
if width > 1 and height > 1: if width > 1 and height > 1:
size = (int(width), int(height))
if size == self._last_background_size:
return
self.bg_image_tk = self.load_background_image(width, height) self.bg_image_tk = self.load_background_image(width, height)
if self.bg_image_tk: if self.bg_image_tk:
self.bg_canvas.delete('bg') if self._bg_image_id is None:
self.bg_canvas.create_image(0, 0, image=self.bg_image_tk, anchor='nw', tags='bg') self._bg_image_id = self.bg_canvas.create_image(
self.bg_canvas.tag_lower('bg') 0,
0,
image=self.bg_image_tk,
anchor='nw',
tags='bg',
)
self.bg_canvas.tag_lower('bg')
else:
self.bg_canvas.itemconfigure(self._bg_image_id, image=self.bg_image_tk)
self._last_background_size = size
def _position_main_content(self): def _position_main_content(self):
"""Position the main content frame on the canvas with responsive sizing""" """Position the main content frame on the canvas with responsive sizing"""
@@ -1831,16 +1851,28 @@ class SteamWorkshopGUI:
x_pos = max(12, (width - content_width) // 2) x_pos = max(12, (width - content_width) // 2)
y_pos = max(12, (height - content_height) // 2) y_pos = max(12, (height - content_height) // 2)
self.bg_canvas.delete('main_content') geometry = (x_pos, y_pos, content_width, content_height)
self.bg_canvas.create_window( if geometry == self._last_main_content_geometry:
x_pos, return
y_pos,
window=self.main_content_frame, if self._main_content_window_id is None:
anchor='nw', self._main_content_window_id = self.bg_canvas.create_window(
width=content_width, x_pos,
height=content_height, y_pos,
tags='main_content', window=self.main_content_frame,
) anchor='nw',
width=content_width,
height=content_height,
tags='main_content',
)
else:
self.bg_canvas.coords(self._main_content_window_id, x_pos, y_pos)
self.bg_canvas.itemconfigure(
self._main_content_window_id,
width=content_width,
height=content_height,
)
self._last_main_content_geometry = geometry
def load_background_image(self, width, height): def load_background_image(self, width, height):
"""Load the Desolation art as the main-app backdrop.""" """Load the Desolation art as the main-app backdrop."""
@@ -1862,32 +1894,47 @@ class SteamWorkshopGUI:
if event.widget == self.root and hasattr(self, 'bg_canvas'): if event.widget == self.root and hasattr(self, 'bg_canvas'):
# First enforce minimum size # First enforce minimum size
self._enforce_minimum_size(event) self._enforce_minimum_size(event)
# Update background image
self._update_main_background()
current_width = self.root.winfo_width() current_width = self.root.winfo_width()
current_height = self.root.winfo_height() current_height = self.root.winfo_height()
if hasattr(self, 'header_canvas') and self.header_canvas.winfo_exists():
header_height = max(70, min(102, int(current_height * 0.082)))
if int(self.header_canvas.cget('height')) != header_height:
self.header_canvas.configure(height=header_height)
self._render_header_canvas()
desired_layout_mode = (
'side_by_side'
if self._should_use_side_by_side(current_width, current_height)
else 'stacked'
)
if desired_layout_mode != getattr(self, '_current_layout_mode', None):
self.root.after(100, self._update_responsive_layout)
# Store current window size
self._last_window_size = (current_width, current_height)
# Update content positioning
self._position_main_content() self._position_main_content()
if self._resize_after_id is not None:
self.root.after_cancel(self._resize_after_id)
self._resize_after_id = self.root.after(
70,
lambda width=current_width, height=current_height: self._apply_main_resize(width, height),
)
def _apply_main_resize(self, width, height):
"""Apply the expensive resize work after the user pauses dragging."""
self._resize_after_id = None
if not self.root.winfo_exists():
return
current_width = self.root.winfo_width()
current_height = self.root.winfo_height()
width = current_width or width
height = current_height or height
self._update_main_background(width, height)
if hasattr(self, 'header_canvas') and self.header_canvas.winfo_exists():
header_height = max(70, min(102, int(height * 0.082)))
if int(self.header_canvas.cget('height')) != header_height:
self.header_canvas.configure(height=header_height)
else:
self._schedule_header_render()
desired_layout_mode = (
'side_by_side'
if self._should_use_side_by_side(width, height)
else 'stacked'
)
if desired_layout_mode != getattr(self, '_current_layout_mode', None):
self._update_responsive_layout()
self._last_window_size = (width, height)
def _on_window_map(self, event): def _on_window_map(self, event):
"""Handle window map event (window becomes visible)""" """Handle window map event (window becomes visible)"""
@@ -1942,14 +1989,27 @@ class SteamWorkshopGUI:
self.progression_logo_source = None self.progression_logo_source = None
self.header_canvas.bind('<Configure>', self._on_header_canvas_configure) self.header_canvas.bind('<Configure>', self._on_header_canvas_configure)
self.root.after_idle(self._render_header_canvas) self.root.after_idle(self._schedule_header_render)
def _on_header_canvas_configure(self, event): def _on_header_canvas_configure(self, event):
"""Refresh the header texture and logo when the header size changes.""" """Refresh the header texture and logo when the header size changes."""
self._render_header_canvas() if event.widget != self.header_canvas:
return
requested_size = (max(1, int(event.width)), max(1, int(event.height)))
if requested_size == self._last_header_requested_size:
return
self._last_header_requested_size = requested_size
self._schedule_header_render()
def _schedule_header_render(self):
"""Debounce expensive header redraw work during window resizes."""
if self._header_render_after_id is not None:
self.root.after_cancel(self._header_render_after_id)
self._header_render_after_id = self.root.after(40, self._render_header_canvas)
def _render_header_canvas(self): def _render_header_canvas(self):
"""Render the striped header background and scale the logo to fit it.""" """Render the striped header background and scale the logo to fit it."""
self._header_render_after_id = None
if not hasattr(self, 'header_canvas') or not self.header_canvas.winfo_exists(): if not hasattr(self, 'header_canvas') or not self.header_canvas.winfo_exists():
return return
@@ -1957,6 +2017,9 @@ class SteamWorkshopGUI:
height = max(1, self.header_canvas.winfo_height()) height = max(1, self.header_canvas.winfo_height())
if width < 40 or height < 24: if width < 40 or height < 24:
return return
render_size = (width, height)
if render_size == self._last_header_render_size:
return
header_texture = create_striped_texture( header_texture = create_striped_texture(
width, width,
@@ -2017,6 +2080,7 @@ class SteamWorkshopGUI:
anchor='center', anchor='center',
tags='header_content', tags='header_content',
) )
self._last_header_render_size = render_size
def create_footer_frame(self, parent): def create_footer_frame(self, parent):
"""Create footer frame with HR Systems logo in bottom right - responsive sizing""" """Create footer frame with HR Systems logo in bottom right - responsive sizing"""
@@ -2403,17 +2467,40 @@ class SteamWorkshopGUI:
'inner_padding': (pad_x, pad_y), 'inner_padding': (pad_x, pad_y),
} }
canvas._panel_window_id = window_id canvas._panel_window_id = window_id
canvas._panel_after_id = None
canvas._panel_requested_size = None
canvas._panel_rendered_size = None
canvas.bind('<Configure>', self._refresh_panel_shell) canvas.bind('<Configure>', self._refresh_panel_shell)
self._panel_shells.append(canvas) self._panel_shells.append(canvas)
self.root.after_idle(lambda current_canvas=canvas: self._render_panel_shell(current_canvas)) self.root.after_idle(lambda current_canvas=canvas: self._schedule_panel_shell_render(current_canvas))
return shell, inner_frame return shell, inner_frame
def _refresh_panel_shell(self, event): def _refresh_panel_shell(self, event):
"""Repaint a striped shell when its canvas changes size.""" """Repaint a striped shell when its canvas changes size."""
self._render_panel_shell(event.widget) canvas = event.widget
if not canvas.winfo_exists():
return
requested_size = (max(1, int(event.width)), max(1, int(event.height)))
if requested_size == getattr(canvas, '_panel_requested_size', None):
return
canvas._panel_requested_size = requested_size
self._schedule_panel_shell_render(canvas)
def _schedule_panel_shell_render(self, canvas):
"""Debounce striped shell redraw work during resizes."""
if not canvas.winfo_exists():
return
after_id = getattr(canvas, '_panel_after_id', None)
if after_id is not None:
self.root.after_cancel(after_id)
canvas._panel_after_id = self.root.after(
40,
lambda current_canvas=canvas: self._render_panel_shell(current_canvas),
)
def _render_panel_shell(self, canvas): def _render_panel_shell(self, canvas):
"""Render the shared striped halo panel background onto a shell canvas.""" """Render the shared striped halo panel background onto a shell canvas."""
canvas._panel_after_id = None
if not canvas.winfo_exists() or not hasattr(canvas, '_panel_options'): if not canvas.winfo_exists() or not hasattr(canvas, '_panel_options'):
return return
@@ -2426,6 +2513,9 @@ class SteamWorkshopGUI:
minimum_size = (halo_padding * 2) + 6 minimum_size = (halo_padding * 2) + 6
if width < minimum_size or height < minimum_size: if width < minimum_size or height < minimum_size:
return return
render_size = (width, height)
if render_size == getattr(canvas, '_panel_rendered_size', None):
return
panel_width = max(1, width - (halo_padding * 2)) panel_width = max(1, width - (halo_padding * 2))
panel_height = max(1, height - (halo_padding * 2)) panel_height = max(1, height - (halo_padding * 2))
@@ -2457,6 +2547,7 @@ class SteamWorkshopGUI:
content_height = max(1, height - (2 * (halo_padding + inner_pad_y))) content_height = max(1, height - (2 * (halo_padding + inner_pad_y)))
canvas.coords(canvas._panel_window_id, halo_padding + inner_pad_x, halo_padding + inner_pad_y) canvas.coords(canvas._panel_window_id, halo_padding + inner_pad_x, halo_padding + inner_pad_y)
canvas.itemconfigure(canvas._panel_window_id, width=content_width, height=content_height) canvas.itemconfigure(canvas._panel_window_id, width=content_width, height=content_height)
canvas._panel_rendered_size = render_size
def create_steam_card(self, parent, title=None, padding=8): def create_steam_card(self, parent, title=None, padding=8):
"""Create a striped charcoal card container.""" """Create a striped charcoal card container."""
+10 -2
View File
@@ -1,3 +1,5 @@
from functools import lru_cache
from PIL import Image, ImageColor, ImageDraw from PIL import Image, ImageColor, ImageDraw
@@ -26,6 +28,12 @@ COLORS = {
} }
@lru_cache(maxsize=4)
def _load_rgba_source(image_path):
"""Load and cache a source image in RGBA form for repeated resizes."""
return Image.open(image_path).convert("RGBA")
def _rgba(color, alpha=255): def _rgba(color, alpha=255):
red, green, blue = ImageColor.getrgb(color) red, green, blue = ImageColor.getrgb(color)
return red, green, blue, alpha return red, green, blue, alpha
@@ -187,7 +195,7 @@ def load_cover_background(image_path, width, height, *, overlay_color=None, over
width = max(1, int(width)) width = max(1, int(width))
height = max(1, int(height)) height = max(1, int(height))
image = Image.open(image_path).convert("RGBA") image = _load_rgba_source(image_path)
source_ratio = image.width / image.height source_ratio = image.width / image.height
target_ratio = width / height target_ratio = width / height
@@ -198,7 +206,7 @@ def load_cover_background(image_path, width, height, *, overlay_color=None, over
new_width = width new_width = width
new_height = int(width / source_ratio) new_height = int(width / source_ratio)
image = image.resize((new_width, new_height), Image.Resampling.LANCZOS) image = image.resize((new_width, new_height), Image.Resampling.BICUBIC)
left = (new_width - width) // 2 left = (new_width - width) // 2
top = (new_height - height) // 2 top = (new_height - height) // 2
image = image.crop((left, top, left + width, top + height)) image = image.crop((left, top, left + width, top + height))