Compare commits
4 Commits
66e7ca4fc7
..
0.2.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
30f3ab4ca5
|
|||
|
c26ca6ec02
|
|||
|
99d6d2a578
|
|||
|
603af4f378
|
+188
-74
@@ -843,15 +843,19 @@ class LoadingScreen:
|
||||
|
||||
def extract_name_from_id(self, expansion_id):
|
||||
"""Extract expansion name from ID as fallback"""
|
||||
if 'royalty' in expansion_id.lower():
|
||||
normalized_id = expansion_id.lower()
|
||||
|
||||
if normalized_id == 'ludeon.rimworld':
|
||||
return 'Core'
|
||||
elif 'royalty' in normalized_id:
|
||||
return 'Royalty'
|
||||
elif 'ideology' in expansion_id.lower():
|
||||
elif 'ideology' in normalized_id:
|
||||
return 'Ideology'
|
||||
elif 'biotech' in expansion_id.lower():
|
||||
elif 'biotech' in normalized_id:
|
||||
return 'Biotech'
|
||||
elif 'anomaly' in expansion_id.lower():
|
||||
elif 'anomaly' in normalized_id:
|
||||
return 'Anomaly'
|
||||
elif 'odyssey' in expansion_id.lower():
|
||||
elif 'odyssey' in normalized_id:
|
||||
return 'Odyssey'
|
||||
else:
|
||||
# Extract the last part after the last dot and capitalize
|
||||
@@ -1639,6 +1643,14 @@ class SteamWorkshopGUI:
|
||||
self.merge_btn = None
|
||||
self.is_rimworld_valid = False
|
||||
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
|
||||
self.left_frame = None
|
||||
@@ -1810,16 +1822,28 @@ class SteamWorkshopGUI:
|
||||
if hasattr(self, 'right_frame') and 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"""
|
||||
width = self.root.winfo_width() or 1920
|
||||
height = self.root.winfo_height() or 1080
|
||||
width = width or self.root.winfo_width() or 1920
|
||||
height = height or self.root.winfo_height() or 1080
|
||||
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)
|
||||
if self.bg_image_tk:
|
||||
self.bg_canvas.delete('bg')
|
||||
self.bg_canvas.create_image(0, 0, image=self.bg_image_tk, anchor='nw', tags='bg')
|
||||
self.bg_canvas.tag_lower('bg')
|
||||
if self._bg_image_id is None:
|
||||
self._bg_image_id = self.bg_canvas.create_image(
|
||||
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):
|
||||
"""Position the main content frame on the canvas with responsive sizing"""
|
||||
@@ -1831,16 +1855,28 @@ class SteamWorkshopGUI:
|
||||
x_pos = max(12, (width - content_width) // 2)
|
||||
y_pos = max(12, (height - content_height) // 2)
|
||||
|
||||
self.bg_canvas.delete('main_content')
|
||||
self.bg_canvas.create_window(
|
||||
x_pos,
|
||||
y_pos,
|
||||
window=self.main_content_frame,
|
||||
anchor='nw',
|
||||
width=content_width,
|
||||
height=content_height,
|
||||
tags='main_content',
|
||||
)
|
||||
geometry = (x_pos, y_pos, content_width, content_height)
|
||||
if geometry == self._last_main_content_geometry:
|
||||
return
|
||||
|
||||
if self._main_content_window_id is None:
|
||||
self._main_content_window_id = self.bg_canvas.create_window(
|
||||
x_pos,
|
||||
y_pos,
|
||||
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):
|
||||
"""Load the Desolation art as the main-app backdrop."""
|
||||
@@ -1862,32 +1898,47 @@ class SteamWorkshopGUI:
|
||||
if event.widget == self.root and hasattr(self, 'bg_canvas'):
|
||||
# First enforce minimum size
|
||||
self._enforce_minimum_size(event)
|
||||
|
||||
# Update background image
|
||||
self._update_main_background()
|
||||
|
||||
|
||||
current_width = self.root.winfo_width()
|
||||
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()
|
||||
|
||||
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):
|
||||
"""Handle window map event (window becomes visible)"""
|
||||
@@ -1942,14 +1993,27 @@ class SteamWorkshopGUI:
|
||||
self.progression_logo_source = None
|
||||
|
||||
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):
|
||||
"""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):
|
||||
"""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():
|
||||
return
|
||||
|
||||
@@ -1957,6 +2021,9 @@ class SteamWorkshopGUI:
|
||||
height = max(1, self.header_canvas.winfo_height())
|
||||
if width < 40 or height < 24:
|
||||
return
|
||||
render_size = (width, height)
|
||||
if render_size == self._last_header_render_size:
|
||||
return
|
||||
|
||||
header_texture = create_striped_texture(
|
||||
width,
|
||||
@@ -2017,6 +2084,7 @@ class SteamWorkshopGUI:
|
||||
anchor='center',
|
||||
tags='header_content',
|
||||
)
|
||||
self._last_header_render_size = render_size
|
||||
|
||||
def create_footer_frame(self, parent):
|
||||
"""Create footer frame with HR Systems logo in bottom right - responsive sizing"""
|
||||
@@ -2403,17 +2471,40 @@ class SteamWorkshopGUI:
|
||||
'inner_padding': (pad_x, pad_y),
|
||||
}
|
||||
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)
|
||||
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
|
||||
|
||||
def _refresh_panel_shell(self, event):
|
||||
"""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):
|
||||
"""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'):
|
||||
return
|
||||
|
||||
@@ -2426,6 +2517,9 @@ class SteamWorkshopGUI:
|
||||
minimum_size = (halo_padding * 2) + 6
|
||||
if width < minimum_size or height < minimum_size:
|
||||
return
|
||||
render_size = (width, height)
|
||||
if render_size == getattr(canvas, '_panel_rendered_size', None):
|
||||
return
|
||||
|
||||
panel_width = max(1, width - (halo_padding * 2))
|
||||
panel_height = max(1, height - (halo_padding * 2))
|
||||
@@ -2457,6 +2551,7 @@ class SteamWorkshopGUI:
|
||||
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.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):
|
||||
"""Create a striped charcoal card container."""
|
||||
@@ -3429,13 +3524,15 @@ class SteamWorkshopGUI:
|
||||
def get_core_mods_from_config(self):
|
||||
"""Get core mods from ModsConfig.xml knownExpansions section - includes ALL DLC the user owns"""
|
||||
core_mods = []
|
||||
seen_core_mod_ids = set()
|
||||
base_game_id = 'ludeon.rimworld'
|
||||
|
||||
try:
|
||||
if not self.modsconfig_path or not os.path.exists(self.modsconfig_path):
|
||||
msg = "ModsConfig.xml not found, using default core mods\n"
|
||||
self._safe_update_output(msg)
|
||||
print(msg.strip())
|
||||
return [('ludeon.rimworld', 'RimWorld')]
|
||||
return [(base_game_id, self.get_default_mod_name(base_game_id))]
|
||||
|
||||
msg = f"Reading ModsConfig.xml from: {self.modsconfig_path}\n"
|
||||
self._safe_update_output(msg)
|
||||
@@ -3457,34 +3554,47 @@ class SteamWorkshopGUI:
|
||||
msg = "No RimWorld path set, using fallback names\n"
|
||||
self._safe_update_output(msg)
|
||||
print(msg.strip())
|
||||
|
||||
def add_core_mod(mod_id):
|
||||
mod_id = (mod_id or "").strip()
|
||||
if not mod_id or mod_id in seen_core_mod_ids:
|
||||
return
|
||||
|
||||
mod_name = self.get_expansion_real_name(mod_id, rimworld_data_path)
|
||||
msg = f"get_expansion_real_name returned: {mod_name}\n"
|
||||
self._safe_update_output(msg)
|
||||
print(msg.strip())
|
||||
if not mod_name:
|
||||
mod_name = self.get_default_mod_name(mod_id)
|
||||
msg = f"Using fallback name: {mod_name}\n"
|
||||
self._safe_update_output(msg)
|
||||
print(msg.strip())
|
||||
|
||||
core_mods.append((mod_id, mod_name))
|
||||
seen_core_mod_ids.add(mod_id)
|
||||
msg = f"Added to core_mods: {mod_id} -> {mod_name}\n"
|
||||
self._safe_update_output(msg)
|
||||
print(msg.strip())
|
||||
|
||||
msg = "Seeding base game Core entry...\n"
|
||||
self._safe_update_output(msg)
|
||||
print(msg.strip())
|
||||
add_core_mod(base_game_id)
|
||||
|
||||
# Find knownExpansions section and include ALL expansions found
|
||||
# Find knownExpansions section and include all owned expansions after Core
|
||||
known_expansions_element = root.find('knownExpansions')
|
||||
if known_expansions_element is not None:
|
||||
msg = "Found knownExpansions section, processing all DLC...\n"
|
||||
msg = "Found knownExpansions section, processing entries...\n"
|
||||
self._safe_update_output(msg)
|
||||
print(msg.strip())
|
||||
for li in known_expansions_element.findall('li'):
|
||||
expansion_id = li.text
|
||||
if expansion_id:
|
||||
expansion_id = expansion_id.strip()
|
||||
msg = f"Processing DLC: {expansion_id}\n"
|
||||
self._safe_update_output(msg)
|
||||
print(msg.strip())
|
||||
# Use the same method as DLC detection to get real names
|
||||
mod_name = self.get_expansion_real_name(expansion_id, rimworld_data_path)
|
||||
msg = f"get_expansion_real_name returned: {mod_name}\n"
|
||||
self._safe_update_output(msg)
|
||||
print(msg.strip())
|
||||
if not mod_name:
|
||||
mod_name = self.get_default_mod_name(expansion_id)
|
||||
msg = f"Using fallback name: {mod_name}\n"
|
||||
self._safe_update_output(msg)
|
||||
print(msg.strip())
|
||||
core_mods.append((expansion_id, mod_name))
|
||||
msg = f"Added to core_mods: {expansion_id} -> {mod_name}\n"
|
||||
msg = f"Processing known expansion: {expansion_id}\n"
|
||||
self._safe_update_output(msg)
|
||||
print(msg.strip())
|
||||
add_core_mod(expansion_id)
|
||||
else:
|
||||
msg = "No knownExpansions section found in ModsConfig.xml\n"
|
||||
self._safe_update_output(msg)
|
||||
@@ -3492,7 +3602,7 @@ class SteamWorkshopGUI:
|
||||
|
||||
if not core_mods:
|
||||
# Fallback if no expansions found - just base game
|
||||
core_mods = [('ludeon.rimworld', 'RimWorld')]
|
||||
core_mods = [(base_game_id, self.get_default_mod_name(base_game_id))]
|
||||
msg = "No expansions found, using fallback base game only\n"
|
||||
self._safe_update_output(msg)
|
||||
print(msg.strip())
|
||||
@@ -3508,7 +3618,7 @@ class SteamWorkshopGUI:
|
||||
msg = f"Error reading core mods: {str(e)}\n"
|
||||
self._safe_update_output(msg)
|
||||
print(msg.strip())
|
||||
core_mods = [('ludeon.rimworld', 'RimWorld')]
|
||||
core_mods = [(base_game_id, self.get_default_mod_name(base_game_id))]
|
||||
|
||||
return core_mods
|
||||
|
||||
@@ -3549,15 +3659,19 @@ class SteamWorkshopGUI:
|
||||
|
||||
def extract_name_from_id(self, expansion_id):
|
||||
"""Extract expansion name from ID as fallback"""
|
||||
if 'royalty' in expansion_id.lower():
|
||||
normalized_id = expansion_id.lower()
|
||||
|
||||
if normalized_id == 'ludeon.rimworld':
|
||||
return 'Core'
|
||||
elif 'royalty' in normalized_id:
|
||||
return 'Royalty'
|
||||
elif 'ideology' in expansion_id.lower():
|
||||
elif 'ideology' in normalized_id:
|
||||
return 'Ideology'
|
||||
elif 'biotech' in expansion_id.lower():
|
||||
elif 'biotech' in normalized_id:
|
||||
return 'Biotech'
|
||||
elif 'anomaly' in expansion_id.lower():
|
||||
elif 'anomaly' in normalized_id:
|
||||
return 'Anomaly'
|
||||
elif 'odyssey' in expansion_id.lower():
|
||||
elif 'odyssey' in normalized_id:
|
||||
return 'Odyssey'
|
||||
else:
|
||||
# Extract the last part after the last dot and capitalize
|
||||
@@ -3569,7 +3683,7 @@ class SteamWorkshopGUI:
|
||||
def get_default_mod_name(self, mod_id):
|
||||
"""Get default display name for core mods"""
|
||||
default_names = {
|
||||
'ludeon.rimworld': 'RimWorld',
|
||||
'ludeon.rimworld': 'Core',
|
||||
'ludeon.rimworld.royalty': 'Royalty',
|
||||
'ludeon.rimworld.ideology': 'Ideology',
|
||||
'ludeon.rimworld.biotech': 'Biotech',
|
||||
|
||||
+10
-2
@@ -1,3 +1,5 @@
|
||||
from functools import lru_cache
|
||||
|
||||
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):
|
||||
red, green, blue = ImageColor.getrgb(color)
|
||||
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))
|
||||
height = max(1, int(height))
|
||||
|
||||
image = Image.open(image_path).convert("RGBA")
|
||||
image = _load_rgba_source(image_path)
|
||||
source_ratio = image.width / image.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_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
|
||||
top = (new_height - height) // 2
|
||||
image = image.crop((left, top, left + width, top + height))
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ Configuration settings for the update checker
|
||||
# Update checker configuration
|
||||
UPDATE_CONFIG = {
|
||||
# Current version of the application
|
||||
"current_version": "0.1.1",
|
||||
"current_version": "0.2.1",
|
||||
|
||||
# Repository information
|
||||
"repo_owner": "HRiggs",
|
||||
|
||||
Reference in New Issue
Block a user