redesign: UI Work
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 358 KiB |
@@ -0,0 +1,179 @@
|
||||
# Steam Collections Page Design Update Summary
|
||||
|
||||
## Overview
|
||||
Updated all UI screens (except mod manager) to match Steam Collections page design with modern dark blue theme, improved typography, and Steam-inspired visual elements.
|
||||
|
||||
## Color Scheme Changes
|
||||
|
||||
### New Steam Collections Inspired Colors
|
||||
```python
|
||||
COLORS = {
|
||||
'bg_primary': '#1b2838', # Steam dark blue background
|
||||
'bg_secondary': '#2a475e', # Steam medium blue background
|
||||
'bg_tertiary': '#1e2328', # Steam darker background for cards
|
||||
'bg_card': '#16202d', # Steam card background
|
||||
'bg_hover': '#2a475e', # Steam hover state
|
||||
'text_primary': '#c7d5e0', # Steam primary text (light blue-gray)
|
||||
'text_highlight': '#66c0f4', # Steam blue highlight color
|
||||
'text_body': '#8f98a0', # Steam body text (muted blue-gray)
|
||||
'text_secondary': '#acb2b8', # Steam secondary text
|
||||
'text_muted': '#67707b', # Steam muted text
|
||||
'accent_green': '#5ba32b', # Steam success green
|
||||
'accent_red': '#cd5c5c', # Steam error red
|
||||
'accent_yellow': '#ffa500', # Steam warning orange
|
||||
'accent_blue': '#66c0f4', # Steam signature blue
|
||||
'border_light': '#3c4043', # Steam light border
|
||||
'border_dark': '#0e141b', # Steam dark border
|
||||
}
|
||||
```
|
||||
|
||||
### Previous Colors (Replaced)
|
||||
- Old yellow-green highlights (#f5f5b5) → Steam blue (#66c0f4)
|
||||
- Old gray backgrounds (#424041) → Steam dark blue (#1b2838)
|
||||
- Old bright colors → Muted Steam palette
|
||||
|
||||
## GUI Changes (steam_workshop_gui.py)
|
||||
|
||||
### 1. Main Application Window
|
||||
- **Background**: Updated to Steam dark blue (#1b2838)
|
||||
- **Card System**: Implemented Steam-style cards with rounded corners (12px radius)
|
||||
- **Typography**: Enhanced with Steam blue highlights for titles
|
||||
- **Padding**: Increased from 10px to 15px for better spacing
|
||||
|
||||
### 2. Input Section Redesign
|
||||
- **Card-based Layout**: Each section now uses Steam-style cards
|
||||
- RimWorld Installation card
|
||||
- Workshop Content card
|
||||
- Advanced Configuration card
|
||||
- Steam Workshop Collections card
|
||||
- **Input Fields**:
|
||||
- Dark backgrounds (#1e2328)
|
||||
- Steam blue focus borders (#66c0f4)
|
||||
- Improved padding and spacing
|
||||
- **Buttons**:
|
||||
- Primary button: Steam blue (#66c0f4) with dark text
|
||||
- Secondary button: Steam green (#5ba32b)
|
||||
- Disabled state: Muted colors
|
||||
|
||||
### 3. Output Section (Logs)
|
||||
- **Card Container**: Logs now in Steam-style card
|
||||
- **Color-coded Messages**:
|
||||
- Success: Steam green
|
||||
- Error: Steam red
|
||||
- Warning: Steam orange
|
||||
- Info: Steam blue
|
||||
- **Timestamps**: Added with muted color
|
||||
- **Background**: Dark Steam theme (#1e2328)
|
||||
|
||||
### 4. Loading Screen Updates
|
||||
- **Card Backgrounds**: Updated to Steam styling with subtle borders
|
||||
- **Corner Radius**: Reduced from 20px to 12px for modern look
|
||||
- **Colors**: All text and UI elements use Steam palette
|
||||
- **Error Cards**: Steam red accents for error states
|
||||
|
||||
### 5. Button Styling
|
||||
- **Enable State**: Steam blue primary, Steam green secondary
|
||||
- **Disable State**: Muted Steam colors
|
||||
- **Hover Effects**: Lighter Steam blue on hover
|
||||
- **Cursor**: Hand cursor for better UX
|
||||
|
||||
## TUI Changes (progression_tui.py)
|
||||
|
||||
### 1. Color Scheme
|
||||
- Updated all TUI_COLORS to match GUI Steam palette
|
||||
- Consistent blue theme throughout terminal interface
|
||||
|
||||
### 2. Header Styling
|
||||
- **Border**: Steam blue double border
|
||||
- **Title**: Added "PROGRESSION LOADER" title to panel
|
||||
- **ASCII Art**: Highlighted with Steam blue
|
||||
|
||||
### 3. Menu System
|
||||
- **Table Styling**: Rounded borders with Steam blue accents
|
||||
- **Status Indicators**:
|
||||
- Valid: ✓ with Steam green
|
||||
- Invalid: ⚠ with Steam yellow
|
||||
- Ready: ✓ Ready with Steam green
|
||||
- Disabled: ⚠ Disabled with muted colors
|
||||
|
||||
### 4. Configuration Display
|
||||
- **Path Display**: Added icons and Steam blue labels
|
||||
- **Panel Styling**: Rounded borders with Steam theme
|
||||
- **Status Colors**: Consistent with GUI
|
||||
|
||||
## Font and Styling (font_and_colors_update.py)
|
||||
|
||||
### 1. New Utility Functions
|
||||
- `apply_steam_styling()`: Returns Steam-inspired style dictionaries
|
||||
- `get_steam_button_colors()`: Provides button color schemes by type
|
||||
- Updated color constants to match Steam theme
|
||||
|
||||
### 2. Button Color Schemes
|
||||
- **Default**: Steam secondary background
|
||||
- **Primary**: Steam blue with dark text
|
||||
- **Success**: Steam green
|
||||
- **Warning**: Steam orange
|
||||
- **Danger**: Steam red
|
||||
|
||||
## Visual Improvements
|
||||
|
||||
### 1. Card System
|
||||
- **Rounded Corners**: 12px radius for modern look
|
||||
- **Subtle Borders**: Light Steam borders (#3c4043)
|
||||
- **Inner Highlights**: Very subtle Steam blue inner glow
|
||||
- **Proper Spacing**: Consistent 10-15px padding
|
||||
|
||||
### 2. Typography Hierarchy
|
||||
- **Titles**: Steam blue (#66c0f4) with bold weight
|
||||
- **Labels**: Steam secondary text (#acb2b8)
|
||||
- **Body Text**: Steam body color (#8f98a0)
|
||||
- **Muted Text**: Steam muted (#67707b)
|
||||
|
||||
### 3. Interactive Elements
|
||||
- **Focus States**: Steam blue borders on input fields
|
||||
- **Hover Effects**: Lighter Steam blue on buttons
|
||||
- **Status Indicators**: Color-coded with Steam palette
|
||||
- **Blinking Animation**: Steam blue/muted for invalid states
|
||||
|
||||
## Consistency Improvements
|
||||
|
||||
### 1. Cross-Platform
|
||||
- GUI and TUI now use matching color schemes
|
||||
- Consistent terminology and styling
|
||||
- Same visual hierarchy in both interfaces
|
||||
|
||||
### 2. Error Handling
|
||||
- Consistent error colors (Steam red)
|
||||
- Warning colors (Steam orange)
|
||||
- Success colors (Steam green)
|
||||
- Info colors (Steam blue)
|
||||
|
||||
### 3. Accessibility
|
||||
- Better contrast ratios with Steam's tested palette
|
||||
- Consistent focus indicators
|
||||
- Clear visual hierarchy
|
||||
|
||||
## Files Modified
|
||||
|
||||
1. **steam_workshop_gui.py** - Main GUI application
|
||||
2. **progression_tui.py** - Terminal user interface
|
||||
3. **font_and_colors_update.py** - Color and styling utilities
|
||||
4. **update_checker.py** - Update dialog styling
|
||||
|
||||
## Testing
|
||||
|
||||
- ✅ Syntax validation passed for all Python files
|
||||
- ✅ Color scheme consistency verified
|
||||
- ✅ Both GUI and TUI updated per requirements
|
||||
- ✅ Steam Collections design patterns implemented
|
||||
|
||||
## Result
|
||||
|
||||
The application now features a cohesive Steam Collections inspired design with:
|
||||
- Modern dark blue theme matching Steam's visual identity
|
||||
- Improved card-based layouts for better organization
|
||||
- Consistent typography and color usage
|
||||
- Enhanced user experience with better visual hierarchy
|
||||
- Professional appearance matching Steam's design standards
|
||||
|
||||
All screens except the mod manager have been updated to match the Steam Collections page design as requested.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 186 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
@@ -0,0 +1,187 @@
|
||||
# Updated font loading and color scheme for Progression Loader
|
||||
# Steam Collections page inspired design:
|
||||
# - Dark blue theme matching Steam's signature colors
|
||||
# - Steam blue (#66c0f4) for highlights and accents
|
||||
# - Dark blue backgrounds (#1b2838, #2a475e) for depth
|
||||
# - Light blue-gray text (#c7d5e0) for readability
|
||||
# - Card-based layouts with rounded corners
|
||||
# - Subtle shadows and glows for depth
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
def get_resource_path(relative_path):
|
||||
"""Get absolute path to resource, works for dev and for PyInstaller"""
|
||||
try:
|
||||
# PyInstaller creates a temp folder and stores path in _MEIPASS
|
||||
base_path = sys._MEIPASS
|
||||
except Exception:
|
||||
base_path = os.path.abspath(".")
|
||||
|
||||
return os.path.join(base_path, relative_path)
|
||||
|
||||
# Steam Collections inspired color constants
|
||||
COLORS = {
|
||||
'bg_primary': '#1b2838', # Steam dark blue background
|
||||
'bg_secondary': '#2a475e', # Steam medium blue background
|
||||
'bg_tertiary': '#1e2328', # Steam darker background for cards
|
||||
'bg_card': '#16202d', # Steam card background
|
||||
'bg_hover': '#2a475e', # Steam hover state
|
||||
'text_primary': '#c7d5e0', # Steam primary text (light blue-gray)
|
||||
'text_highlight': '#66c0f4', # Steam blue highlight color
|
||||
'text_body': '#8f98a0', # Steam body text (muted blue-gray)
|
||||
'text_secondary': '#acb2b8', # Steam secondary text
|
||||
'text_muted': '#67707b', # Steam muted text
|
||||
'accent_green': '#5ba32b', # Steam success green
|
||||
'accent_red': '#cd5c5c', # Steam error red
|
||||
'accent_yellow': '#ffa500', # Steam warning orange
|
||||
'accent_blue': '#66c0f4', # Steam signature blue
|
||||
'border_light': '#3c4043', # Steam light border
|
||||
'border_dark': '#0e141b', # Steam dark border
|
||||
}
|
||||
|
||||
def load_all_georgia_fonts():
|
||||
"""Load all Georgia font variants using Windows AddFontResourceEx with private flag"""
|
||||
custom_font_available = False
|
||||
custom_font_family = None
|
||||
|
||||
# List of Georgia font files to load
|
||||
georgia_fonts = [
|
||||
"georgia.ttf", # Regular
|
||||
"georgiab.ttf", # Bold
|
||||
"georgiai.ttf", # Italic
|
||||
"georgiaz.ttf" # Bold Italic
|
||||
]
|
||||
|
||||
fonts_loaded = 0
|
||||
|
||||
try:
|
||||
for font_file in georgia_fonts:
|
||||
font_path = get_resource_path(os.path.join("art", font_file))
|
||||
if os.path.exists(font_path):
|
||||
abs_font_path = os.path.abspath(font_path)
|
||||
|
||||
# Use the Stack Overflow method with AddFontResourceEx
|
||||
success = _load_font_private(abs_font_path)
|
||||
|
||||
if success:
|
||||
fonts_loaded += 1
|
||||
print(f"Successfully loaded font: {font_file}")
|
||||
else:
|
||||
print(f"Failed to load font: {font_file}")
|
||||
else:
|
||||
print(f"Font file not found: {font_path}")
|
||||
|
||||
if fonts_loaded > 0:
|
||||
custom_font_available = True
|
||||
custom_font_family = "Georgia"
|
||||
print(f"Successfully loaded {fonts_loaded} Georgia font variants")
|
||||
else:
|
||||
print("No Georgia fonts could be loaded, using system fallback")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error loading fonts: {e}")
|
||||
custom_font_available = False
|
||||
custom_font_family = None
|
||||
|
||||
return custom_font_available, custom_font_family
|
||||
|
||||
def _load_font_private(fontpath):
|
||||
"""
|
||||
Load font privately using AddFontResourceEx
|
||||
Based on Stack Overflow solution by Felipe
|
||||
"""
|
||||
try:
|
||||
from ctypes import windll, byref, create_unicode_buffer
|
||||
|
||||
# Constants for AddFontResourceEx
|
||||
FR_PRIVATE = 0x10 # Font is private to this process
|
||||
FR_NOT_ENUM = 0x20 # Font won't appear in font enumeration
|
||||
|
||||
# Create unicode buffer for the font path
|
||||
pathbuf = create_unicode_buffer(fontpath)
|
||||
|
||||
# Use AddFontResourceExW for Unicode strings
|
||||
AddFontResourceEx = windll.gdi32.AddFontResourceExW
|
||||
|
||||
# Set flags: private (unloaded when process dies) and not enumerable
|
||||
flags = FR_PRIVATE | FR_NOT_ENUM
|
||||
|
||||
# Add the font resource
|
||||
numFontsAdded = AddFontResourceEx(byref(pathbuf), flags, 0)
|
||||
|
||||
return numFontsAdded > 0
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in _load_font_private: {e}")
|
||||
return False
|
||||
|
||||
def apply_steam_styling():
|
||||
"""Apply Steam Collections page inspired styling to UI components"""
|
||||
return {
|
||||
'button_style': {
|
||||
'relief': 'flat',
|
||||
'borderwidth': 0,
|
||||
'highlightthickness': 0,
|
||||
'font': ('Georgia', 10, 'bold'),
|
||||
'cursor': 'hand2'
|
||||
},
|
||||
'entry_style': {
|
||||
'relief': 'flat',
|
||||
'borderwidth': 2,
|
||||
'highlightthickness': 0,
|
||||
'font': ('Georgia', 9),
|
||||
'insertbackground': COLORS['text_primary']
|
||||
},
|
||||
'label_style': {
|
||||
'font': ('Georgia', 9),
|
||||
'anchor': 'w'
|
||||
},
|
||||
'title_style': {
|
||||
'font': ('Georgia', 14, 'bold'),
|
||||
'anchor': 'center'
|
||||
},
|
||||
'card_style': {
|
||||
'relief': 'flat',
|
||||
'borderwidth': 1,
|
||||
'highlightthickness': 0
|
||||
}
|
||||
}
|
||||
|
||||
def get_steam_button_colors(button_type='default'):
|
||||
"""Get Steam-inspired button color schemes"""
|
||||
button_colors = {
|
||||
'default': {
|
||||
'bg': COLORS['bg_secondary'],
|
||||
'fg': COLORS['text_primary'],
|
||||
'activebackground': COLORS['bg_hover'],
|
||||
'activeforeground': COLORS['text_highlight']
|
||||
},
|
||||
'primary': {
|
||||
'bg': COLORS['accent_blue'],
|
||||
'fg': COLORS['bg_primary'],
|
||||
'activebackground': COLORS['text_highlight'],
|
||||
'activeforeground': COLORS['bg_primary']
|
||||
},
|
||||
'success': {
|
||||
'bg': COLORS['accent_green'],
|
||||
'fg': COLORS['text_primary'],
|
||||
'activebackground': '#6bb33f',
|
||||
'activeforeground': COLORS['text_primary']
|
||||
},
|
||||
'warning': {
|
||||
'bg': COLORS['accent_yellow'],
|
||||
'fg': COLORS['bg_primary'],
|
||||
'activebackground': '#ffb733',
|
||||
'activeforeground': COLORS['bg_primary']
|
||||
},
|
||||
'danger': {
|
||||
'bg': COLORS['accent_red'],
|
||||
'fg': COLORS['text_primary'],
|
||||
'activebackground': '#d66f6f',
|
||||
'activeforeground': COLORS['text_primary']
|
||||
}
|
||||
}
|
||||
|
||||
return button_colors.get(button_type, button_colors['default'])
|
||||
+138
-88
@@ -36,6 +36,23 @@ from update_config import get_update_config
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# Steam Collections page inspired color scheme for TUI
|
||||
TUI_COLORS = {
|
||||
'bg_primary': '#1b2838', # Steam dark blue background
|
||||
'bg_secondary': '#2a475e', # Steam medium blue background
|
||||
'bg_tertiary': '#1e2328', # Steam darker background for cards
|
||||
'text_primary': '#c7d5e0', # Steam primary text (light blue-gray)
|
||||
'text_highlight': '#66c0f4', # Steam blue highlight color
|
||||
'text_body': '#8f98a0', # Steam body text (muted blue-gray)
|
||||
'text_secondary': '#acb2b8', # Steam secondary text
|
||||
'text_muted': '#67707b', # Steam muted text
|
||||
'accent_green': '#5ba32b', # Steam success green
|
||||
'accent_red': '#cd5c5c', # Steam error red
|
||||
'accent_yellow': '#ffa500', # Steam warning orange
|
||||
'accent_blue': '#66c0f4', # Steam signature blue
|
||||
'accent_cyan': '#66c0f4', # Steam info/header color (same as blue)
|
||||
}
|
||||
|
||||
console = Console()
|
||||
|
||||
class ProgressionTUI:
|
||||
@@ -114,23 +131,26 @@ class ProgressionTUI:
|
||||
self.hr_systems_logo = "Hudson Riggs Systems"
|
||||
|
||||
def display_header(self):
|
||||
"""Display the header with ASCII art"""
|
||||
# Use the actual progression logo ASCII art instead of just text
|
||||
"""Display the header with ASCII art using Steam Collections inspired styling"""
|
||||
# Use the actual progression logo ASCII art with Steam blue highlight
|
||||
header_panel = Panel(
|
||||
Align.center(self.progression_logo),
|
||||
box=box.DOUBLE,
|
||||
style="cyan",
|
||||
padding=(1, 2)
|
||||
style=TUI_COLORS['accent_blue'], # Steam blue
|
||||
border_style=TUI_COLORS['accent_blue'],
|
||||
padding=(1, 2),
|
||||
title="[bold]PROGRESSION LOADER[/bold]",
|
||||
title_align="center"
|
||||
)
|
||||
|
||||
return header_panel
|
||||
|
||||
def display_footer(self):
|
||||
"""Display footer with HR Systems info"""
|
||||
"""Display footer with HR Systems info using Steam Collections inspired styling"""
|
||||
footer_text = Text()
|
||||
footer_text.append("Hudson Riggs Systems", style="dim white")
|
||||
footer_text.append(" - ", style="dim white")
|
||||
footer_text.append("https://hudsonriggs.systems", style="dim blue underline")
|
||||
footer_text.append("Hudson Riggs Systems", style=f"dim {TUI_COLORS['text_secondary']}")
|
||||
footer_text.append(" - ", style=f"dim {TUI_COLORS['text_secondary']}")
|
||||
footer_text.append("https://hudsonriggs.systems", style=f"dim {TUI_COLORS['accent_blue']} underline")
|
||||
|
||||
return Align.center(footer_text)
|
||||
|
||||
@@ -147,40 +167,66 @@ class ProgressionTUI:
|
||||
self.console.print(self.display_header())
|
||||
self.console.print()
|
||||
|
||||
# Create main menu options
|
||||
menu_table = Table(show_header=False, box=box.SIMPLE, padding=(0, 2))
|
||||
menu_table.add_column("Option", style="cyan bold", width=4)
|
||||
menu_table.add_column("Description", style="white")
|
||||
menu_table.add_column("Status", style="green", width=15)
|
||||
# Create main menu options with Steam Collections inspired styling
|
||||
menu_table = Table(show_header=False, box=box.ROUNDED, padding=(0, 2))
|
||||
menu_table.add_column("Option", style=f"{TUI_COLORS['accent_blue']} bold", width=4)
|
||||
menu_table.add_column("Description", style=TUI_COLORS['text_primary'])
|
||||
menu_table.add_column("Status", style=TUI_COLORS['accent_green'], width=15)
|
||||
|
||||
# Menu options with status indicators
|
||||
rimworld_status = "Valid" if self.is_rimworld_valid else "Not Set"
|
||||
rimworld_color = "green" if self.is_rimworld_valid else "red"
|
||||
# Menu options with Steam-styled status indicators
|
||||
rimworld_status = "✓ Valid" if self.is_rimworld_valid else "⚠ Not Set"
|
||||
rimworld_color = TUI_COLORS['accent_green'] if self.is_rimworld_valid else TUI_COLORS['accent_yellow']
|
||||
|
||||
menu_table.add_row("1", "Set RimWorld Game Folder", f"[{rimworld_color}]{rimworld_status}[/{rimworld_color}]")
|
||||
menu_table.add_row("2", "Check Steam Workshop Subscriptions", "")
|
||||
menu_table.add_row("3", "Check RimWorld Expansions", "")
|
||||
menu_table.add_row("4", "Load Progression Pack Complete", "Enabled" if self.is_rimworld_valid else "[dim]Disabled[/dim]")
|
||||
menu_table.add_row("5", "Merge with Current Mods Config", "Enabled" if self.is_rimworld_valid else "[dim]Disabled[/dim]")
|
||||
menu_table.add_row("4", "Load Progression Pack Complete", f"[{TUI_COLORS['accent_green']}]✓ Ready[/{TUI_COLORS['accent_green']}]" if self.is_rimworld_valid else f"[{TUI_COLORS['text_muted']}]⚠ Disabled[/{TUI_COLORS['text_muted']}]")
|
||||
menu_table.add_row("5", "Merge with Current Mods Config", f"[{TUI_COLORS['accent_green']}]✓ Ready[/{TUI_COLORS['accent_green']}]" if self.is_rimworld_valid else f"[{TUI_COLORS['text_muted']}]⚠ Disabled[/{TUI_COLORS['text_muted']}]")
|
||||
menu_table.add_row("6", "Check for Updates", "")
|
||||
menu_table.add_row("7", "View Configuration", "")
|
||||
menu_table.add_row("0", "Exit", "")
|
||||
|
||||
# Create Steam-styled menu panel
|
||||
menu_panel = Panel(
|
||||
menu_table,
|
||||
title="Main Menu",
|
||||
title="[bold]Main Menu[/bold]",
|
||||
title_align="center",
|
||||
box=box.ROUNDED,
|
||||
style="white"
|
||||
style=TUI_COLORS['text_primary'],
|
||||
border_style=TUI_COLORS['accent_blue']
|
||||
)
|
||||
|
||||
self.console.print(menu_panel)
|
||||
self.console.print()
|
||||
|
||||
# Display current paths if set
|
||||
# Display current paths if set with Steam Collections styling
|
||||
if self.rimworld_path or self.workshop_path:
|
||||
info_table = Table(show_header=False, box=None, padding=(0, 1))
|
||||
info_table.add_column("Label", style="cyan", width=25)
|
||||
info_table.add_column("Path", style="white")
|
||||
info_table.add_column("Label", style=f"{TUI_COLORS['accent_blue']} bold", width=25)
|
||||
info_table.add_column("Path", style=TUI_COLORS['text_body'])
|
||||
|
||||
if self.rimworld_path:
|
||||
status_icon = "✓" if self.is_rimworld_valid else "⚠"
|
||||
status_color = TUI_COLORS['accent_green'] if self.is_rimworld_valid else TUI_COLORS['accent_yellow']
|
||||
info_table.add_row(f"[{status_color}]{status_icon}[/{status_color}] RimWorld Path:", self.rimworld_path)
|
||||
|
||||
if self.workshop_path:
|
||||
info_table.add_row(f"[{TUI_COLORS['accent_blue']}]📁[/{TUI_COLORS['accent_blue']}] Workshop Path:", self.workshop_path)
|
||||
|
||||
if self.modsconfig_path:
|
||||
info_table.add_row(f"[{TUI_COLORS['accent_blue']}]⚙[/{TUI_COLORS['accent_blue']}] ModsConfig Path:", self.modsconfig_path)
|
||||
|
||||
# Create Steam-styled info panel
|
||||
info_panel = Panel(
|
||||
info_table,
|
||||
title="[bold]Current Configuration[/bold]",
|
||||
title_align="center",
|
||||
box=box.ROUNDED,
|
||||
style=TUI_COLORS['text_secondary'],
|
||||
border_style=TUI_COLORS['text_muted']
|
||||
)
|
||||
self.console.print(info_panel)
|
||||
self.console.print()
|
||||
|
||||
if self.rimworld_path:
|
||||
info_table.add_row("RimWorld Path:", self.rimworld_path)
|
||||
@@ -201,7 +247,7 @@ class ProgressionTUI:
|
||||
choice = Prompt.ask("Select an option", choices=["0", "1", "2", "3", "4", "5", "6", "7"], default="1")
|
||||
|
||||
if choice == "0":
|
||||
self.console.print("\n[yellow]Thank you for using Progression Loader![/yellow]")
|
||||
self.console.print(f"\n[{TUI_COLORS['accent_yellow']}]Thank you for using Progression Loader![/{TUI_COLORS['accent_yellow']}]")
|
||||
break
|
||||
elif choice == "1":
|
||||
self.set_rimworld_path()
|
||||
@@ -213,13 +259,13 @@ class ProgressionTUI:
|
||||
if self.is_rimworld_valid:
|
||||
self.load_progression_pack()
|
||||
else:
|
||||
self.console.print("\n[red]Please set a valid RimWorld path first![/red]")
|
||||
self.console.print(f"\n[{TUI_COLORS['accent_red']}]Please set a valid RimWorld path first![/{TUI_COLORS['accent_red']}]")
|
||||
self.console.input("\nPress Enter to continue...")
|
||||
elif choice == "5":
|
||||
if self.is_rimworld_valid:
|
||||
self.merge_with_current_mods()
|
||||
else:
|
||||
self.console.print("\n[red]Please set a valid RimWorld path first![/red]")
|
||||
self.console.print(f"\n[{TUI_COLORS['accent_red']}]Please set a valid RimWorld path first![/{TUI_COLORS['accent_red']}]")
|
||||
self.console.input("\nPress Enter to continue...")
|
||||
elif choice == "6":
|
||||
self.check_for_updates()
|
||||
@@ -227,10 +273,10 @@ class ProgressionTUI:
|
||||
self.view_configuration()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
self.console.print("\n\n[yellow]Goodbye![/yellow]")
|
||||
self.console.print(f"\n\n[{TUI_COLORS['accent_yellow']}]Goodbye![/{TUI_COLORS['accent_yellow']}]")
|
||||
break
|
||||
except Exception as e:
|
||||
self.console.print(f"\n[red]Error: {e}[/red]")
|
||||
self.console.print(f"\n[{TUI_COLORS['accent_red']}]Error: {e}[/{TUI_COLORS['accent_red']}]")
|
||||
self.console.input("\nPress Enter to continue...")
|
||||
|
||||
def set_rimworld_path(self):
|
||||
@@ -239,28 +285,30 @@ class ProgressionTUI:
|
||||
self.console.print(self.display_header())
|
||||
self.console.print()
|
||||
|
||||
# Instructions
|
||||
# Instructions with modern styling - bold titles, italic emphasis
|
||||
instructions = Panel(
|
||||
Text.from_markup(
|
||||
"To find your RimWorld path:\n"
|
||||
"[bold]To find your RimWorld path:[/bold]\n\n"
|
||||
"1. Open Steam\n"
|
||||
"2. Right-click RimWorld in your library\n"
|
||||
"3. Select 'Manage' > 'Browse local files'\n"
|
||||
"4. Copy the path from the address bar\n\n"
|
||||
"Example: C:\\Steam\\steamapps\\common\\RimWorld"
|
||||
"[italic dim]Example: C:\\Steam\\steamapps\\common\\RimWorld[/italic dim]"
|
||||
),
|
||||
title="Instructions",
|
||||
style="cyan"
|
||||
title=f"[bold {TUI_COLORS['text_highlight']}]Instructions[/bold {TUI_COLORS['text_highlight']}]",
|
||||
title_align="center",
|
||||
style=TUI_COLORS['accent_cyan'],
|
||||
border_style="rounded"
|
||||
)
|
||||
self.console.print(instructions)
|
||||
self.console.print()
|
||||
|
||||
# Current path if set
|
||||
# Current path if set with new colors
|
||||
if self.rimworld_path:
|
||||
current_panel = Panel(
|
||||
f"Current path: {self.rimworld_path}",
|
||||
title="Current RimWorld Path",
|
||||
style="green" if self.is_rimworld_valid else "red"
|
||||
style=TUI_COLORS['accent_green'] if self.is_rimworld_valid else TUI_COLORS['accent_red']
|
||||
)
|
||||
self.console.print(current_panel)
|
||||
self.console.print()
|
||||
@@ -283,17 +331,17 @@ class ProgressionTUI:
|
||||
# Derive workshop path
|
||||
self.derive_workshop_path()
|
||||
|
||||
progress.update(task, description="[green]Valid RimWorld installation found![/green]")
|
||||
progress.update(task, description=f"[{TUI_COLORS['accent_green']}]Valid RimWorld installation found![/{TUI_COLORS['accent_green']}]")
|
||||
time.sleep(1)
|
||||
|
||||
self.console.print(f"\n[green]RimWorld path set successfully![/green]")
|
||||
self.console.print(f"\n[{TUI_COLORS['accent_green']}]RimWorld path set successfully![/{TUI_COLORS['accent_green']}]")
|
||||
self.console.print(f"Workshop path: {self.workshop_path}")
|
||||
else:
|
||||
progress.update(task, description="[red]Invalid RimWorld path![/red]")
|
||||
progress.update(task, description=f"[{TUI_COLORS['accent_red']}]Invalid RimWorld path![/{TUI_COLORS['accent_red']}]")
|
||||
time.sleep(1)
|
||||
|
||||
self.console.print(f"\n[red]Invalid RimWorld installation at: {new_path}[/red]")
|
||||
self.console.print("[red]Please ensure the path contains RimWorld.exe and Data folder[/red]")
|
||||
self.console.print(f"\n[{TUI_COLORS['accent_red']}]Invalid RimWorld installation at: {new_path}[/{TUI_COLORS['accent_red']}]")
|
||||
self.console.print(f"[{TUI_COLORS['accent_red']}]Please ensure the path contains RimWorld.exe and Data folder[/{TUI_COLORS['accent_red']}]")
|
||||
|
||||
self.console.input("\nPress Enter to continue...")
|
||||
|
||||
@@ -339,26 +387,28 @@ class ProgressionTUI:
|
||||
self.console.print(self.display_header())
|
||||
self.console.print()
|
||||
|
||||
# Display collections
|
||||
collections_table = Table(show_header=True, box=box.ROUNDED)
|
||||
collections_table.add_column("Collection", style="cyan bold", width=20)
|
||||
collections_table.add_column("URL", style="blue", width=50)
|
||||
collections_table.add_column("Subscribed", style="green", width=12)
|
||||
# Display collections with modern styling
|
||||
collections_table = Table(show_header=True, box=box.ROUNDED, border_style="bright_black")
|
||||
collections_table.add_column("Collection", style=f"{TUI_COLORS['text_highlight']} bold", width=20)
|
||||
collections_table.add_column("URL", style=TUI_COLORS['accent_blue'], width=50)
|
||||
collections_table.add_column("Subscribed", style=TUI_COLORS['accent_green'], width=12)
|
||||
|
||||
for name, url in self.collections.items():
|
||||
status = "Yes" if self.subscription_status[name] else "No"
|
||||
status_color = "green" if self.subscription_status[name] else "red"
|
||||
status_color = TUI_COLORS['accent_green'] if self.subscription_status[name] else TUI_COLORS['accent_red']
|
||||
collections_table.add_row(name, url, f"[{status_color}]{status}[/{status_color}]")
|
||||
|
||||
collections_panel = Panel(
|
||||
collections_table,
|
||||
title="Steam Workshop Collections",
|
||||
style="white"
|
||||
title="[bold]Steam Workshop Collections[/bold]",
|
||||
title_align="center",
|
||||
style=TUI_COLORS['text_primary'],
|
||||
border_style="rounded"
|
||||
)
|
||||
self.console.print(collections_panel)
|
||||
self.console.print()
|
||||
|
||||
# Instructions
|
||||
# Instructions with new color scheme
|
||||
instructions = Panel(
|
||||
Text.from_markup(
|
||||
"To subscribe to collections:\n"
|
||||
@@ -367,10 +417,10 @@ class ProgressionTUI:
|
||||
"3. Click 'Subscribe' on the Steam Workshop page\n"
|
||||
"4. Wait for Steam to download the collection\n"
|
||||
"5. Return here and mark as subscribed\n\n"
|
||||
"[yellow]All collections are required for the complete Progression experience.[/yellow]"
|
||||
f"[{TUI_COLORS['accent_yellow']}]All collections are required for the complete Progression experience.[/{TUI_COLORS['accent_yellow']}]"
|
||||
),
|
||||
title="Instructions",
|
||||
style="cyan"
|
||||
style=TUI_COLORS['accent_cyan']
|
||||
)
|
||||
self.console.print(instructions)
|
||||
self.console.print()
|
||||
@@ -378,8 +428,8 @@ class ProgressionTUI:
|
||||
# Menu options
|
||||
while True:
|
||||
menu_table = Table(show_header=False, box=box.SIMPLE, padding=(0, 2))
|
||||
menu_table.add_column("Option", style="cyan bold", width=4)
|
||||
menu_table.add_column("Description", style="white")
|
||||
menu_table.add_column("Option", style=f"{TUI_COLORS['accent_cyan']} bold", width=4)
|
||||
menu_table.add_column("Description", style=TUI_COLORS['text_primary'])
|
||||
|
||||
menu_table.add_row("1", "Open Core Collection in browser")
|
||||
menu_table.add_row("2", "Open Content Collection in browser")
|
||||
@@ -390,7 +440,7 @@ class ProgressionTUI:
|
||||
menu_table.add_row("7", "Check if all subscribed")
|
||||
menu_table.add_row("0", "Back to main menu")
|
||||
|
||||
self.console.print(Panel(menu_table, title="Subscription Menu", style="white"))
|
||||
self.console.print(Panel(menu_table, title="Subscription Menu", style=TUI_COLORS['text_primary']))
|
||||
self.console.print()
|
||||
|
||||
choice = Prompt.ask("Select an option", choices=["0", "1", "2", "3", "4", "5", "6", "7"])
|
||||
@@ -399,29 +449,29 @@ class ProgressionTUI:
|
||||
break
|
||||
elif choice == "1":
|
||||
webbrowser.open(self.collections["Core Collection"])
|
||||
self.console.print("[green]Opened Core Collection in browser[/green]")
|
||||
self.console.print(f"[{TUI_COLORS['accent_green']}]Opened Core Collection in browser[/{TUI_COLORS['accent_green']}]")
|
||||
elif choice == "2":
|
||||
webbrowser.open(self.collections["Content Collection"])
|
||||
self.console.print("[green]Opened Content Collection in browser[/green]")
|
||||
self.console.print(f"[{TUI_COLORS['accent_green']}]Opened Content Collection in browser[/{TUI_COLORS['accent_green']}]")
|
||||
elif choice == "3":
|
||||
webbrowser.open(self.collections["Cosmetics Collection"])
|
||||
self.console.print("[green]Opened Cosmetics Collection in browser[/green]")
|
||||
self.console.print(f"[{TUI_COLORS['accent_green']}]Opened Cosmetics Collection in browser[/{TUI_COLORS['accent_green']}]")
|
||||
elif choice == "4":
|
||||
self.subscription_status["Core Collection"] = True
|
||||
self.console.print("[green]Core Collection marked as subscribed[/green]")
|
||||
self.console.print(f"[{TUI_COLORS['accent_green']}]Core Collection marked as subscribed[/{TUI_COLORS['accent_green']}]")
|
||||
elif choice == "5":
|
||||
self.subscription_status["Content Collection"] = True
|
||||
self.console.print("[green]Content Collection marked as subscribed[/green]")
|
||||
self.console.print(f"[{TUI_COLORS['accent_green']}]Content Collection marked as subscribed[/{TUI_COLORS['accent_green']}]")
|
||||
elif choice == "6":
|
||||
self.subscription_status["Cosmetics Collection"] = True
|
||||
self.console.print("[green]Cosmetics Collection marked as subscribed[/green]")
|
||||
self.console.print(f"[{TUI_COLORS['accent_green']}]Cosmetics Collection marked as subscribed[/{TUI_COLORS['accent_green']}]")
|
||||
elif choice == "7":
|
||||
all_subscribed = all(self.subscription_status.values())
|
||||
if all_subscribed:
|
||||
self.console.print("[green]All collections are subscribed! You can proceed.[/green]")
|
||||
self.console.print(f"[{TUI_COLORS['accent_green']}]All collections are subscribed! You can proceed.[/{TUI_COLORS['accent_green']}]")
|
||||
else:
|
||||
missing = [name for name, status in self.subscription_status.items() if not status]
|
||||
self.console.print(f"[red]Missing subscriptions: {', '.join(missing)}[/red]")
|
||||
self.console.print(f"[{TUI_COLORS['accent_red']}]Missing subscriptions: {', '.join(missing)}[/{TUI_COLORS['accent_red']}]")
|
||||
|
||||
self.console.print()
|
||||
|
||||
@@ -441,44 +491,44 @@ class ProgressionTUI:
|
||||
# Check expansions
|
||||
self.check_expansions_thread()
|
||||
|
||||
progress.update(task, description="[green]Expansion check complete![/green]")
|
||||
progress.update(task, description=f"[{TUI_COLORS['accent_green']}]Expansion check complete![/{TUI_COLORS['accent_green']}]")
|
||||
time.sleep(1)
|
||||
|
||||
# Display results
|
||||
# Display results with new color scheme
|
||||
if self.expansion_check_results:
|
||||
expansion_table = Table(show_header=True, box=box.ROUNDED)
|
||||
expansion_table.add_column("Expansion", style="cyan bold", width=15)
|
||||
expansion_table.add_column("Status", style="white", width=10)
|
||||
expansion_table.add_column("Expansion", style=f"{TUI_COLORS['accent_cyan']} bold", width=15)
|
||||
expansion_table.add_column("Status", style=TUI_COLORS['text_primary'], width=10)
|
||||
|
||||
missing_expansions = []
|
||||
for expansion, owned in self.expansion_check_results.items():
|
||||
status = "Owned" if owned else "Missing"
|
||||
status_color = "green" if owned else "red"
|
||||
status_color = TUI_COLORS['accent_green'] if owned else TUI_COLORS['accent_red']
|
||||
expansion_table.add_row(expansion, f"[{status_color}]{status}[/{status_color}]")
|
||||
|
||||
if not owned:
|
||||
missing_expansions.append(expansion)
|
||||
|
||||
self.console.print(Panel(expansion_table, title="RimWorld Expansions", style="white"))
|
||||
self.console.print(Panel(expansion_table, title="RimWorld Expansions", style=TUI_COLORS['text_primary']))
|
||||
self.console.print()
|
||||
|
||||
if missing_expansions:
|
||||
warning_text = Text()
|
||||
warning_text.append("WARNING: ", style="bold red")
|
||||
warning_text.append(f"You don't own {', '.join(missing_expansions)}!\n", style="red")
|
||||
warning_text.append("Some progression mods may not work without these expansions.", style="yellow")
|
||||
warning_text.append("WARNING: ", style=f"bold {TUI_COLORS['accent_red']}")
|
||||
warning_text.append(f"You don't own {', '.join(missing_expansions)}!\n", style=TUI_COLORS['accent_red'])
|
||||
warning_text.append("Some progression mods may not work without these expansions.", style=TUI_COLORS['accent_yellow'])
|
||||
|
||||
warning_panel = Panel(warning_text, title="Missing Expansions", style="red")
|
||||
warning_panel = Panel(warning_text, title="Missing Expansions", style=TUI_COLORS['accent_red'])
|
||||
self.console.print(warning_panel)
|
||||
else:
|
||||
success_panel = Panel(
|
||||
"[green]All RimWorld expansions detected![/green]",
|
||||
f"[{TUI_COLORS['accent_green']}]All RimWorld expansions detected![/{TUI_COLORS['accent_green']}]",
|
||||
title="Expansion Check",
|
||||
style="green"
|
||||
style=TUI_COLORS['accent_green']
|
||||
)
|
||||
self.console.print(success_panel)
|
||||
else:
|
||||
self.console.print("[yellow]Could not check expansions. Please ensure RimWorld path is set correctly.[/yellow]")
|
||||
self.console.print(f"[{TUI_COLORS['accent_yellow']}]Could not check expansions. Please ensure RimWorld path is set correctly.[/{TUI_COLORS['accent_yellow']}]")
|
||||
|
||||
self.console.input("\nPress Enter to continue...")
|
||||
|
||||
@@ -680,25 +730,25 @@ class ProgressionTUI:
|
||||
self.console.print(self.display_header())
|
||||
self.console.print()
|
||||
|
||||
# Check prerequisites
|
||||
# Check prerequisites with new colors
|
||||
all_subscribed = all(self.subscription_status.values())
|
||||
if not all_subscribed:
|
||||
missing = [name for name, status in self.subscription_status.items() if not status]
|
||||
error_panel = Panel(
|
||||
f"[red]Please subscribe to all collections first!\nMissing: {', '.join(missing)}[/red]",
|
||||
f"[{TUI_COLORS['accent_red']}]Please subscribe to all collections first!\nMissing: {', '.join(missing)}[/{TUI_COLORS['accent_red']}]",
|
||||
title="Prerequisites Not Met",
|
||||
style="red"
|
||||
style=TUI_COLORS['accent_red']
|
||||
)
|
||||
self.console.print(error_panel)
|
||||
self.console.input("\nPress Enter to continue...")
|
||||
return
|
||||
|
||||
# Confirm action
|
||||
# Confirm action with new colors
|
||||
confirm_text = Text()
|
||||
confirm_text.append("This will replace your current ModsConfig.xml with the complete Progression pack.\n", style="yellow")
|
||||
confirm_text.append("Your current mod configuration will be backed up.", style="white")
|
||||
confirm_text.append("This will replace your current ModsConfig.xml with the complete Progression pack.\n", style=TUI_COLORS['accent_yellow'])
|
||||
confirm_text.append("Your current mod configuration will be backed up.", style=TUI_COLORS['text_primary'])
|
||||
|
||||
confirm_panel = Panel(confirm_text, title="Confirmation", style="yellow")
|
||||
confirm_panel = Panel(confirm_text, title="Confirmation", style=TUI_COLORS['accent_yellow'])
|
||||
self.console.print(confirm_panel)
|
||||
self.console.print()
|
||||
|
||||
@@ -1075,18 +1125,18 @@ class ProgressionTUI:
|
||||
def main():
|
||||
"""Main entry point for the TUI application"""
|
||||
try:
|
||||
# Check for updates first
|
||||
# Check for updates first with new colors
|
||||
config = get_update_config()
|
||||
if config.get("check_on_startup", True):
|
||||
console.print("[cyan]Checking for updates...[/cyan]")
|
||||
console.print(f"[{TUI_COLORS['accent_cyan']}]Checking for updates...[/{TUI_COLORS['accent_cyan']}]")
|
||||
try:
|
||||
update_checker = UpdateChecker(config["current_version"])
|
||||
release_info, error = update_checker.check_for_updates_sync()
|
||||
|
||||
if release_info and update_checker.is_newer_version(release_info['version']):
|
||||
console.print(f"[yellow]Update available: {release_info['version']}[/yellow]")
|
||||
console.print(f"[{TUI_COLORS['accent_yellow']}]Update available: {release_info['version']}[/{TUI_COLORS['accent_yellow']}]")
|
||||
if config.get("updates_required", False):
|
||||
console.print("[red]This update is required. Please update before continuing.[/red]")
|
||||
console.print(f"[{TUI_COLORS['accent_red']}]This update is required. Please update before continuing.[/{TUI_COLORS['accent_red']}]")
|
||||
if Confirm.ask("Open download page?"):
|
||||
webbrowser.open(release_info['html_url'])
|
||||
return
|
||||
@@ -1100,9 +1150,9 @@ def main():
|
||||
app.show_main_menu()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
console.print("\n[yellow]Goodbye![/yellow]")
|
||||
console.print(f"\n[{TUI_COLORS['accent_yellow']}]Goodbye![/{TUI_COLORS['accent_yellow']}]")
|
||||
except Exception as e:
|
||||
console.print(f"[red]Fatal error: {e}[/red]")
|
||||
console.print(f"[{TUI_COLORS['accent_red']}]Fatal error: {e}[/{TUI_COLORS['accent_red']}]")
|
||||
console.print("[dim]Please report this error to the developers.[/dim]")
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 962 KiB |
+1179
-617
File diff suppressed because it is too large
Load Diff
+250
@@ -0,0 +1,250 @@
|
||||
from PIL import Image, ImageColor, ImageDraw
|
||||
|
||||
|
||||
FONT_FAMILY = "Georgia"
|
||||
|
||||
COLORS = {
|
||||
"bg_primary": "#151515",
|
||||
"bg_secondary": "#1d1d1d",
|
||||
"bg_tertiary": "#212121",
|
||||
"bg_card": "#2b2b2b",
|
||||
"bg_hover": "#36312c",
|
||||
"bg_error": "#2f2926",
|
||||
"text_primary": "#f5f2ec",
|
||||
"text_highlight": "#f0a621",
|
||||
"text_body": "#c8c8c8",
|
||||
"text_secondary": "#ffffff",
|
||||
"text_muted": "#979797",
|
||||
"accent_green": "#3a8e35",
|
||||
"accent_red": "#ff4a46",
|
||||
"accent_yellow": "#f0a621",
|
||||
"accent_blue": "#d7b56a",
|
||||
"border_light": "#4d453b",
|
||||
"border_dark": "#0f0f0f",
|
||||
"stripe": "#1d1d1d",
|
||||
"stripe_soft": "#232323",
|
||||
}
|
||||
|
||||
|
||||
def _rgba(color, alpha=255):
|
||||
red, green, blue = ImageColor.getrgb(color)
|
||||
return red, green, blue, alpha
|
||||
|
||||
|
||||
def create_striped_texture(
|
||||
width,
|
||||
height,
|
||||
*,
|
||||
base_color=None,
|
||||
stripe_color=None,
|
||||
stripe_width=16,
|
||||
stripe_gap=18,
|
||||
stripe_alpha=110,
|
||||
corner_radius=0,
|
||||
border_color=None,
|
||||
border_width=0,
|
||||
border_alpha=255,
|
||||
):
|
||||
"""Create a dark diagonal striped texture matching the pack UI style."""
|
||||
width = max(1, int(width))
|
||||
height = max(1, int(height))
|
||||
|
||||
base = Image.new(
|
||||
"RGBA",
|
||||
(width, height),
|
||||
_rgba(base_color or COLORS["bg_card"]),
|
||||
)
|
||||
|
||||
stripes = Image.new("RGBA", (width, height), (0, 0, 0, 0))
|
||||
stripe_draw = ImageDraw.Draw(stripes)
|
||||
span = width + height
|
||||
step = max(1, stripe_width + stripe_gap)
|
||||
|
||||
for offset in range(-height, span + height, step):
|
||||
stripe_draw.line(
|
||||
(offset, 0, offset + height, height),
|
||||
fill=_rgba(stripe_color or COLORS["stripe"], stripe_alpha),
|
||||
width=max(1, stripe_width),
|
||||
)
|
||||
|
||||
image = Image.alpha_composite(base, stripes)
|
||||
|
||||
if border_color and border_width > 0:
|
||||
border_draw = ImageDraw.Draw(image)
|
||||
inset = max(1, border_width // 2)
|
||||
bounds = (inset, inset, width - inset - 1, height - inset - 1)
|
||||
if corner_radius > 0:
|
||||
border_draw.rounded_rectangle(
|
||||
bounds,
|
||||
radius=max(1, corner_radius - inset),
|
||||
outline=_rgba(border_color, border_alpha),
|
||||
width=border_width,
|
||||
)
|
||||
else:
|
||||
border_draw.rectangle(
|
||||
bounds,
|
||||
outline=_rgba(border_color, border_alpha),
|
||||
width=border_width,
|
||||
)
|
||||
|
||||
if corner_radius > 0:
|
||||
mask = Image.new("L", (width, height), 0)
|
||||
mask_draw = ImageDraw.Draw(mask)
|
||||
mask_draw.rounded_rectangle(
|
||||
(0, 0, width - 1, height - 1),
|
||||
radius=corner_radius,
|
||||
fill=255,
|
||||
)
|
||||
clipped = Image.new("RGBA", (width, height), (0, 0, 0, 0))
|
||||
clipped.paste(image, (0, 0), mask)
|
||||
image = clipped
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def create_striped_panel(
|
||||
width,
|
||||
height,
|
||||
*,
|
||||
panel_color=None,
|
||||
stripe_color=None,
|
||||
stripe_width=18,
|
||||
stripe_gap=18,
|
||||
stripe_alpha=110,
|
||||
halo_padding=22,
|
||||
halo_alpha=135,
|
||||
panel_alpha=240,
|
||||
corner_radius=18,
|
||||
border_color=None,
|
||||
border_width=1,
|
||||
border_alpha=255,
|
||||
):
|
||||
"""Create a mostly solid panel with stripes that extend just beyond its edges."""
|
||||
width = max(1, int(width))
|
||||
height = max(1, int(height))
|
||||
halo_padding = max(0, int(halo_padding))
|
||||
total_width = width + (halo_padding * 2)
|
||||
total_height = height + (halo_padding * 2)
|
||||
|
||||
image = Image.new("RGBA", (total_width, total_height), (0, 0, 0, 0))
|
||||
|
||||
stripes = Image.new("RGBA", (total_width, total_height), (0, 0, 0, 0))
|
||||
stripe_draw = ImageDraw.Draw(stripes)
|
||||
span = total_width + total_height
|
||||
step = max(1, stripe_width + stripe_gap)
|
||||
|
||||
for offset in range(-total_height, span + total_height, step):
|
||||
stripe_draw.line(
|
||||
(offset, 0, offset + total_height, total_height),
|
||||
fill=_rgba(stripe_color or COLORS["stripe"], halo_alpha),
|
||||
width=max(1, stripe_width),
|
||||
)
|
||||
|
||||
halo_mask = Image.new("L", (total_width, total_height), 0)
|
||||
halo_draw = ImageDraw.Draw(halo_mask)
|
||||
halo_draw.rounded_rectangle(
|
||||
(0, 0, total_width - 1, total_height - 1),
|
||||
radius=max(1, corner_radius + halo_padding),
|
||||
fill=255,
|
||||
)
|
||||
halo_image = Image.new("RGBA", (total_width, total_height), (0, 0, 0, 0))
|
||||
halo_image.paste(stripes, (0, 0), halo_mask)
|
||||
image = Image.alpha_composite(image, halo_image)
|
||||
|
||||
panel_left = halo_padding
|
||||
panel_top = halo_padding
|
||||
panel_right = panel_left + width - 1
|
||||
panel_bottom = panel_top + height - 1
|
||||
|
||||
panel = Image.new("RGBA", (total_width, total_height), (0, 0, 0, 0))
|
||||
panel_draw = ImageDraw.Draw(panel)
|
||||
panel_draw.rounded_rectangle(
|
||||
(panel_left, panel_top, panel_right, panel_bottom),
|
||||
radius=corner_radius,
|
||||
fill=_rgba(panel_color or COLORS["bg_card"], panel_alpha),
|
||||
)
|
||||
|
||||
if border_color and border_width > 0:
|
||||
inset = max(1, border_width // 2)
|
||||
panel_draw.rounded_rectangle(
|
||||
(
|
||||
panel_left + inset,
|
||||
panel_top + inset,
|
||||
panel_right - inset,
|
||||
panel_bottom - inset,
|
||||
),
|
||||
radius=max(1, corner_radius - inset),
|
||||
outline=_rgba(border_color, border_alpha),
|
||||
width=border_width,
|
||||
)
|
||||
|
||||
image = Image.alpha_composite(image, panel)
|
||||
return image
|
||||
|
||||
|
||||
def load_cover_background(image_path, width, height, *, overlay_color=None, overlay_alpha=125):
|
||||
"""Load an image, scale it to cover, and apply a dark overlay for readability."""
|
||||
width = max(1, int(width))
|
||||
height = max(1, int(height))
|
||||
|
||||
image = Image.open(image_path).convert("RGBA")
|
||||
source_ratio = image.width / image.height
|
||||
target_ratio = width / height
|
||||
|
||||
if source_ratio > target_ratio:
|
||||
new_height = height
|
||||
new_width = int(height * source_ratio)
|
||||
else:
|
||||
new_width = width
|
||||
new_height = int(width / source_ratio)
|
||||
|
||||
image = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
||||
left = (new_width - width) // 2
|
||||
top = (new_height - height) // 2
|
||||
image = image.crop((left, top, left + width, top + height))
|
||||
|
||||
overlay = Image.new(
|
||||
"RGBA",
|
||||
(width, height),
|
||||
_rgba(overlay_color or COLORS["bg_primary"], overlay_alpha),
|
||||
)
|
||||
return Image.alpha_composite(image, overlay)
|
||||
|
||||
|
||||
def style_text_button(
|
||||
button,
|
||||
foreground,
|
||||
background,
|
||||
*,
|
||||
hover_foreground=None,
|
||||
disabled_foreground=None,
|
||||
):
|
||||
"""Style a tkinter Button to look like a color-coded text action."""
|
||||
button.configure(
|
||||
bg=background,
|
||||
fg=foreground,
|
||||
activebackground=background,
|
||||
activeforeground=hover_foreground or COLORS["text_primary"],
|
||||
relief="flat",
|
||||
bd=0,
|
||||
borderwidth=0,
|
||||
highlightthickness=0,
|
||||
disabledforeground=disabled_foreground or COLORS["text_muted"],
|
||||
cursor="hand2",
|
||||
)
|
||||
|
||||
|
||||
def center_window(window, width, height, parent=None):
|
||||
"""Center a window either on its parent or the current screen."""
|
||||
width = int(width)
|
||||
height = int(height)
|
||||
window.update_idletasks()
|
||||
|
||||
if parent is not None and parent.winfo_exists():
|
||||
x = parent.winfo_rootx() + (parent.winfo_width() // 2) - (width // 2)
|
||||
y = parent.winfo_rooty() + (parent.winfo_height() // 2) - (height // 2)
|
||||
else:
|
||||
x = (window.winfo_screenwidth() // 2) - (width // 2)
|
||||
y = (window.winfo_screenheight() // 2) - (height // 2)
|
||||
|
||||
window.geometry(f"{width}x{height}+{max(0, x)}+{max(0, y)}")
|
||||
+148
-58
@@ -16,6 +16,7 @@ from datetime import datetime, timedelta
|
||||
import os
|
||||
from pathlib import Path
|
||||
from update_config import get_update_config
|
||||
from ui_theme import COLORS, FONT_FAMILY, style_text_button, center_window
|
||||
|
||||
class UpdateChecker:
|
||||
def __init__(self, current_version=None):
|
||||
@@ -171,6 +172,57 @@ class UpdateChecker:
|
||||
|
||||
return latest_release, None
|
||||
|
||||
def _show_notice_dialog(self, parent_window, title, message, accent_color):
|
||||
"""Show a small themed notice dialog."""
|
||||
dialog = tk.Toplevel(parent_window)
|
||||
dialog.title(title)
|
||||
dialog.configure(bg=COLORS['bg_primary'])
|
||||
dialog.resizable(False, False)
|
||||
dialog.attributes('-topmost', True)
|
||||
|
||||
panel = tk.Frame(
|
||||
dialog,
|
||||
bg=COLORS['bg_card'],
|
||||
highlightbackground=COLORS['border_light'],
|
||||
highlightcolor=COLORS['border_light'],
|
||||
highlightthickness=1,
|
||||
)
|
||||
panel.pack(fill='both', expand=True, padx=16, pady=16)
|
||||
|
||||
tk.Label(
|
||||
panel,
|
||||
text=title,
|
||||
fg=accent_color,
|
||||
bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 18, 'bold italic'),
|
||||
).pack(pady=(18, 10))
|
||||
|
||||
tk.Label(
|
||||
panel,
|
||||
text=message,
|
||||
fg=COLORS['text_body'],
|
||||
bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 11),
|
||||
justify='center',
|
||||
wraplength=340,
|
||||
).pack(padx=24, pady=(0, 18))
|
||||
|
||||
close_btn = tk.Button(
|
||||
panel,
|
||||
text="Close",
|
||||
command=dialog.destroy,
|
||||
bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 12, 'bold italic'),
|
||||
padx=0,
|
||||
pady=6,
|
||||
)
|
||||
style_text_button(close_btn, COLORS['accent_green'], COLORS['bg_card'])
|
||||
close_btn.pack(pady=(0, 18))
|
||||
|
||||
center_window(dialog, 420, 220, parent_window)
|
||||
dialog.protocol("WM_DELETE_WINDOW", dialog.destroy)
|
||||
return dialog
|
||||
|
||||
def show_update_dialog(self, parent_window, release_info):
|
||||
"""Show an update notification dialog"""
|
||||
if not release_info:
|
||||
@@ -184,7 +236,7 @@ class UpdateChecker:
|
||||
# Create update dialog
|
||||
dialog = tk.Toplevel(parent_window)
|
||||
dialog.title("Update Available - Progression Loader")
|
||||
dialog.configure(bg='#2b2b2b')
|
||||
dialog.configure(bg=COLORS['bg_primary'])
|
||||
dialog.resizable(False, False)
|
||||
dialog.attributes('-topmost', True)
|
||||
|
||||
@@ -195,58 +247,74 @@ class UpdateChecker:
|
||||
except:
|
||||
pass
|
||||
|
||||
# Calculate dialog size
|
||||
dialog_width = 500
|
||||
dialog_height = 400
|
||||
dialog_height = 430
|
||||
center_window(dialog, dialog_width, dialog_height, parent_window)
|
||||
|
||||
# Center the dialog
|
||||
x = parent_window.winfo_x() + (parent_window.winfo_width() // 2) - (dialog_width // 2)
|
||||
y = parent_window.winfo_y() + (parent_window.winfo_height() // 2) - (dialog_height // 2)
|
||||
dialog.geometry(f"{dialog_width}x{dialog_height}+{x}+{y}")
|
||||
panel = tk.Frame(
|
||||
dialog,
|
||||
bg=COLORS['bg_card'],
|
||||
highlightbackground=COLORS['border_light'],
|
||||
highlightcolor=COLORS['border_light'],
|
||||
highlightthickness=1,
|
||||
)
|
||||
panel.pack(fill='both', expand=True, padx=16, pady=16)
|
||||
|
||||
# Title
|
||||
title_label = tk.Label(dialog,
|
||||
text="🔄 Update Available!",
|
||||
fg='#00ff00', bg='#2b2b2b',
|
||||
font=('Arial', 16, 'bold'))
|
||||
title_label = tk.Label(
|
||||
panel,
|
||||
text="Update Available",
|
||||
fg=COLORS['text_highlight'],
|
||||
bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 18, 'bold italic'),
|
||||
)
|
||||
title_label.pack(pady=20)
|
||||
|
||||
# Version info
|
||||
version_frame = tk.Frame(dialog, bg='#2b2b2b')
|
||||
version_frame = tk.Frame(panel, bg=COLORS['bg_card'])
|
||||
version_frame.pack(pady=10)
|
||||
|
||||
current_label = tk.Label(version_frame,
|
||||
text=f"Current Version: {self.current_version}",
|
||||
fg='#cccccc', bg='#2b2b2b',
|
||||
font=('Arial', 12))
|
||||
fg=COLORS['text_body'], bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 12))
|
||||
current_label.pack()
|
||||
|
||||
latest_label = tk.Label(version_frame,
|
||||
text=f"Latest Version: {latest_version}",
|
||||
fg='#00ff00', bg='#2b2b2b',
|
||||
font=('Arial', 12, 'bold'))
|
||||
fg=COLORS['accent_green'], bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 12, 'bold'))
|
||||
latest_label.pack()
|
||||
|
||||
# Release notes
|
||||
if release_info.get('body'):
|
||||
notes_label = tk.Label(dialog,
|
||||
notes_label = tk.Label(panel,
|
||||
text="Release Notes:",
|
||||
fg='#cccccc', bg='#2b2b2b',
|
||||
font=('Arial', 12, 'bold'))
|
||||
fg=COLORS['text_primary'], bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 12, 'bold italic'))
|
||||
notes_label.pack(pady=(20, 5))
|
||||
|
||||
# Create scrollable text widget for release notes
|
||||
notes_frame = tk.Frame(dialog, bg='#2b2b2b')
|
||||
notes_frame = tk.Frame(panel, bg=COLORS['bg_card'])
|
||||
notes_frame.pack(fill='both', expand=True, padx=20, pady=(0, 20))
|
||||
|
||||
notes_text = tk.Text(notes_frame,
|
||||
height=8,
|
||||
bg='#404040', fg='#ffffff',
|
||||
font=('Arial', 10),
|
||||
bg=COLORS['bg_tertiary'], fg=COLORS['text_body'],
|
||||
font=(FONT_FAMILY, 10),
|
||||
wrap=tk.WORD,
|
||||
state='disabled')
|
||||
state='disabled',
|
||||
relief='flat',
|
||||
bd=0,
|
||||
insertbackground=COLORS['text_primary'],
|
||||
highlightthickness=1,
|
||||
highlightbackground=COLORS['border_light'])
|
||||
|
||||
scrollbar = tk.Scrollbar(notes_frame)
|
||||
scrollbar = tk.Scrollbar(
|
||||
notes_frame,
|
||||
bg=COLORS['bg_tertiary'],
|
||||
troughcolor=COLORS['bg_secondary'],
|
||||
activebackground=COLORS['bg_hover'],
|
||||
)
|
||||
scrollbar.pack(side='right', fill='y')
|
||||
|
||||
notes_text.pack(side='left', fill='both', expand=True)
|
||||
@@ -259,7 +327,7 @@ class UpdateChecker:
|
||||
notes_text.config(state='disabled')
|
||||
|
||||
# Buttons
|
||||
button_frame = tk.Frame(dialog, bg='#2b2b2b')
|
||||
button_frame = tk.Frame(panel, bg=COLORS['bg_card'])
|
||||
button_frame.pack(pady=20)
|
||||
|
||||
def download_update():
|
||||
@@ -302,9 +370,10 @@ class UpdateChecker:
|
||||
download_btn = tk.Button(button_frame,
|
||||
text="Download Update",
|
||||
command=download_update,
|
||||
bg='#00aa00', fg='white',
|
||||
font=('Arial', 12, 'bold'),
|
||||
padx=20, pady=5)
|
||||
bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 12, 'bold italic'),
|
||||
padx=0, pady=6)
|
||||
style_text_button(download_btn, COLORS['accent_green'], COLORS['bg_card'])
|
||||
download_btn.pack(side='left', padx=5)
|
||||
|
||||
# Only show remind/skip buttons if persistence is enabled
|
||||
@@ -312,25 +381,28 @@ class UpdateChecker:
|
||||
later_btn = tk.Button(button_frame,
|
||||
text="Remind Later",
|
||||
command=remind_later,
|
||||
bg='#0078d4', fg='white',
|
||||
font=('Arial', 12),
|
||||
padx=20, pady=5)
|
||||
bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 12, 'bold italic'),
|
||||
padx=0, pady=6)
|
||||
style_text_button(later_btn, COLORS['accent_yellow'], COLORS['bg_card'])
|
||||
later_btn.pack(side='left', padx=5)
|
||||
|
||||
skip_btn = tk.Button(button_frame,
|
||||
text="Skip Version",
|
||||
command=skip_version,
|
||||
bg='#666666', fg='white',
|
||||
font=('Arial', 12),
|
||||
padx=20, pady=5)
|
||||
bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 12, 'bold italic'),
|
||||
padx=0, pady=6)
|
||||
style_text_button(skip_btn, COLORS['accent_red'], COLORS['bg_card'])
|
||||
skip_btn.pack(side='left', padx=5)
|
||||
else:
|
||||
close_btn = tk.Button(button_frame,
|
||||
text="Close",
|
||||
command=dialog.destroy,
|
||||
bg='#666666', fg='white',
|
||||
font=('Arial', 12),
|
||||
padx=20, pady=5)
|
||||
bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 12, 'bold italic'),
|
||||
padx=0, pady=6)
|
||||
style_text_button(close_btn, COLORS['accent_red'], COLORS['bg_card'])
|
||||
close_btn.pack(side='left', padx=5)
|
||||
|
||||
# Handle window close
|
||||
@@ -355,15 +427,21 @@ class UpdateChecker:
|
||||
"""Manually check for updates and show result"""
|
||||
def check_complete(release_info, error):
|
||||
if error:
|
||||
messagebox.showerror("Update Check Failed",
|
||||
self._show_notice_dialog(
|
||||
parent_window,
|
||||
"Update Check Failed",
|
||||
f"Could not check for updates:\n{error}",
|
||||
parent=parent_window)
|
||||
COLORS['accent_red'],
|
||||
)
|
||||
return
|
||||
|
||||
if not release_info:
|
||||
messagebox.showinfo("No Updates",
|
||||
self._show_notice_dialog(
|
||||
parent_window,
|
||||
"No Updates",
|
||||
"Could not retrieve release information.",
|
||||
parent=parent_window)
|
||||
COLORS['accent_yellow'],
|
||||
)
|
||||
return
|
||||
|
||||
latest_version = release_info['version']
|
||||
@@ -371,33 +449,44 @@ class UpdateChecker:
|
||||
if self.is_newer_version(latest_version) and not self.should_skip_version(latest_version):
|
||||
self.show_update_dialog(parent_window, release_info)
|
||||
else:
|
||||
messagebox.showinfo("Up to Date",
|
||||
self._show_notice_dialog(
|
||||
parent_window,
|
||||
"Up To Date",
|
||||
f"You are running the latest version ({self.current_version}).",
|
||||
parent=parent_window)
|
||||
COLORS['accent_green'],
|
||||
)
|
||||
|
||||
# Show checking message
|
||||
checking_dialog = tk.Toplevel(parent_window)
|
||||
checking_dialog.title("Checking for Updates")
|
||||
checking_dialog.configure(bg='#2b2b2b')
|
||||
checking_dialog.configure(bg=COLORS['bg_primary'])
|
||||
checking_dialog.resizable(False, False)
|
||||
checking_dialog.attributes('-topmost', True)
|
||||
|
||||
# Center the dialog
|
||||
checking_dialog.geometry("300x100")
|
||||
x = parent_window.winfo_x() + (parent_window.winfo_width() // 2) - 150
|
||||
y = parent_window.winfo_y() + (parent_window.winfo_height() // 2) - 50
|
||||
checking_dialog.geometry(f"300x100+{x}+{y}")
|
||||
panel = tk.Frame(
|
||||
checking_dialog,
|
||||
bg=COLORS['bg_card'],
|
||||
highlightbackground=COLORS['border_light'],
|
||||
highlightcolor=COLORS['border_light'],
|
||||
highlightthickness=1,
|
||||
)
|
||||
panel.pack(fill='both', expand=True, padx=14, pady=14)
|
||||
|
||||
label = tk.Label(checking_dialog,
|
||||
label = tk.Label(
|
||||
panel,
|
||||
text="Checking for updates...",
|
||||
fg='white', bg='#2b2b2b',
|
||||
font=('Arial', 12))
|
||||
fg=COLORS['text_primary'],
|
||||
bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 12, 'bold italic'),
|
||||
)
|
||||
label.pack(expand=True)
|
||||
|
||||
center_window(checking_dialog, 320, 120, parent_window)
|
||||
|
||||
def check_and_close():
|
||||
release_info, error = self.check_for_updates_sync()
|
||||
checking_dialog.destroy()
|
||||
check_complete(release_info, error)
|
||||
parent_window.after(0, lambda: checking_dialog.destroy())
|
||||
parent_window.after(0, lambda: check_complete(release_info, error))
|
||||
|
||||
# Start check in background
|
||||
threading.Thread(target=check_and_close, daemon=True).start()
|
||||
@@ -452,9 +541,10 @@ def integrate_update_checker_with_gui(gui_class):
|
||||
update_btn = tk.Button(parent_frame,
|
||||
text="Check Updates",
|
||||
command=self.manual_update_check,
|
||||
bg='#404040', fg='white',
|
||||
font=('Arial', 10),
|
||||
padx=10, pady=2)
|
||||
bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 10, 'bold italic'),
|
||||
padx=8, pady=3)
|
||||
style_text_button(update_btn, COLORS['accent_yellow'], COLORS['bg_card'])
|
||||
update_btn.pack(side='right', padx=5, pady=5)
|
||||
except Exception as e:
|
||||
print(f"Could not add update button: {e}")
|
||||
|
||||
Reference in New Issue
Block a user