Skip to content

Commit 4a78946

Browse files
style
1 parent 6cc9f20 commit 4a78946

8 files changed

Lines changed: 649 additions & 247 deletions

File tree

src/multiclaw/core/settings.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@
2121
"verify_settlements": False,
2222
"max_request_age_seconds": 300,
2323
"enabled_networks": {
24-
"8453": False, # Base Mainnet - disabled by default
25-
"84532": True, # Base Sepolia - enabled by default
24+
"1": True, # Ethereum Mainnet
25+
"11155111": True, # Ethereum Sepolia
26+
"8453": True, # Base Mainnet
27+
"84532": True, # Base Sepolia
28+
"1187947933": True, # SKALE Base
29+
"324705682": True, # SKALE Base Sepolia
2630
}
2731
},
2832
"server": {
@@ -44,7 +48,9 @@ class SigningSettings:
4448
"""Signing-related settings."""
4549
verify_settlements: bool = False
4650
max_request_age_seconds: int = 300
47-
enabled_networks: dict = field(default_factory=lambda: {"8453": False, "84532": True})
51+
enabled_networks: dict = field(default_factory=lambda: {
52+
"1": True, "11155111": True, "8453": True, "84532": True, "1187947933": True, "324705682": True
53+
})
4854

4955

5056
@dataclass
@@ -104,7 +110,9 @@ def from_dict(cls, data: dict) -> "AppSettings":
104110
s = data["signing"]
105111
settings.signing.verify_settlements = s.get("verify_settlements", False)
106112
settings.signing.max_request_age_seconds = s.get("max_request_age_seconds", 300)
107-
settings.signing.enabled_networks = s.get("enabled_networks", {"8453": False, "84532": True})
113+
settings.signing.enabled_networks = s.get("enabled_networks", {
114+
"1": True, "11155111": True, "8453": True, "84532": True, "1187947933": True, "324705682": True
115+
})
108116

109117
if "server" in data:
110118
s = data["server"]

src/multiclaw/ui/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
- Dialogs: Agent registration, policy editing, wallet management, settings
99
"""
1010

11-
from .theme import Theme, ask_question, show_warning, show_info
11+
from .theme import Theme, LIGHT, DARK, build_qss, set_role, ask_question, show_warning, show_info
1212
from .main_window import MainWindow
1313
from .tabs import (
1414
PoliciesTab,
@@ -30,6 +30,10 @@
3030
__all__ = [
3131
# Theme
3232
"Theme",
33+
"LIGHT",
34+
"DARK",
35+
"build_qss",
36+
"set_role",
3337
"ask_question",
3438
"show_warning",
3539
"show_info",

src/multiclaw/ui/dialogs.py

Lines changed: 73 additions & 68 deletions
Large diffs are not rendered by default.

src/multiclaw/ui/main_window.py

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from typing import Optional, TYPE_CHECKING
1515
from datetime import datetime
1616

17-
from .theme import Theme
17+
from .theme import Theme, DARK, build_qss, set_role
1818
from .tabs import (
1919
PoliciesTab, AgentsTab, HistoryTab, WalletTab, NetworkTab, LogTab
2020
)
@@ -191,6 +191,9 @@ def __init__(self, core: "MultiClaw"):
191191
self.setup_signing_service()
192192
self.setup_event_subscriptions()
193193

194+
# Apply theme (default to light)
195+
self.apply_theme(self._settings.get("theme", "light"))
196+
194197
# Initialize status indicators
195198
self.update_status_indicators()
196199

@@ -199,7 +202,7 @@ def __init__(self, core: "MultiClaw"):
199202
QTimer.singleShot(0, self.hide if self._settings.get("minimize_to_tray", False) else self.showMinimized)
200203

201204
# Auto-start server if enabled
202-
if self._settings.get("auto_start_server", False):
205+
if self._settings.get("auto_start_server", True):
203206
QTimer.singleShot(500, self.network_tab.toggle_server)
204207

205208
def _get_agents_for_address(self, wallet_address: str) -> list:
@@ -312,28 +315,17 @@ def update_status_indicators(self):
312315
"""Update all three status indicators in the header."""
313316
# Wallet indicator - green only if we have wallets AND unlocked
314317
has_wallets = len(self.wallet_tab.get_wallet_list()) > 0
315-
if has_wallets and self.wallet_tab.is_unlocked:
316-
self.wallet_indicator.setStyleSheet(f"color: {Theme.LIME}; font-size: 12px;")
317-
self.wallet_label.setStyleSheet(f"color: {Theme.LIME};")
318-
else:
319-
self.wallet_indicator.setStyleSheet(f"color: {Theme.RUST}; font-size: 12px;")
320-
self.wallet_label.setStyleSheet(f"color: {Theme.RUST};")
318+
wallet_on = has_wallets and self.wallet_tab.is_unlocked
319+
set_role(self.wallet_indicator, status="on" if wallet_on else "off")
320+
set_role(self.wallet_label, status="on" if wallet_on else "off")
321321

322322
# Server indicator
323-
if self.server_running:
324-
self.server_indicator.setStyleSheet(f"color: {Theme.LIME}; font-size: 12px;")
325-
self.server_label.setStyleSheet(f"color: {Theme.LIME};")
326-
else:
327-
self.server_indicator.setStyleSheet(f"color: {Theme.RUST}; font-size: 12px;")
328-
self.server_label.setStyleSheet(f"color: {Theme.RUST};")
323+
set_role(self.server_indicator, status="on" if self.server_running else "off")
324+
set_role(self.server_label, status="on" if self.server_running else "off")
329325

330326
# Signing indicator
331-
if self.signing_enabled:
332-
self.signing_indicator.setStyleSheet(f"color: {Theme.LIME}; font-size: 12px;")
333-
self.signing_label.setStyleSheet(f"color: {Theme.LIME};")
334-
else:
335-
self.signing_indicator.setStyleSheet(f"color: {Theme.RUST}; font-size: 12px;")
336-
self.signing_label.setStyleSheet(f"color: {Theme.RUST};")
327+
set_role(self.signing_indicator, status="on" if self.signing_enabled else "off")
328+
set_role(self.signing_label, status="on" if self.signing_enabled else "off")
337329

338330
def _load_settings(self) -> dict:
339331
"""Load GUI-specific settings from disk.
@@ -366,6 +358,16 @@ def _save_settings(self):
366358
except Exception as e:
367359
logging.getLogger(__name__).error(f"Failed to save GUI settings: {e}")
368360

361+
def apply_theme(self, theme_name: str = "light"):
362+
"""Apply the selected theme (light or dark) to the application."""
363+
from .theme import LIGHT, DARK, build_qss
364+
palette = DARK if theme_name == "dark" else LIGHT
365+
app = QApplication.instance()
366+
if app:
367+
app.setStyleSheet(build_qss(palette))
368+
self._settings["theme"] = theme_name
369+
self._save_settings()
370+
369371
def update_status(self):
370372
"""Update status bar."""
371373
if self.core.is_server_running():
@@ -375,7 +377,7 @@ def update_status(self):
375377

376378
def create_header(self) -> QFrame:
377379
header = QFrame()
378-
header.setStyleSheet(f"background-color: {Theme.BLACK};")
380+
header.setObjectName("header") # Styled via QSS #header selector
379381
header.setFixedHeight(100)
380382

381383
header_layout = QHBoxLayout(header)
@@ -390,7 +392,7 @@ def create_header(self) -> QFrame:
390392
logo_label.setPixmap(scaled)
391393
else:
392394
logo_label.setText("MULTICLAW")
393-
logo_label.setStyleSheet(f"color: {Theme.LIME}; font-weight: bold; font-size: 16px;")
395+
logo_label.setObjectName("logo") # Styled via QSS #logo selector
394396

395397
header_layout.addWidget(logo_label, alignment=Qt.AlignmentFlag.AlignVCenter)
396398
header_layout.addStretch()
@@ -403,12 +405,12 @@ def create_header(self) -> QFrame:
403405
wallet_row = QHBoxLayout()
404406
wallet_row.setSpacing(4)
405407
self.wallet_indicator = QLabel("●")
406-
self.wallet_indicator.setStyleSheet(f"color: {Theme.RUST}; font-size: 12px;")
408+
self.wallet_indicator.setProperty("status", "off")
407409
self.wallet_indicator.setFixedWidth(12)
408410
wallet_row.addWidget(self.wallet_indicator)
409411
self.wallet_label = QLabel("Wallet")
410412
self.wallet_label.setFont(QFont(Theme.MONO_FONT, 9))
411-
self.wallet_label.setStyleSheet(f"color: {Theme.RUST};")
413+
self.wallet_label.setProperty("status", "off")
412414
self.wallet_label.setCursor(Qt.CursorShape.PointingHandCursor)
413415
self.wallet_label.mousePressEvent = lambda e: self.on_wallet_indicator_clicked()
414416
wallet_row.addWidget(self.wallet_label)
@@ -418,27 +420,27 @@ def create_header(self) -> QFrame:
418420
server_row = QHBoxLayout()
419421
server_row.setSpacing(4)
420422
self.server_indicator = QLabel("●")
421-
self.server_indicator.setStyleSheet(f"color: {Theme.RUST}; font-size: 12px;")
423+
self.server_indicator.setProperty("status", "off")
422424
self.server_indicator.setFixedWidth(12)
423425
server_row.addWidget(self.server_indicator)
424426
self.server_label = QLabel("Server")
425427
self.server_label.setFont(QFont(Theme.MONO_FONT, 9))
426-
self.server_label.setStyleSheet(f"color: {Theme.RUST};")
428+
self.server_label.setProperty("status", "off")
427429
self.server_label.setCursor(Qt.CursorShape.PointingHandCursor)
428430
self.server_label.mousePressEvent = lambda e: self.on_server_indicator_clicked()
429431
server_row.addWidget(self.server_label)
430432
status_layout.addLayout(server_row)
431433

432-
# Signing indicator
434+
# Signing indicator (starts enabled)
433435
signing_row = QHBoxLayout()
434436
signing_row.setSpacing(4)
435437
self.signing_indicator = QLabel("●")
436-
self.signing_indicator.setStyleSheet(f"color: {Theme.LIME}; font-size: 12px;")
438+
self.signing_indicator.setProperty("status", "on")
437439
self.signing_indicator.setFixedWidth(12)
438440
signing_row.addWidget(self.signing_indicator)
439441
self.signing_label = QLabel("Signing")
440442
self.signing_label.setFont(QFont(Theme.MONO_FONT, 9))
441-
self.signing_label.setStyleSheet(f"color: {Theme.LIME};")
443+
self.signing_label.setProperty("status", "on")
442444
self.signing_label.setCursor(Qt.CursorShape.PointingHandCursor)
443445
self.signing_label.mousePressEvent = lambda e: self.toggle_global_signing()
444446
signing_row.addWidget(self.signing_label)
@@ -450,13 +452,7 @@ def create_header(self) -> QFrame:
450452
self.activity_log = QTextEdit()
451453
self.activity_log.setReadOnly(True)
452454
self.activity_log.setFont(QFont(Theme.MONO_FONT, 9))
453-
self.activity_log.setStyleSheet(f"""
454-
QTextEdit {{
455-
background-color: transparent;
456-
border: none;
457-
color: {Theme.LIME_DIM};
458-
}}
459-
""")
455+
# Activity log is styled via QSS: QWidget#header QTextEdit
460456
self.activity_log.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
461457
self.activity_log.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
462458
self.activity_log.setMinimumWidth(450)
@@ -841,6 +837,7 @@ def show_settings(self):
841837
dialog = SettingsDialog(self._settings, self)
842838
if dialog.exec() == QDialog.DialogCode.Accepted and dialog.has_changes():
843839
new_settings = dialog.get_settings()
840+
old_theme = self._settings.get("theme", "light")
844841
self._settings.update(new_settings)
845842
self._save_settings()
846843

@@ -854,6 +851,11 @@ def show_settings(self):
854851
replay_window = new_settings.get("replay_window_seconds", 300)
855852
self.core.set_max_request_age(replay_window)
856853

854+
# Apply theme if changed
855+
new_theme = new_settings.get("theme", "light")
856+
if new_theme != old_theme:
857+
self.apply_theme(new_theme)
858+
857859
def register_agent(self):
858860
"""Open the agent registration dialog."""
859861
self.tabs.setCurrentWidget(self.agents_tab)
@@ -906,12 +908,12 @@ def show_about(self):
906908

907909
# Tagline
908910
tagline = QLabel("MultiClaw, by Primer")
909-
tagline.setStyleSheet(f"font-style: italic;")
911+
tagline.setProperty("role", "hint")
910912
layout.addWidget(tagline)
911913

912914
# Version
913915
version = QLabel(f"Version {__version__}")
914-
version.setStyleSheet(f"color: {Theme.CHARCOAL};")
916+
version.setProperty("role", "muted")
915917
layout.addWidget(version)
916918

917919
layout.addSpacing(4)
@@ -992,7 +994,7 @@ def show_quick_start(self):
992994

993995
if hint:
994996
hint_label = QLabel(f" <i>{hint}</i>")
995-
hint_label.setStyleSheet(f"color: {Theme.CHARCOAL};")
997+
hint_label.setProperty("role", "muted")
996998
layout.addWidget(hint_label)
997999

9981000
layout.addSpacing(12)

src/multiclaw/ui/market_tab.py

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QTimer
2424
from PyQt6.QtGui import QFont, QColor
2525

26-
from .theme import Theme
26+
from .theme import Theme, set_role
2727

2828
logger = logging.getLogger(__name__)
2929

@@ -146,26 +146,19 @@ def _build_ui(self):
146146

147147
self.detail_title = QLabel("Select a service to view details")
148148
self.detail_title.setFont(QFont(Theme.MONO_FONT, 11, QFont.Weight.Bold))
149-
self.detail_title.setStyleSheet(f"color: {Theme.BLACK};")
150149
detail_header.addWidget(self.detail_title)
151150

152151
detail_header.addStretch()
153152

154153
self.copy_snippet_btn = QPushButton("Copy Agent Snippet")
155154
self.copy_snippet_btn.setFont(QFont(Theme.MONO_FONT, 9))
156-
self.copy_snippet_btn.setStyleSheet(
157-
f"background-color: {Theme.LIME}; color: {Theme.BLACK}; "
158-
f"font-weight: bold; padding: 4px 12px; border-radius: 3px;"
159-
)
155+
self.copy_snippet_btn.setProperty("variant", "primary")
160156
self.copy_snippet_btn.clicked.connect(self._copy_agent_snippet)
161157
self.copy_snippet_btn.setEnabled(False)
162158
detail_header.addWidget(self.copy_snippet_btn)
163159

164160
self.copy_url_btn = QPushButton("Copy URL")
165161
self.copy_url_btn.setFont(QFont(Theme.MONO_FONT, 9))
166-
self.copy_url_btn.setStyleSheet(
167-
f"padding: 4px 12px; border-radius: 3px;"
168-
)
169162
self.copy_url_btn.clicked.connect(self._copy_selected_url)
170163
self.copy_url_btn.setEnabled(False)
171164
detail_header.addWidget(self.copy_url_btn)
@@ -175,14 +168,7 @@ def _build_ui(self):
175168
self.detail_text = QTextEdit()
176169
self.detail_text.setReadOnly(True)
177170
self.detail_text.setFont(QFont(Theme.MONO_FONT, 9))
178-
self.detail_text.setStyleSheet(f"""
179-
QTextEdit {{
180-
background-color: {Theme.BLACK};
181-
border: 1px solid {Theme.CHARCOAL};
182-
color: {Theme.WHITE};
183-
padding: 8px;
184-
}}
185-
""")
171+
self.detail_text.setObjectName("terminal") # Styled via QSS
186172
self.detail_text.setMinimumHeight(120)
187173
detail_layout.addWidget(self.detail_text)
188174

@@ -197,7 +183,7 @@ def _build_ui(self):
197183

198184
self.status_label = QLabel("No data loaded")
199185
self.status_label.setFont(QFont(Theme.MONO_FONT, 8))
200-
self.status_label.setStyleSheet(f"color: {Theme.CHARCOAL};")
186+
self.status_label.setProperty("role", "muted")
201187
status_row.addWidget(self.status_label)
202188

203189
status_row.addStretch()
@@ -235,7 +221,7 @@ def _on_fetch_complete(self, services: list, error: str):
235221

236222
if error:
237223
self.status_label.setText(f"Error: {error}")
238-
self.status_label.setStyleSheet(f"color: {Theme.ERROR}; font-size: 8pt;")
224+
set_role(self.status_label, role="error")
239225
self.activity.emit(f"Market fetch failed: {error}", True)
240226
return
241227

@@ -258,7 +244,7 @@ def _on_fetch_complete(self, services: list, error: str):
258244

259245
self._apply_filters()
260246

261-
self.status_label.setStyleSheet(f"color: {Theme.CHARCOAL}; font-size: 8pt;")
247+
set_role(self.status_label, role="muted")
262248
self.status_label.setText(
263249
f"{len(services)} services · Last updated: {self._last_fetch.strftime('%H:%M:%S')}"
264250
)

0 commit comments

Comments
 (0)