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.
This commit is contained in:
Vxrtrauter
2026-04-05 06:39:29 +02:00
parent 9e3d1cf90a
commit 12506dd1df
7 changed files with 46 additions and 7 deletions
+8 -2
View File
@@ -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)
+20 -1
View File
@@ -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"]};
+5
View File
@@ -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
+6 -2
View File
@@ -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()
+3 -1
View File
@@ -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)
+3 -1
View File
@@ -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")
+1
View File
@@ -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 = []