1717
1818#include < QDesktopServices>
1919#include < QSettings>
20+ #include < QWidgetAction>
21+ #include < QToolButton>
22+ #include < QHBoxLayout>
23+ #include < QVBoxLayout>
24+ #include < QFrame>
25+ #include < QPainter>
26+
27+ namespace {
28+ QIcon recoloredIcon (const QString &svgPath, const QColor &color, int size = 18 )
29+ {
30+ QIcon svgIcon (svgPath);
31+ QImage img = svgIcon.pixmap (QSize (size, size)).toImage ().convertToFormat (QImage::Format_ARGB32);
32+ for (int y = 0 ; y < img.height (); y++)
33+ {
34+ QRgb *line = reinterpret_cast <QRgb*>(img.scanLine (y));
35+ for (int x = 0 ; x < img.width (); x++)
36+ {
37+ int a = qAlpha (line[x]);
38+ if (a > 0 )
39+ line[x] = qRgba (color.red (), color.green (), color.blue (), a);
40+ }
41+ }
42+ return QIcon (QPixmap::fromImage (img));
43+ }
44+ }
2045
2146SysTray::SysTray (HyperionDaemon* hyperiond)
2247 : QSystemTrayIcon(QIcon(" :/hyperion-32px.png" ), hyperiond)
@@ -31,6 +56,10 @@ SysTray::SysTray(HyperionDaemon* hyperiond)
3156 QGuiApplication::setAttribute (Qt::AA_UseHighDpiPixmaps);
3257#endif
3358
59+ // Detect dark theme
60+ QPalette pal = QGuiApplication::palette ();
61+ _darkTheme = pal.color (QPalette::Window).lightness () < 128 ;
62+
3463 setupConnections ();
3564 createBaseTrayMenu ();
3665
@@ -64,6 +93,15 @@ void SysTray::createBaseTrayMenu()
6493{
6594 _trayMenu = new QMenu ();
6695
96+ // Dark theme styling
97+ if (_darkTheme)
98+ {
99+ _trayMenu->setStyleSheet (
100+ " QMenu { background-color: #2b2b2b; color: #e0e0e0; border: 1px solid #555; }"
101+ " QMenu::separator { height: 1px; background: #555; margin: 4px 8px; }"
102+ );
103+ }
104+
67105 // Create actions
68106 _settingsAction = createAction (tr (" &Settings" ), " :/settings.svg" , [this ]() {
69107 settings ();
@@ -121,9 +159,24 @@ void SysTray::setupConnections()
121159
122160QAction *SysTray::createAction (const QString &text, const QString &iconPath, const std::function<void ()> &method)
123161{
124- auto * action = new QAction (text, this );
125- action->setIcon (QIcon (iconPath));
126- connect (action, &QAction::triggered, this , method);
162+ QColor ic = _darkTheme ? Qt::white : Qt::black;
163+ QIcon icon = recoloredIcon (iconPath, ic, 18 );
164+
165+ auto * btn = new QToolButton ();
166+ btn->setIcon (icon);
167+ btn->setText (QStringLiteral (" " ) + text);
168+ btn->setToolButtonStyle (Qt::ToolButtonTextBesideIcon);
169+ btn->setIconSize (QSize (18 , 18 ));
170+ btn->setAutoRaise (true );
171+ btn->setCursor (Qt::PointingHandCursor);
172+ btn->setStyleSheet (
173+ " QToolButton { border: none; text-align: left; padding: 4px 16px 4px 10px; }"
174+ " QToolButton:hover { background-color: rgba(128,128,128,64); }"
175+ );
176+ QObject::connect (btn, &QToolButton::clicked, this , method);
177+
178+ auto * action = new QWidgetAction (this );
179+ action->setDefaultWidget (btn);
127180 return action;
128181}
129182
@@ -194,35 +247,26 @@ void SysTray::handleInstanceStarted(quint8 instance)
194247 return ;
195248 }
196249
197- // Create a new menu for this instance
250+ // Get instance name
198251 QString instanceName;
199252 if (auto mgr = _instanceManagerWeak.toStrongRef ())
200253 {
201254 instanceName = mgr->getInstanceName (instance);
202255 }
203- auto * instanceMenu = new QMenu (instanceName);
204256
205- // Create actions for the instance menu
206- QAction *colorAction = createAction (tr (" &Color" ), " :/color.svg" , [this , instance]() {
207- showColorDialog (instance);
208- });
209- instanceMenu->addAction (colorAction);
257+ // First instance: horizontal icon buttons directly in main menu
258+ if (!_firstInstanceAction)
259+ {
260+ _firstInstanceNumber = instance;
210261
211262#if defined(ENABLE_EFFECTENGINE)
212- // Get the list of effects
213- const QList<EffectDefinition> effectsDefinitions = EffectFileHandler::getInstance ()->getEffects ();
214-
215- if (!effectsDefinitions.empty ())
216- {
217- // Effects submenu
218- auto * effectsMenu = new QMenu (tr (" Effects" ), instanceMenu);
219- effectsMenu->setObjectName (" effectsMenu" );
220- instanceMenu->addMenu (effectsMenu);
263+ _firstEffectsMenu = new QMenu (tr (" Effects" ));
264+ _firstEffectsMenu->setObjectName (" effectsMenu" );
221265
222- // Add effects when the menu is first created
266+ const QList<EffectDefinition> effectsDefinitions = EffectFileHandler::getInstance ()-> getEffects ();
223267 for (const auto & effect : effectsDefinitions)
224268 {
225- QAction const * effectAction = effectsMenu ->addAction (effect.name );
269+ QAction* effectAction = _firstEffectsMenu ->addAction (effect.name );
226270 connect (effectAction, &QAction::triggered, [this , instance, effectName = effect.name ]() {
227271 setEffect (instance, effectName);
228272 });
@@ -231,23 +275,163 @@ void SysTray::handleInstanceStarted(quint8 instance)
231275 {
232276 connect (eff.get (), &EffectFileHandler::effectListChanged, this , &SysTray::onEffectListChanged);
233277 }
234- }
235278#endif
236279
237- QAction *clearAction = createAction (tr (" &Clear" ), " :/clear.svg" , [instance, this ]() {
238- clearSource (instance);
239- });
240- instanceMenu->addAction (clearAction);
280+ QColor ic = _darkTheme ? Qt::white : Qt::black;
281+
282+ auto * btnWidget = new QWidget ();
283+ auto * hLayout = new QHBoxLayout (btnWidget);
284+ hLayout->setContentsMargins (8 , 6 , 8 , 0 );
285+ hLayout->setSpacing (4 );
286+ btnWidget->setFixedHeight (37 );
287+
288+ auto makeBtnGroup = [&](const QIcon& icon, const QString& tip, bool checkable = false ) -> std::tuple<QToolButton*, QFrame*> {
289+ auto * container = new QWidget ();
290+ container->setFixedWidth (26 );
291+ auto * col = new QVBoxLayout (container);
292+ col->setContentsMargins (0 , 0 , 0 , 0 );
293+ col->setSpacing (1 );
294+
295+ auto * btn = new QToolButton ();
296+ btn->setIcon (icon);
297+ btn->setToolTip (tip);
298+ if (checkable) btn->setCheckable (true );
299+ btn->setIconSize (QSize (26 , 26 ));
300+ btn->setAutoRaise (true );
301+ btn->setStyleSheet (" QToolButton:hover { background-color: #3d6db5; }" );
302+ col->addWidget (btn, 0 , Qt::AlignHCenter);
303+
304+ auto * ind = new QFrame ();
305+ ind->setFixedSize (26 , 4 );
306+ ind->setStyleSheet (" background-color: transparent; border-radius: 2px;" );
307+ col->addWidget (ind, 0 , Qt::AlignHCenter);
308+
309+ hLayout->addWidget (container, 0 , Qt::AlignVCenter);
310+ return {btn, ind};
311+ };
312+
313+ auto [colorBtn, colorInd] = makeBtnGroup (recoloredIcon (" :/color.svg" , ic, 26 ), tr (" Color" ));
314+
315+ #if defined(ENABLE_EFFECTENGINE)
316+ auto [effectsBtn, effectsInd] = makeBtnGroup (recoloredIcon (" :/effects.svg" , ic, 26 ), tr (" Effects" ), true );
317+ effectsBtn->setPopupMode (QToolButton::InstantPopup);
318+ effectsBtn->setMenu (_firstEffectsMenu);
319+ #else
320+ QToolButton* effectsBtn = nullptr ;
321+ QFrame* effectsInd = nullptr ;
322+ #endif
323+
324+ auto [clearBtn, clearInd] = makeBtnGroup (recoloredIcon (" :/clear.svg" , ic, 26 ), tr (" Clear" ));
325+
326+ // Color clicked
327+ connect (colorBtn, &QToolButton::clicked, [this , colorBtn, effectsBtn, effectsInd, instance, colorInd]() {
328+ QColor selectedColor = QColorDialog::getColor (_lastColor, nullptr , tr (" Select Color" ));
329+ if (selectedColor.isValid ())
330+ {
331+ _lastColor = selectedColor;
332+ setColor (instance, selectedColor);
333+ colorInd->setStyleSheet (QString (
334+ " background-color: %1; border-radius: 2px;"
335+ ).arg (selectedColor.name ()));
336+ if (effectsBtn) {
337+ effectsBtn->setChecked (false );
338+ effectsInd->setStyleSheet (
339+ " background-color: transparent; border-radius: 2px;"
340+ );
341+ }
342+ }
343+ });
344+
345+ // Effects triggered
346+ #if defined(ENABLE_EFFECTENGINE)
347+ connect (_firstEffectsMenu, &QMenu::triggered, [effectsBtn, colorInd, effectsInd](QAction*) {
348+ effectsBtn->setChecked (true );
349+ colorInd->setStyleSheet (
350+ " background-color: transparent; border-radius: 2px;"
351+ );
352+ effectsInd->setStyleSheet (
353+ " background-color: #32cd32; border-radius: 2px;"
354+ );
355+ });
356+ #endif
357+
358+ // Clear clicked
359+ connect (clearBtn, &QToolButton::clicked, [this , colorInd, effectsBtn, effectsInd, instance]() {
360+ clearSource (instance);
361+ colorInd->setStyleSheet (
362+ " background-color: transparent; border-radius: 2px;"
363+ );
364+ if (effectsBtn) {
365+ effectsBtn->setChecked (false );
366+ effectsInd->setStyleSheet (
367+ " background-color: transparent; border-radius: 2px;"
368+ );
369+ }
370+ });
371+
372+ _firstInstanceAction = new QWidgetAction (this );
373+ _firstInstanceAction->setDefaultWidget (btnWidget);
374+ _trayMenu->insertAction (_settingsAction, _firstInstanceAction);
375+ }
376+ else
377+ {
378+ // Subsequent instances get submenus
379+ auto * instanceMenu = new QMenu (instanceName);
241380
242- _trayMenu->insertMenu (_suspendAction, instanceMenu);
381+ QAction *colorAction = createAction (tr (" &Color" ), " :/color.svg" , [this , instance]() {
382+ showColorDialog (instance);
383+ });
384+ instanceMenu->addAction (colorAction);
243385
244- // Store the menu for later reference
245- _instanceMenus[instance] = instanceMenu;
386+ #if defined(ENABLE_EFFECTENGINE)
387+ const QList<EffectDefinition> effectsDefinitions = EffectFileHandler::getInstance ()->getEffects ();
388+ if (!effectsDefinitions.empty ())
389+ {
390+ auto * effectsMenu = new QMenu (tr (" Effects" ), instanceMenu);
391+ effectsMenu->setObjectName (" effectsMenu" );
392+ instanceMenu->addMenu (effectsMenu);
393+
394+ for (const auto & effect : effectsDefinitions)
395+ {
396+ QAction* effectAction = effectsMenu->addAction (effect.name );
397+ connect (effectAction, &QAction::triggered, [this , instance, effectName = effect.name ]() {
398+ setEffect (instance, effectName);
399+ });
400+ }
401+ if (auto eff = EffectFileHandler::getInstance ())
402+ {
403+ connect (eff.get (), &EffectFileHandler::effectListChanged, this , &SysTray::onEffectListChanged);
404+ }
405+ }
406+ #endif
407+
408+ QAction *clearAction = createAction (tr (" &Clear" ), " :/clear.svg" , [instance, this ]() {
409+ clearSource (instance);
410+ });
411+ instanceMenu->addAction (clearAction);
412+
413+ _trayMenu->insertMenu (_suspendAction, instanceMenu);
414+ _instanceMenus[instance] = instanceMenu;
415+ }
246416}
247417
248418void SysTray::handleInstanceStopped (quint8 instance)
249419{
250- // Check if the instance exists
420+ // Check if the first instance (inline buttons) is stopping
421+ if (_firstInstanceAction && instance == _firstInstanceNumber)
422+ {
423+ _trayMenu->removeAction (_firstInstanceAction);
424+ delete _firstInstanceAction;
425+ _firstInstanceAction = nullptr ;
426+
427+ #if defined(ENABLE_EFFECTENGINE)
428+ delete _firstEffectsMenu;
429+ _firstEffectsMenu = nullptr ;
430+ #endif
431+ return ;
432+ }
433+
434+ // Check if the instance menu exists
251435 if (!_instanceMenus.contains (instance))
252436 return ;
253437
@@ -280,9 +464,24 @@ void SysTray::onEffectListChanged()
280464 // Get the updated list of effects
281465 const QList<EffectDefinition> effectsDefinitions = EffectFileHandler::getInstance ()->getEffects ();
282466
467+ // Update inline effects menu for the first instance
468+ if (_firstEffectsMenu)
469+ {
470+ _firstEffectsMenu->clear ();
471+ quint8 instance = _firstInstanceNumber;
472+ for (const auto & effect : effectsDefinitions)
473+ {
474+ QAction* effectAction = _firstEffectsMenu->addAction (effect.name );
475+ connect (effectAction, &QAction::triggered, [this , instance, effectName = effect.name ]() {
476+ setEffect (instance, effectName);
477+ });
478+ }
479+ }
480+
481+ // Update submenu effects for other instances
283482 for (auto it = _instanceMenus.begin (); it != _instanceMenus.end (); ++it)
284483 {
285- QMenu const * instanceMenu = it.value (); // Access the value (QMenu*) from the map
484+ QMenu* instanceMenu = it.value ();
286485 quint8 instanceNumber = it.key ();
287486
288487 QMenu* effectsMenu = instanceMenu->findChild <QMenu*>(" effectsMenu" );
@@ -295,7 +494,7 @@ void SysTray::onEffectListChanged()
295494 // Re-add the updated list of effects
296495 for (const auto & effect : effectsDefinitions)
297496 {
298- QAction const * effectAction = effectsMenu->addAction (effect.name );
497+ QAction* effectAction = effectsMenu->addAction (effect.name );
299498 connect (effectAction, &QAction::triggered, [this , instance = instanceNumber, effectName = effect.name ]() {
300499 setEffect (instance, effectName);
301500 });
@@ -342,14 +541,17 @@ void SysTray::settings() const
342541bool SysTray::getCurrentAutorunState ()
343542{
344543 const QSettings reg (" HKEY_CURRENT_USER\\ SOFTWARE\\ Microsoft\\ Windows\\ CurrentVersion\\ Run" , QSettings::NativeFormat);
345- if (reg.value (" Hyperion" , 0 ).toString () == QApplication::applicationFilePath ().replace (' /' , ' \\ ' ))
544+ bool enabled = (reg.value (" Hyperion" , 0 ).toString () == QApplication::applicationFilePath ().replace (' /' , ' \\ ' ));
545+ auto * wa = qobject_cast<QWidgetAction*>(_autorunAction);
546+ if (wa)
346547 {
347- _autorunAction->setText (tr (" &Disable autostart" ));
348- return true ;
548+ QToolButton* btn = qobject_cast<QToolButton*>(wa->defaultWidget ());
549+ if (btn)
550+ {
551+ btn->setText (QStringLiteral (" " ) + (enabled ? tr (" &Disable autostart" ) : tr (" &Enable autostart" )));
552+ }
349553 }
350-
351- _autorunAction->setText (tr (" &Enable autostart" ));
352- return false ;
554+ return enabled;
353555}
354556
355557void SysTray::setAutorunState ()
0 commit comments