From 12506dd1df0a2970dae04215d799000e34199eb9 Mon Sep 17 00:00:00 2001 From: Vxrtrauter <101264710+Vxrtrauter@users.noreply.github.com> Date: Sun, 5 Apr 2026 06:39:29 +0200 Subject: [PATCH] feat: Add customizable accent color setting Introduce an accent color preference across the app: add a settings UI input (with restart note), persist it to AppState and config (read/write), and accept it in save_settings. Integrate the value into theming: compute a semi-transparent selection color, apply it to table selection styles, and pass it to qdarktheme as the primary color (including when window transparency is enabled). Also apply the accent to the tracker combo selection styling as a UI polish. --- src/interface/dialogs/settings.py | 10 ++++++++-- src/interface/dialogs/theme.py | 21 ++++++++++++++++++++- src/interface/gui.py | 5 +++++ src/main.py | 8 ++++++-- src/utils/config/config.py | 4 +++- src/utils/config/settings.py | 4 +++- src/utils/data/state.py | 1 + 7 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/interface/dialogs/settings.py b/src/interface/dialogs/settings.py index e71d1a4..2fb6652 100644 --- a/src/interface/dialogs/settings.py +++ b/src/interface/dialogs/settings.py @@ -88,6 +88,11 @@ def settings_dialog(self): transparent_window_container, transparent_window_checkbox = create_widget(QCheckBox, "Window Transparency (requires restart) (Linux/MacOS only): ") transparent_window_checkbox.setChecked(state.window_transparency) transparent_window_checkbox.toggled.connect(lambda checked: setattr(state, 'window_transparency', checked)) + + # Accent Color + accent_color_container, accent_color_input = create_widget(QLineEdit, "Accent Color (requires restart): ", width=180, height=30) + accent_color_input.setPlaceholderText("e.g. #fca7d7") + accent_color_input.setText(state.accent_color) # API URL Widget api_url_container, api_url = create_widget(QLineEdit, "API Server URL: ", width=180, height=30) @@ -289,7 +294,8 @@ def settings_dialog(self): image_offset.value(), image_opacity.value(), image_as_wallpaper=image_mode_checkbox.isChecked(), - image_position=image_position_combo.currentText() + image_position=image_position_combo.currentText(), + accent_color=accent_color_input.text().strip() ) cancel_btn.clicked.connect(dialog.reject) @@ -297,7 +303,7 @@ def settings_dialog(self): layout.addWidget(save_btn) tabs = QtWidgets.QTabWidget() - create_tab("General", [autoresume_container, update_checkbox_container, transparent_window_container], tabs=tabs, stretch=True) + create_tab("General", [autoresume_container, update_checkbox_container, transparent_window_container, accent_color_container], tabs=tabs, stretch=True) create_tab("Image", [enable_image_container, image_mode_container, image_position_container, image_width_container, image_offset_container, image_opacity_container], tabs=tabs, stretch=True) create_tab("Paths", [download_path_container, image_path_container], tabs=tabs, stretch=True) create_tab("Network", [interface_container, max_connections_container, max_downloads_container, up_speed_limit_container, down_speed_limit_container, api_url_container], tabs=tabs, stretch=True) diff --git a/src/interface/dialogs/theme.py b/src/interface/dialogs/theme.py index 18011f8..2b23212 100644 --- a/src/interface/dialogs/theme.py +++ b/src/interface/dialogs/theme.py @@ -1,4 +1,6 @@ from PySide6.QtGui import QColor +from utils.logging.logs import consoleLog +from utils.data.state import state import darkdetect def _is_dark_mode(): @@ -27,15 +29,32 @@ def _theme_colors(): } +def _accent_selection_color(alpha: float = 0.35) -> str: + if not state.accent_color: + return None + try: + hex_color = state.accent_color.lstrip('#') + if len(hex_color) != 6: + return None + r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16) + return f"rgba({r}, {g}, {b}, {alpha})" + except ValueError: + consoleLog(f"Invalid Color: {state.accent_color}") + return None + + def _table_stylesheet(view_type="QTableWidget"): c = _theme_colors() dark = _is_dark_mode() color_rule = "" if dark else f"color: {c['text']};" + selected_bg = _accent_selection_color() or c["selected"] + selection_color_rule = f"selection-background-color: {selected_bg};" if state.accent_color else "" return f""" {view_type} {{ border: none; outline: 0; font-size: 13px; + {selection_color_rule} {color_rule} }} {view_type}::item {{ @@ -45,7 +64,7 @@ def _table_stylesheet(view_type="QTableWidget"): {color_rule} }} {view_type}::item:selected {{ - background: {c["selected"]}; + background: {selected_bg}; outline: none; border: none; border-bottom: 1px solid {c["border"]}; diff --git a/src/interface/gui.py b/src/interface/gui.py index 1b048ef..cd1765a 100644 --- a/src/interface/gui.py +++ b/src/interface/gui.py @@ -6,6 +6,7 @@ from interface.assets.base64_icons import settings_white_base64 from interface.assets.base64_icons import settings_black_base64 from interface.dialogs.downloadlist import download_list_update from interface.dialogs.update import get_version, UpdateDialog +from interface.dialogs.theme import _accent_selection_color from utils.logging.logs import consoleLog, flush_log_buffer from interface.dialogs.downloadmodel import DownloadModel from interface.dialogs.settings import settings_dialog @@ -177,6 +178,10 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): self.tracker_list.addItems(list(state.trackers.keys())) self.tracker_list.setCursor(Qt.CursorShape.PointingHandCursor) self.tracker_list.activated.connect(self.set_tracker) + if state.accent_color: + self.tracker_list.setStyleSheet( + f"QComboBox QAbstractItemView::item:selected {{ background: {_accent_selection_color()}; }}" + ) self.corner_layout.addWidget(self.tracker_list) # Settings button diff --git a/src/main.py b/src/main.py index 4b9ed24..5722bca 100644 --- a/src/main.py +++ b/src/main.py @@ -21,13 +21,17 @@ import time import sys def run_gui(app): - qdarktheme.setup_theme("auto") + custom_colors = {} + if state.accent_color: + custom_colors["primary"] = state.accent_color + qdarktheme.setup_theme("auto", custom_colors=custom_colors if custom_colors else None) widget = MainWindow() # Check OS for window transparency compatibility and apply if state.window_transparency and platform.system() != "Windows": widget.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) - qdarktheme.setup_theme("auto", custom_colors={"background": "#00000000"}) + transparent_colors = {"background": "#00000000", **custom_colors} + qdarktheme.setup_theme("auto", custom_colors=transparent_colors) set_main_window(widget) widget.show() diff --git a/src/utils/config/config.py b/src/utils/config/config.py index 15cf7a4..fc481fe 100644 --- a/src/utils/config/config.py +++ b/src/utils/config/config.py @@ -14,7 +14,8 @@ def create_config(): "debug": str(state.debug), "ignore_updates": str(state.ignore_updates), "autoresume": str(state.autoresume), - "window_transparency": str(state.window_transparency) + "window_transparency": str(state.window_transparency), + "accent_color": str(state.accent_color) } config["Network"] = { @@ -84,6 +85,7 @@ def read_config(): state.ignore_updates = config.getboolean("General", "ignore_updates", fallback=state.ignore_updates) state.autoresume = config.getboolean("General", "autoresume", fallback=state.autoresume) state.window_transparency = config.getboolean("General", "window_transparency", fallback=state.window_transparency) + state.accent_color = config.get("General", "accent_color", fallback=state.accent_color) # Network state.api_url = config.get("Network", "api_url", fallback=state.api_url) diff --git a/src/utils/config/settings.py b/src/utils/config/settings.py index 2fc94a6..09df0da 100644 --- a/src/utils/config/settings.py +++ b/src/utils/config/settings.py @@ -5,7 +5,7 @@ from utils.data.state import state -def save_settings(close=lambda: None, apiurl=None, download_path=None, down_speed_limit=None, up_speed_limit=None, image_path=None, autoresume=None, max_connections=None, max_downloads=None, bound_interface=None, image_width=None, image_offset=None, image_opacity=None, image_as_wallpaper=None, image_position=None): +def save_settings(close=lambda: None, apiurl=None, download_path=None, down_speed_limit=None, up_speed_limit=None, image_path=None, autoresume=None, max_connections=None, max_downloads=None, bound_interface=None, image_width=None, image_offset=None, image_opacity=None, image_as_wallpaper=None, image_position=None, accent_color=None): if apiurl is not None: state.api_url = apiurl if download_path is not None: @@ -34,6 +34,8 @@ def save_settings(close=lambda: None, apiurl=None, download_path=None, down_spee state.image_as_wallpaper = image_as_wallpaper if image_position is not None: state.image_position = image_position + if accent_color is not None: + state.accent_color = accent_color update_settings() # Update LibTorrent Session Settings consoleLog("Saved Settings") diff --git a/src/utils/data/state.py b/src/utils/data/state.py index 75ed4eb..1004d0a 100644 --- a/src/utils/data/state.py +++ b/src/utils/data/state.py @@ -25,6 +25,7 @@ class AppState(QObject): # GUI self.window_transparency: bool = False + self.accent_color: str = "" self.trackertable: QTableWidget self.interfaces: List = [] self.active_interfaces: List = []