mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-06-17 11:47:44 +02:00
Sync from development - prepare for v0.2.0
This commit is contained in:
@@ -2,6 +2,9 @@
|
||||
GUI Utilities for Jackify Frontend
|
||||
"""
|
||||
import re
|
||||
from typing import Tuple, Optional
|
||||
from PySide6.QtWidgets import QApplication, QWidget
|
||||
from PySide6.QtCore import QSize, QPoint
|
||||
|
||||
ANSI_COLOR_MAP = {
|
||||
'30': 'black', '31': 'red', '32': 'green', '33': 'yellow', '34': 'blue', '35': 'magenta', '36': 'cyan', '37': 'white',
|
||||
@@ -50,4 +53,272 @@ def ansi_to_html(text):
|
||||
else:
|
||||
result += chunk
|
||||
result = result.replace('\n', '<br>')
|
||||
return result
|
||||
return result
|
||||
|
||||
|
||||
def get_screen_geometry(widget: Optional[QWidget] = None) -> Tuple[int, int, int, int]:
|
||||
"""
|
||||
Get available screen geometry for a widget.
|
||||
|
||||
Args:
|
||||
widget: Widget to get screen for (uses primary screen if None)
|
||||
|
||||
Returns:
|
||||
Tuple of (x, y, width, height) for available screen geometry
|
||||
"""
|
||||
app = QApplication.instance()
|
||||
if not app:
|
||||
return (0, 0, 1920, 1080) # Fallback
|
||||
|
||||
if widget:
|
||||
screen = None
|
||||
window_handle = widget.windowHandle()
|
||||
if window_handle and window_handle.screen():
|
||||
screen = window_handle.screen()
|
||||
else:
|
||||
try:
|
||||
global_pos = widget.mapToGlobal(widget.rect().center())
|
||||
except Exception:
|
||||
global_pos = QPoint(0, 0)
|
||||
if app:
|
||||
screen = app.screenAt(global_pos)
|
||||
if not screen and app:
|
||||
screen = app.primaryScreen()
|
||||
else:
|
||||
screen = app.primaryScreen()
|
||||
|
||||
if screen:
|
||||
geometry = screen.availableGeometry()
|
||||
return (geometry.x(), geometry.y(), geometry.width(), geometry.height())
|
||||
|
||||
return (0, 0, 1920, 1080) # Fallback
|
||||
|
||||
|
||||
def calculate_window_size(
|
||||
widget: Optional[QWidget] = None,
|
||||
width_ratio: float = 0.7,
|
||||
height_ratio: float = 0.6,
|
||||
min_width: int = 900,
|
||||
min_height: int = 500,
|
||||
max_width: Optional[int] = None,
|
||||
max_height: Optional[int] = None
|
||||
) -> Tuple[int, int]:
|
||||
"""
|
||||
Calculate appropriate window size based on screen geometry.
|
||||
|
||||
Args:
|
||||
widget: Widget to calculate size for (uses primary screen if None)
|
||||
width_ratio: Fraction of screen width to use (0.0-1.0)
|
||||
height_ratio: Fraction of screen height to use (0.0-1.0)
|
||||
min_width: Minimum window width
|
||||
min_height: Minimum window height
|
||||
max_width: Maximum window width (None = no limit)
|
||||
max_height: Maximum window height (None = no limit)
|
||||
|
||||
Returns:
|
||||
Tuple of (width, height)
|
||||
"""
|
||||
_, _, screen_width, screen_height = get_screen_geometry(widget)
|
||||
|
||||
# Calculate size based on ratios
|
||||
width = int(screen_width * width_ratio)
|
||||
height = int(screen_height * height_ratio)
|
||||
|
||||
# Apply minimums
|
||||
width = max(width, min_width)
|
||||
height = max(height, min_height)
|
||||
|
||||
# Apply maximums
|
||||
if max_width:
|
||||
width = min(width, max_width)
|
||||
if max_height:
|
||||
height = min(height, max_height)
|
||||
|
||||
# Ensure we don't exceed screen bounds
|
||||
width = min(width, screen_width)
|
||||
height = min(height, screen_height)
|
||||
|
||||
return (width, height)
|
||||
|
||||
|
||||
def calculate_window_position(
|
||||
widget: QWidget,
|
||||
window_width: int,
|
||||
window_height: int,
|
||||
parent: Optional[QWidget] = None
|
||||
) -> QPoint:
|
||||
"""
|
||||
Calculate appropriate window position (centered on parent or screen).
|
||||
|
||||
Args:
|
||||
widget: Widget to position
|
||||
window_width: Width of window to position
|
||||
window_height: Height of window to position
|
||||
parent: Parent widget to center on (centers on screen if None)
|
||||
|
||||
Returns:
|
||||
QPoint with x, y coordinates
|
||||
"""
|
||||
_, _, screen_width, screen_height = get_screen_geometry(widget)
|
||||
|
||||
if parent:
|
||||
parent_geometry = parent.geometry()
|
||||
x = parent_geometry.x() + (parent_geometry.width() - window_width) // 2
|
||||
y = parent_geometry.y() + (parent_geometry.height() - window_height) // 2
|
||||
else:
|
||||
# Center on screen
|
||||
x = (screen_width - window_width) // 2
|
||||
y = (screen_height - window_height) // 2
|
||||
|
||||
# Ensure window stays on screen
|
||||
x = max(0, min(x, screen_width - window_width))
|
||||
y = max(0, min(y, screen_height - window_height))
|
||||
|
||||
return QPoint(x, y)
|
||||
|
||||
|
||||
def set_responsive_minimum(window: Optional[QWidget], min_width: int = 960,
|
||||
min_height: int = 520, margin: int = 32):
|
||||
"""
|
||||
Apply minimum size constraints that respect the current screen bounds.
|
||||
|
||||
Args:
|
||||
window: Target window
|
||||
min_width: Desired minimum width
|
||||
min_height: Desired minimum height
|
||||
margin: Pixels to subtract from available size to avoid full-screen overlap
|
||||
"""
|
||||
if window is None:
|
||||
return
|
||||
|
||||
_, _, screen_width, screen_height = get_screen_geometry(window)
|
||||
|
||||
width_cap = min_width
|
||||
height_cap = min_height
|
||||
|
||||
if screen_width:
|
||||
available_width = max(640, screen_width - margin)
|
||||
available_width = min(available_width, screen_width)
|
||||
width_cap = min(min_width, available_width)
|
||||
if screen_height:
|
||||
available_height = max(520, screen_height - margin)
|
||||
available_height = min(available_height, screen_height)
|
||||
height_cap = min(min_height, available_height)
|
||||
|
||||
window.setMinimumSize(QSize(width_cap, height_cap))
|
||||
|
||||
def load_saved_window_size(window: QWidget) -> Optional[Tuple[int, int]]:
|
||||
"""
|
||||
Load saved window size from config if available.
|
||||
Only returns sizes that are reasonable (compact menu size, not expanded).
|
||||
|
||||
Args:
|
||||
window: Window widget (used to validate size against screen)
|
||||
|
||||
Returns:
|
||||
Tuple of (width, height) if saved size exists and is valid, None otherwise
|
||||
"""
|
||||
try:
|
||||
from ...backend.handlers.config_handler import ConfigHandler
|
||||
config_handler = ConfigHandler()
|
||||
|
||||
saved_width = config_handler.get('window_width')
|
||||
saved_height = config_handler.get('window_height')
|
||||
|
||||
if saved_width and saved_height:
|
||||
# Validate saved size is reasonable (not too small, fits on screen)
|
||||
_, _, screen_width, screen_height = get_screen_geometry(window)
|
||||
min_width = 1200
|
||||
min_height = 500
|
||||
max_height = int(screen_height * 0.6) # Reject sizes larger than 60% of screen (expanded state)
|
||||
|
||||
# Ensure saved size is within reasonable bounds (compact menu size)
|
||||
# Reject expanded sizes that are too tall
|
||||
if (min_width <= saved_width <= screen_width and
|
||||
min_height <= saved_height <= max_height):
|
||||
return (saved_width, saved_height)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def save_window_size(window: QWidget):
|
||||
"""
|
||||
Save current window size to config.
|
||||
|
||||
Args:
|
||||
window: Window widget to save size for
|
||||
"""
|
||||
try:
|
||||
from ...backend.handlers.config_handler import ConfigHandler
|
||||
config_handler = ConfigHandler()
|
||||
|
||||
size = window.size()
|
||||
config_handler.set('window_width', size.width())
|
||||
config_handler.set('window_height', size.height())
|
||||
config_handler.save_config()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def apply_window_size_and_position(
|
||||
window: QWidget,
|
||||
width_ratio: float = 0.7,
|
||||
height_ratio: float = 0.6,
|
||||
min_width: int = 900,
|
||||
min_height: int = 500,
|
||||
max_width: Optional[int] = None,
|
||||
max_height: Optional[int] = None,
|
||||
parent: Optional[QWidget] = None,
|
||||
preserve_position: bool = False,
|
||||
use_saved_size: bool = True
|
||||
):
|
||||
"""
|
||||
Apply dynamic window sizing and positioning based on screen geometry.
|
||||
Optionally uses saved window size if user has manually resized before.
|
||||
|
||||
Args:
|
||||
window: Window widget to size/position
|
||||
width_ratio: Fraction of screen width to use (if no saved size)
|
||||
height_ratio: Fraction of screen height to use (if no saved size)
|
||||
min_width: Minimum window width
|
||||
min_height: Minimum window height
|
||||
max_width: Maximum window width (None = no limit)
|
||||
max_height: Maximum window height (None = no limit)
|
||||
parent: Parent widget to center on (centers on screen if None)
|
||||
preserve_position: If True, preserve current size and position (only set minimums)
|
||||
use_saved_size: If True, check for saved window size first
|
||||
"""
|
||||
# Set minimum size first
|
||||
window.setMinimumSize(QSize(min_width, min_height))
|
||||
|
||||
# If preserve_position is True, don't resize - just ensure minimums are set
|
||||
if preserve_position:
|
||||
# Only ensure current size meets minimums, don't change size
|
||||
current_size = window.size()
|
||||
if current_size.width() < min_width:
|
||||
window.resize(min_width, current_size.height())
|
||||
if current_size.height() < min_height:
|
||||
window.resize(window.size().width(), min_height)
|
||||
return
|
||||
|
||||
# Check for saved window size first
|
||||
width = None
|
||||
height = None
|
||||
|
||||
if use_saved_size:
|
||||
saved_size = load_saved_window_size(window)
|
||||
if saved_size:
|
||||
width, height = saved_size
|
||||
|
||||
# If no saved size, calculate dynamically
|
||||
if width is None or height is None:
|
||||
width, height = calculate_window_size(
|
||||
window, width_ratio, height_ratio, min_width, min_height, max_width, max_height
|
||||
)
|
||||
|
||||
# Calculate and set position
|
||||
pos = calculate_window_position(window, width, height, parent)
|
||||
window.resize(width, height)
|
||||
window.move(pos)
|
||||
|
||||
Reference in New Issue
Block a user