Svg colors #212

Merged
Boria138 merged 9 commits from Dervart/PortProtonQt:svg-colors into main 2026-07-03 16:50:00 +00:00
9 changed files with 191 additions and 36 deletions

No files matched your search

+11
View File
@@ -618,6 +618,17 @@ ICON_COLORS = {
}
```
Recoloring of button states (pressed/focused) is supported, as well as specifying wildcard filenames for batch processing:
```python
ICON_COLORS = {
"*_hover": color_text,
"*_pressed": color_text,
"*_focused": color_text,
"*_disabled": color_text,
}
```
Only icons listed in `ICON_COLORS` are recolored. Icons without an entry keep their original file unchanged. The source SVG is never modified; PortProtonQt writes a recolored copy to the icon cache and uses that path.
The recoloring helper handles common SVG paint declarations: `fill`, `stroke`, `color`, `stop-color`, `flood-color`, `lighting-color`, inline `style` attributes, and CSS inside `<style>` blocks. It preserves non-color paint values such as `none`, `transparent`, `url(#...)`, `context-fill`, and `context-stroke`.
+11
View File
@@ -618,6 +618,17 @@ ICON_COLORS = {
}
```
Поддерживается перекрашивание состояний нажатия/фокуса кнопок, а так же указание "wildcard" имён файлов для групповой обработки:
```python
ICON_COLORS = {
"*_hover": color_text,
"*_pressed": color_text,
"*_focused": color_text,
"*_disabled": color_text,
}
```
Перекрашиваются только иконки, перечисленные в `ICON_COLORS`. Если записи для иконки нет, она остаётся в исходном виде. Исходный SVG не изменяется: PortProtonQt создаёт перекрашенную копию в кеше и использует путь к ней.
Механизм перекраски учитывает распространённые SVG-объявления цвета: `fill`, `stroke`, `color`, `stop-color`, `flood-color`, `lighting-color`, inline-атрибуты `style` и CSS внутри блоков `<style>`. Значения, не являющиеся обычным цветом, сохраняются без изменений: `none`, `transparent`, `url(#...)`, `context-fill` и `context-stroke`.
+15 -2
View File
@@ -2,7 +2,7 @@ import os
import glob
import shutil
import tempfile
from PySide6.QtWidgets import QMessageBox, QDialog, QMenu, QLineEdit, QApplication, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QFrame
from PySide6.QtWidgets import QMessageBox, QDialog, QMenu, QLineEdit, QComboBox, QApplication, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QFrame
from PySide6.QtCore import QUrl, QPoint, QObject, Signal, Qt, QStandardPaths, QTimer
from PySide6.QtGui import QDesktopServices, QIcon, QKeySequence
from portprotonqt.localization import _
@@ -1349,6 +1349,19 @@ class CustomLineEdit(QLineEdit):
self.backspace()
self.setCursorPosition(cursor_pos)
class CustomComboBox(QComboBox):
def __init__(self, parent=None, theme=None):
super().__init__(parent)
self.theme = theme
self.setEditable(True) # если нужен редактируемый режим
def contextMenuEvent(self, event):
line_edit = self.lineEdit()
if line_edit is not None:
show_themed_line_edit_context_menu(line_edit, event.globalPos(), self.theme)
else:
super().contextMenuEvent(event)
def show_themed_line_edit_context_menu(line_edit: QLineEdit, global_pos: QPoint, theme=None) -> None:
"""Show a themed context menu for any line edit widget."""
@@ -1377,7 +1390,7 @@ def show_themed_line_edit_context_menu(line_edit: QLineEdit, global_pos: QPoint,
add_action(menu, _("Copy"), QKeySequence.StandardKey.Copy, "copy", line_edit.copy, line_edit.hasSelectedText())
add_action(menu, _("Paste"), QKeySequence.StandardKey.Paste, "paste", line_edit.paste,
QApplication.clipboard().mimeData().hasText())
add_action(menu, _("Delete"), QKeySequence.StandardKey.Delete, "delete", line_edit.backspace,
add_action(menu, _("Delete"), QKeySequence.StandardKey.Delete, "delete", lambda: line_edit.backspace(),
line_edit.hasSelectedText())
menu.addSeparator()
add_action(menu, _("Select All"), QKeySequence.StandardKey.SelectAll, "select_all", line_edit.selectAll,
+92 -12
View File
@@ -1,6 +1,7 @@
import os
from PySide6.QtWidgets import QLabel, QPushButton, QStyle, QStyleOptionButton, QWidget, QLayout, QLayoutItem, QScrollArea, QGraphicsOpacityEffect
from PySide6.QtCore import Qt, Signal, QRect, QRectF, QSize, Property, QPropertyAnimation, QEasingCurve, QTimer, QEvent
from PySide6.QtGui import QFont, QFontMetrics, QIcon, QPainter
from PySide6.QtGui import QFont, QFontMetrics, QIcon, QPainter, QPalette, QColor
from PySide6.QtSvg import QSvgRenderer
from portprotonqt.theme_manager import ThemeManager
from portprotonqt.config import ui_config
@@ -539,14 +540,62 @@ class AutoSizeButton(QPushButton):
self._original_font = self.font()
self._original_text = self.text()
self._icon_name = self._extract_icon_name(icon)
if self._icon:
self.setIcon(self._icon)
self.setMouseTracking(True)
self.setCursor(Qt.CursorShape.PointingHandCursor)
self.setFlat(True)
self.setMinimumWidth(50)
self.adjustFontSize()
def _extract_icon_name(self, icon_path):
if isinstance(icon_path, str):
base = os.path.basename(icon_path)
name, _ = os.path.splitext(base)
return name
return None
def _get_icon_color(self):
if not self._icon_name:
return None
state = None
if not self.isEnabled():
state = "disabled"
elif self.isDown():
state = "pressed"
elif self.hasFocus():
state = "focused"
elif self.underMouse():
state = "hover"
colors_dict = getattr(self.theme, 'ICON_COLORS', {})
keys = []
if state:
keys.append(f"{self._icon_name}_{state}")
keys.append(f"*_{state}")
keys.append(self._icon_name)
for key in keys:
color = colors_dict.get(key)
if color is not None:
return color
if not self.isEnabled():
return getattr(self.theme, 'color_disabled', None)
if self.isDown():
return getattr(self.theme, 'color_accent_dark', None) or getattr(self.theme, 'color_accent', None)
if self.underMouse() or self.hasFocus():
return getattr(self.theme, 'color_accent', None)
return None
def setAlignment(self, alignment):
self._alignment = alignment
self.update()
@@ -559,6 +608,7 @@ class AutoSizeButton(QPushButton):
def setIcon(self, icon: object) -> None:
self._icon = icon
self._icon_name = self._extract_icon_name(icon)
if _is_svg_icon(icon):
super().setIcon(QIcon())
elif isinstance(icon, str):
@@ -592,26 +642,40 @@ class AutoSizeButton(QPushButton):
option,
self,
)
icon_color = self._get_icon_color()
if icon_color and self._icon_name:
colored_path = self.theme_manager.get_colored_icon_path(
self._icon_name,
icon_color,
self.current_theme_name
)
if colored_path and os.path.exists(colored_path):
icon_path = colored_path
fm = QFontMetrics(self.font())
text = self.text()
text_width = fm.horizontalAdvance(text)
text_width = fm.horizontalAdvance(text) if text else 0
icon_spacing = self._icon_size // 2 if text else 0
total_width = self._icon_size + icon_spacing + text_width
x = rect.left() + (rect.width() - total_width) // 2
icon_y = rect.top() + (rect.height() - self._icon_size) // 2
icon_rect = QRect(x, icon_y, self._icon_size, self._icon_size)
QSvgRenderer(icon_path).render(painter, QRectF(icon_rect))
renderer = QSvgRenderer(icon_path)
if renderer.isValid():
renderer.render(painter, QRectF(icon_rect))
text_rect = QRect(x + self._icon_size + icon_spacing, rect.top(), text_width, rect.height())
self.style().drawItemText(
painter,
text_rect,
self._alignment,
self.palette(),
self.isEnabled(),
text,
self.foregroundRole(),
)
if icon_color:
painter.setPen(QColor(icon_color))
else:
painter.setPen(self.palette().color(QPalette.ColorRole.ButtonText))
painter.drawText(text_rect, self._alignment, text)
painter.end()
def setText(self, text):
self._original_text = text
@@ -680,6 +744,22 @@ class AutoSizeButton(QPushButton):
height = fm.height() + margins.top() + margins.bottom() + self._pad_top + self._pad_bottom
return QSize(width, height)
def enterEvent(self, event):
super().enterEvent(event)
self.update()
def leaveEvent(self, event):
super().leaveEvent(event)
self.update()
def focusInEvent(self, event):
super().focusInEvent(event)
self.update()
def focusOutEvent(self, event):
super().focusOutEvent(event)
self.update()
class NavLabel(QLabel):
clicked = Signal()
+2 -1
View File
@@ -27,6 +27,7 @@ from portprotonqt.logger import get_logger
from portprotonqt.portproton_api import get_user_conf_setting, set_user_conf_setting
from portprotonqt.scripts_utils.prefix_backup import is_legacy_squashfs_backup
from portprotonqt.settings_manager import get_available_prefix_options, get_available_wine_options
from portprotonqt.context_menu_manager import CustomComboBox
logger = get_logger(__name__)
@@ -90,7 +91,7 @@ class MainWindowWineTabMixin(_MainWindowTypingBase):
formLayout.addRow(self.wineTitleLabel, self.wineCombo)
self.prefixes = get_available_prefix_options(self.portproton_location)
self.prefixCombo = QComboBox()
self.prefixCombo = CustomComboBox(self, theme=self.theme)
self.prefixCombo.view().window().setWindowFlags(
Qt.WindowType.Popup | Qt.WindowType.FramelessWindowHint
)
+14 -4
View File
@@ -29,7 +29,7 @@ exeSettingsGroupBoxElementVerticalSpacing = 2
exeSettingsGroupBoxElementHorizontalSpacing = 5
# === Core Palette (light) ===
color_accent = "#70b8ff"
color_accent = "#409EFF"
color_bg = "#F8F9FC"
color_surface = "#F0F2F5"
color_surface_elevated = "#E9ECEF"
@@ -223,7 +223,7 @@ LIBRARY_CONTROLS_BUTTON_STYLE = f"""
min-height: 25px;
}}
QPushButton:hover, QPushButton:pressed, QPushButton:focus, QPushButton:checked {{
background: qradialgradient(cx:0.5, cy:0.5, radius:0.8, stop:0 {color_surface_elevated}, stop:1 {color_surface_light});
background: {color_accent};
border: {border_thin} {color_accent};
}}
"""
@@ -301,10 +301,12 @@ COMBOBOX_STYLE = f"""
QComboBox:hover {{
border: {border_medium} {color_accent};
background: {color_accent};
color: {color_text};
}}
QComboBox:focus {{
border: {border_medium} {color_accent};
background-color: {color_accent};
color: {color_text};
}}
QComboBox:disabled {{
background: {color_surface_hover};
@@ -343,6 +345,7 @@ COMBOBOX_STYLE = f"""
}}
QComboBox:editable {{
background: {color_surface};
color: {color_text_dark};
}}
QComboBox::drop-down:editable:focus {{
background: {color_accent};
@@ -369,7 +372,7 @@ COMBOBOX_STYLE = f"""
}}
QListView::item:focus {{
background: {color_accent};
color: {color_text_dark};
color: {color_text};
}}
""" % (
theme_manager.get_icon("down", current_theme_name, as_path=True),
@@ -381,6 +384,7 @@ SETTINGS_TABLE_COMBOBOX_STYLE = f"""
QComboBox#settingsTableCombo:focus {{
background: {color_surface};
border: {border_medium} {color_accent};
color: {color_text_dark};
}}
"""
@@ -444,9 +448,11 @@ TAB_STYLE = f"""
QTabBar::tab:selected {{
background: {color_accent};
color: {color_text_dark};
color: {color_text};
}}
QTabBar::tab:hover {{
background: {color_accent};
color: {color_text};
}}
"""
@@ -472,11 +478,13 @@ ACTION_BUTTON_STYLE = f"""
border: {border_medium} {color_accent};
}}
QPushButton:pressed {{
background: {color_bg};
background: {color_accent};
color: {color_text};
}}
QPushButton:focus {{
border: {border_medium} {color_accent};
background-color: {color_accent};
color: {color_text};
}}
"""
@@ -494,6 +502,7 @@ ACTION_BUTTON_ACTIVE_STYLE = f"""
QPushButton:hover {{
background: {color_accent};
border: {border_medium} {color_accent};
color: {color_text};
}}
QPushButton:pressed {{
background: {color_bg};
@@ -501,6 +510,7 @@ ACTION_BUTTON_ACTIVE_STYLE = f"""
QPushButton:focus {{
border: {border_medium} {color_accent};
background-color: {color_accent};
color: {color_text};
}}
"""
+1 -1
View File
@@ -171,7 +171,7 @@ LIBRARY_CONTROLS_BUTTON_STYLE = f"""
min-height: 25px;
}}
QPushButton:hover, QPushButton:pressed, QPushButton:focus, QPushButton:checked {{
background: qradialgradient(cx:0.5, cy:0.5, radius:0.8, stop:0 {color_surface_hover}, stop:1 {color_surface_elevated});
background: {color_accent};
border: {border_thin} {color_accent};
}}
"""
+37 -16
View File
@@ -42,7 +42,7 @@ border_radius_small = "10px"
border_radius_large = "15px"
border_radius_card = "20px"
border_radius_badge = "5px"
color_accent = "#70b8ff"
color_accent = "#409EFF"
color_bg = "#F8F9FC"
color_bg_darker = "#bfc0c7"
color_surface = "#F0F2F5"
@@ -55,6 +55,14 @@ color_surface_light = "#d2d3db"
color_surface_mid = "#9394a5"
color_text_dark = "#212529"
# === Button Icons Color ===
ICON_COLORS = {
"*_hover": color_text,
"*_pressed": color_text,
"*_focused": color_text,
"*_disabled": color_text,
}
# Widget State
color_combo_disabled_bg = "#dee2e6"
color_combo_disabled_border = "#dee2e6"
@@ -421,11 +429,13 @@ MESSAGE_BOX_STYLE = f"""
}}
QMessageBox QPushButton:hover {{
background: {color_accent};
color: {color_text};
border-color: border: {border_thin} {color_accent};
}}
QMessageBox QPushButton:focus {{
border: {border_medium} {color_accent};
background: {color_accent};
color: {color_text};
}}
"""
@@ -1015,11 +1025,7 @@ LIBRARY_CONTROLS_BUTTON_STYLE = f"""
QPushButton:pressed,
QPushButton:focus,
QPushButton:checked {{
background: qradialgradient(
cx:0.5, cy:0.5, radius:0.8,
stop:0 {color_surface_elevated},
stop:1 {color_surface_light}
);
background: {color_accent};
border: {border_thin} {color_accent};
}}
"""
@@ -1146,7 +1152,7 @@ QTableWidget::item:selected,
QTableWidget::item:focus,
QTableWidget::item:selected:focus {{
background: {color_accent};
color: {color_text_dark};
color: {color_text};
selection-background-color: {color_accent};
}}
QTableWidget::item:hover {{
@@ -1154,7 +1160,7 @@ QTableWidget::item:hover {{
}}
QTableWidget::item:selected:hover {{
background: {color_accent};
color: {color_text_dark};
color: {color_text};
}}
"""
@@ -1237,11 +1243,13 @@ COMBOBOX_STYLE = f"""
QComboBox:hover {{
border: {border_medium} {color_accent};
background: {color_accent};
color: {color_text};
}}
/* Focus state */
QComboBox:focus {{
border: {border_medium} {color_accent};
background-color: {color_accent};
color: {color_text};
}}
QComboBox:disabled {{
background: {color_combo_disabled_bg};
@@ -1281,6 +1289,7 @@ COMBOBOX_STYLE = f"""
}}
QComboBox:editable {{
background: {color_surface};
color: {color_text_dark};
}}
QComboBox::drop-down:editable:focus {{
background: {color_accent};
@@ -1308,7 +1317,7 @@ COMBOBOX_STYLE = f"""
/* Selection in list when item is focused */
QListView::item:focus {{
background: {color_accent};
color: {color_text_dark};
color: {color_text};
}}
"""
@@ -1317,6 +1326,7 @@ SETTINGS_TABLE_COMBOBOX_STYLE = f"""
QComboBox#settingsTableCombo:focus {{
background: {color_surface};
border: {border_medium} {color_accent};
color: {color_text_dark};
}}
"""
@@ -1450,10 +1460,11 @@ TAB_STYLE = f"""
}}
QTabBar::tab:selected {{
background: {color_accent};
color: {color_text_dark};
color: {color_text};
}}
QTabBar::tab:hover {{
background: {color_accent};
color: {color_text};
}}
"""
@@ -1502,13 +1513,16 @@ ACTION_BUTTON_STYLE = f"""
QPushButton:hover {{
background: {color_accent};
border: {border_medium} {color_accent};
color: {color_text};
}}
QPushButton:pressed {{
background: {color_bg};
background: {color_accent};
color: {color_text};
}}
QPushButton:focus {{
border: {border_medium} {color_accent};
background-color: {color_accent};
color: {color_text};
}}
"""
@@ -1526,6 +1540,7 @@ ACTION_BUTTON_ACTIVE_STYLE = f"""
QPushButton:hover {{
background: {color_accent};
border: {border_medium} {color_accent};
color: {color_text};
}}
QPushButton:pressed {{
background: {color_bg};
@@ -1533,6 +1548,7 @@ ACTION_BUTTON_ACTIVE_STYLE = f"""
QPushButton:focus {{
border: {border_medium} {color_accent};
background-color: {color_accent};
color: {color_text};
}}
"""
@@ -1563,6 +1579,7 @@ QTableWidget::item:selected,
QTableWidget::item:selected:!active,
QTableWidget::item:hover {{
background: {color_accent};
color: {color_text};
}}
/* LogArea */
QFrame {{
@@ -1658,18 +1675,18 @@ CONTEXT_MENU_STYLE = f"""
}}
QMenu::item:selected {{
background: {color_accent};
color: {color_text_dark};
color: {color_text};
}}
QMenu::item:disabled {{
color: {color_nav_inactive};
}}
QMenu::item:hover {{
background: {color_accent};
color: {color_text_dark};
color: {color_text};
}}
QMenu::item:focus {{
background: {color_accent};
color: {color_text_dark};
color: {color_text};
border: {border_thin} {color_border_light};
border-radius: {border_radius_small};
}}
@@ -1697,26 +1714,30 @@ QPushButton {{
QPushButton:hover {{
background-color: {color_accent};
border: {border_thin} {color_accent};
color: {color_text};
}}
QPushButton:focus {{
border: {border_thin} {color_accent};
background-color: {color_accent};
color: {color_text};
}}
QPushButton[vk_selected="true"] {{
border: {border_thin} {color_accent};
background-color: {color_accent};
color: {color_text};
}}
QPushButton:pressed {{
background-color: {color_surface_hover};
border: {border_none} {color_transparent};
color: {color_text};
}}
QPushButton[checked="true"] {{
background-color: {color_accent};
color: {color_text_dark};
color: {color_text};
border: {border_none} {color_transparent};
}}
QPushButton[checked="true"]:focus {{
border: {border_thin} {color_text_dark};
border: {border_thin} {color_text};
}}
"""
@@ -78,6 +78,14 @@ color_gamepad_supported = "#00ff00"
color_white = "#ffffff"
missing_exe_cover_opacity = 0.45
# === Button Icons Color ===
ICON_COLORS = {
"*_hover": color_text,
"*_pressed": color_text,
"*_focused": color_text,
"*_disabled": color_text,
}
# === Widget State Colors ===
color_combo_disabled_bg = "#2a2c35"
color_combo_disabled_border = "#2a2c35"