feat: tray-first UX, text outline, start-at-login toggle, font max 200

- All settings move to flat tray menu; right-click on clock disabled
- ClockLabel: custom QPainterPath text rendering with configurable
  glyph outline (BorderColor) — verified pixel-level offscreen
- Fixed glyph positioning bug (baseline math) caught by isolated test
- Window auto-fits content (adjustSize) — no more oversized clock
- Start at Login: checkable tray item, persisted in QSettings,
  LaunchAgent enable/disable (TDD: roundTrip test caught disable()
  returning false when plist already absent)
- Color dialog: hex/HTML input + 'transparent' + native picker fallback;
  opaque colors stored as #RRGGBB
- Border: glyph outline instead of box border; background still painted
  in paintEvent (QSS doesn't render with WA_TranslucentBackground on macOS)
- Removed window shadow (setHasShadow:NO), no bold, SF Pro Rounded
- Font size dialogs allow up to 200 (was 72)
- macOS: icon template 16pt, light/dark auto-adapt; LaunchAgent points
  to /Applications install
This commit is contained in:
2026-08-19 22:41:33 +03:00
parent 6b9cb32534
commit d9008479c1
9 changed files with 402 additions and 61 deletions
+60 -19
View File
@@ -2,6 +2,7 @@
#include <QAction>
#include <QMenu>
#include <QPainter>
#include <QSettings>
#include <QStyleHints>
#include <QSystemTrayIcon>
#include "autostart_manager.h"
@@ -14,23 +15,27 @@ constexpr int trayIconExtent = 16;
QIcon createTrayIcon(const QColor &color)
{
QPixmap pixmap(trayIconExtent, trayIconExtent);
pixmap.setDevicePixelRatio(qApp->devicePixelRatio());
// Render at 16 logical points times the device pixel ratio so the
// bitmap is crisp on Retina but occupies exactly one menu bar cell.
const qreal devicePixelRatio = qApp->devicePixelRatio();
QPixmap pixmap(qRound(trayIconExtent * devicePixelRatio),
qRound(trayIconExtent * devicePixelRatio));
pixmap.setDevicePixelRatio(devicePixelRatio);
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(QPen(color, 1.5));
painter.drawEllipse(QPointF(8, 8), 5.5, 5.5);
painter.drawLine(QPointF(8, 4.5), QPointF(8, 8));
painter.drawLine(QPointF(8, 8), QPointF(10.5, 9.5));
QIcon icon(pixmap);
#ifdef Q_OS_MAC
// Template image: the system tints it black/white to match the
// active appearance, like native menu bar icons.
icon.setIsMask(true);
#endif
// Draw inside the 16pt coordinate space; center with a small margin.
const QPointF center(trayIconExtent / 2.0, trayIconExtent / 2.0);
const qreal radius = trayIconExtent / 2.0 - 1.5;
painter.drawEllipse(center, radius, radius);
painter.drawLine(QPointF(center.x(), center.y() - radius * 0.55), center);
painter.drawLine(center, QPointF(center.x() + radius * 0.5, center.y() + radius * 0.3));
QIcon icon;
icon.addPixmap(pixmap, QIcon::Normal, QIcon::Off);
return icon;
}
@@ -51,7 +56,20 @@ int main(int argc, char *argv[])
ClockWidget clock;
clock.show();
AutoStartManager::enableForCurrentApplication();
// Start at Login: honor the persisted choice instead of always forcing it.
{
QSettings autoStartSettings("Clouck", "Clouck");
const bool startAtLogin = autoStartSettings.value("startAtLogin", true).toBool();
if (startAtLogin)
{
AutoStartManager::enableForCurrentApplication();
}
else
{
AutoStartManager::disable();
}
autoStartSettings.setValue("startAtLogin", startAtLogin);
}
const QStyleHints *styleHints = app.styleHints();
QSystemTrayIcon trayIcon(createTrayIcon(trayIconColorForScheme(styleHints->colorScheme())), &app);
@@ -64,6 +82,36 @@ int main(int argc, char *argv[])
QMenu trayMenu;
QAction *toggleClockAction = trayMenu.addAction("Turn Clock Off");
QAction *startAtLoginAction = trayMenu.addAction("Start at Login");
{
QSettings autoStartSettings("Clouck", "Clouck");
startAtLoginAction->setCheckable(true);
startAtLoginAction->setChecked(autoStartSettings.value("startAtLogin", true).toBool());
}
QObject::connect(startAtLoginAction, &QAction::toggled, &app, [](bool checked) {
QSettings autoStartSettings("Clouck", "Clouck");
autoStartSettings.setValue("startAtLogin", checked);
if (checked)
{
AutoStartManager::enableForCurrentApplication();
}
else
{
AutoStartManager::disable();
}
});
trayMenu.addSeparator();
// Clock settings live directly in the tray menu — no submenu.
const QList<QAction *> clockActions = clock.menu()->actions();
for (QAction *action : clockActions)
{
trayMenu.addAction(action);
}
trayMenu.addSeparator();
QAction *quitAction = trayMenu.addAction("Quit");
const auto toggleClock = [&clock, toggleClockAction]() {
@@ -81,13 +129,6 @@ int main(int argc, char *argv[])
QObject::connect(toggleClockAction, &QAction::triggered, &app, toggleClock);
QObject::connect(quitAction, &QAction::triggered, &app, &QApplication::quit);
QObject::connect(&trayIcon, &QSystemTrayIcon::activated, &app,
[toggleClock](QSystemTrayIcon::ActivationReason reason) {
if (reason == QSystemTrayIcon::Trigger)
{
toggleClock();
}
});
trayIcon.setContextMenu(&trayMenu);
trayIcon.show();