Workcat: cute desktop cat, no window closing
Reworks the example into a pure desktop-pet: the cat wanders, sits, grooms, stretches, dashes and naps, reacts to petting and dragging, and follows the cursor with its eyes. The window-scanning and Accessibility code is gone. The cat is drawn from rounded primitives plus Shape paths in a fixed 100x90 design space, so it scales cleanly to any size. Linux/X11 is the primary target: the tray is optional, Wayland gets a warning, and WORKCAT_X11_BYPASS / WORKCAT_OPAQUE cover uncooperative window managers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
acef398f49
17 files changed
+1584
No files matched your search
@@ -0,0 +1,71 @@
|
||||
#include "appsettings.h"
|
||||
|
||||
AppSettings::AppSettings(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QColor AppSettings::coat() const
|
||||
{
|
||||
return m_settings.value("coat", QColor("#f4b183")).value<QColor>();
|
||||
}
|
||||
|
||||
void AppSettings::setCoat(const QColor &color)
|
||||
{
|
||||
if (color == coat())
|
||||
return;
|
||||
m_settings.setValue("coat", color);
|
||||
emit coatChanged();
|
||||
}
|
||||
|
||||
int AppSettings::catSize() const
|
||||
{
|
||||
return m_settings.value("catSize", 120).toInt();
|
||||
}
|
||||
|
||||
void AppSettings::setCatSize(int pixels)
|
||||
{
|
||||
pixels = qBound(72, pixels, 260);
|
||||
if (pixels == catSize())
|
||||
return;
|
||||
m_settings.setValue("catSize", pixels);
|
||||
emit catSizeChanged();
|
||||
}
|
||||
|
||||
qreal AppSettings::walkSpeed() const
|
||||
{
|
||||
return m_settings.value("walkSpeed", 45.0).toReal();
|
||||
}
|
||||
|
||||
void AppSettings::setWalkSpeed(qreal pixelsPerSecond)
|
||||
{
|
||||
pixelsPerSecond = qBound(10.0, pixelsPerSecond, 200.0);
|
||||
if (qFuzzyCompare(pixelsPerSecond, walkSpeed()))
|
||||
return;
|
||||
m_settings.setValue("walkSpeed", pixelsPerSecond);
|
||||
emit walkSpeedChanged();
|
||||
}
|
||||
|
||||
bool AppSettings::followCursor() const
|
||||
{
|
||||
return m_settings.value("followCursor", true).toBool();
|
||||
}
|
||||
|
||||
void AppSettings::setFollowCursor(bool follow)
|
||||
{
|
||||
if (follow == followCursor())
|
||||
return;
|
||||
m_settings.setValue("followCursor", follow);
|
||||
emit followCursorChanged();
|
||||
}
|
||||
|
||||
int AppSettings::petCount() const
|
||||
{
|
||||
return m_settings.value("petCount", 0).toInt();
|
||||
}
|
||||
|
||||
void AppSettings::incrementPetCount()
|
||||
{
|
||||
m_settings.setValue("petCount", petCount() + 1);
|
||||
emit petCountChanged();
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
#include <QSettings>
|
||||
#include <QColor>
|
||||
|
||||
class AppSettings : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("Provided by C++")
|
||||
|
||||
Q_PROPERTY(QColor coat READ coat WRITE setCoat NOTIFY coatChanged)
|
||||
Q_PROPERTY(int catSize READ catSize WRITE setCatSize NOTIFY catSizeChanged)
|
||||
Q_PROPERTY(qreal walkSpeed READ walkSpeed WRITE setWalkSpeed NOTIFY walkSpeedChanged)
|
||||
Q_PROPERTY(bool followCursor READ followCursor WRITE setFollowCursor NOTIFY followCursorChanged)
|
||||
Q_PROPERTY(int petCount READ petCount NOTIFY petCountChanged)
|
||||
|
||||
public:
|
||||
explicit AppSettings(QObject *parent = nullptr);
|
||||
|
||||
QColor coat() const;
|
||||
void setCoat(const QColor &color);
|
||||
|
||||
int catSize() const;
|
||||
void setCatSize(int pixels);
|
||||
|
||||
qreal walkSpeed() const;
|
||||
void setWalkSpeed(qreal pixelsPerSecond);
|
||||
|
||||
bool followCursor() const;
|
||||
void setFollowCursor(bool follow);
|
||||
|
||||
int petCount() const;
|
||||
void incrementPetCount();
|
||||
|
||||
signals:
|
||||
void coatChanged();
|
||||
void catSizeChanged();
|
||||
void walkSpeedChanged();
|
||||
void followCursorChanged();
|
||||
void petCountChanged();
|
||||
|
||||
private:
|
||||
QSettings m_settings;
|
||||
};
|
||||
@@ -0,0 +1,217 @@
|
||||
#include "catcontroller.h"
|
||||
#include "appsettings.h"
|
||||
|
||||
#include <QCursor>
|
||||
#include <QMetaEnum>
|
||||
#include <QRandomGenerator>
|
||||
#include <QWindow>
|
||||
|
||||
CatController::CatController(AppSettings *settings, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_settings(settings)
|
||||
{
|
||||
m_clock.start();
|
||||
|
||||
m_tickTimer.setInterval(16);
|
||||
connect(&m_tickTimer, &QTimer::timeout, this, &CatController::tick);
|
||||
m_tickTimer.start();
|
||||
|
||||
m_moodTimer.setSingleShot(true);
|
||||
connect(&m_moodTimer, &QTimer::timeout, this, &CatController::pickNextMood);
|
||||
|
||||
m_cursorTimer.setInterval(40);
|
||||
connect(&m_cursorTimer, &QTimer::timeout, this, &CatController::trackCursor);
|
||||
m_cursorTimer.start();
|
||||
|
||||
// WORKCAT_POSE=Sleeping freezes the cat in one mood (handy for screenshots).
|
||||
const QByteArray pose = qgetenv("WORKCAT_POSE");
|
||||
if (!pose.isEmpty()) {
|
||||
const QMetaEnum me = QMetaEnum::fromType<Mood>();
|
||||
bool ok = false;
|
||||
const int value = me.keyToValue(pose.constData(), &ok);
|
||||
if (ok) {
|
||||
m_frozen = true;
|
||||
setMood(static_cast<Mood>(value));
|
||||
}
|
||||
}
|
||||
if (!m_frozen)
|
||||
pickNextMood();
|
||||
}
|
||||
|
||||
void CatController::setWindow(QWindow *window)
|
||||
{
|
||||
m_window = window;
|
||||
}
|
||||
|
||||
void CatController::setX(qreal x)
|
||||
{
|
||||
if (qFuzzyCompare(x, m_x))
|
||||
return;
|
||||
m_x = x;
|
||||
emit xChanged();
|
||||
}
|
||||
|
||||
void CatController::setScreenWidth(qreal width)
|
||||
{
|
||||
if (qFuzzyCompare(width, m_screenWidth))
|
||||
return;
|
||||
m_screenWidth = width;
|
||||
emit screenWidthChanged();
|
||||
}
|
||||
|
||||
void CatController::setCatWidth(qreal width)
|
||||
{
|
||||
if (qFuzzyCompare(width, m_catWidth))
|
||||
return;
|
||||
m_catWidth = width;
|
||||
emit catWidthChanged();
|
||||
}
|
||||
|
||||
void CatController::setPaused(bool paused)
|
||||
{
|
||||
if (paused == m_paused)
|
||||
return;
|
||||
m_paused = paused;
|
||||
emit pausedChanged();
|
||||
if (paused) {
|
||||
m_moodTimer.stop();
|
||||
setMood(Mood::Sleeping);
|
||||
} else {
|
||||
pickNextMood();
|
||||
}
|
||||
}
|
||||
|
||||
void CatController::setDragging(bool dragging)
|
||||
{
|
||||
if (dragging == m_dragging)
|
||||
return;
|
||||
m_dragging = dragging;
|
||||
emit draggingChanged();
|
||||
if (m_frozen)
|
||||
return;
|
||||
if (dragging) {
|
||||
m_moodTimer.stop();
|
||||
setMood(Mood::Alert);
|
||||
} else if (!m_paused) {
|
||||
holdMood(Mood::Sitting, 1500);
|
||||
}
|
||||
}
|
||||
|
||||
void CatController::setHovered(bool hovered)
|
||||
{
|
||||
if (hovered == m_hovered)
|
||||
return;
|
||||
m_hovered = hovered;
|
||||
emit hoveredChanged();
|
||||
}
|
||||
|
||||
void CatController::pet()
|
||||
{
|
||||
if (m_paused || m_frozen)
|
||||
return;
|
||||
m_settings->incrementPetCount();
|
||||
holdMood(Mood::Loving, 1800);
|
||||
}
|
||||
|
||||
void CatController::doTrick(Mood mood)
|
||||
{
|
||||
if (m_paused)
|
||||
return;
|
||||
m_frozen = false;
|
||||
holdMood(mood, mood == Mood::Sleeping ? 15000 : 3500);
|
||||
}
|
||||
|
||||
void CatController::holdMood(Mood mood, int ms)
|
||||
{
|
||||
setMood(mood);
|
||||
m_moodTimer.start(ms);
|
||||
}
|
||||
|
||||
void CatController::trackCursor()
|
||||
{
|
||||
if (!m_window || !m_settings->followCursor()) {
|
||||
if (!m_look.isNull()) {
|
||||
m_look = {};
|
||||
emit lookChanged();
|
||||
}
|
||||
return;
|
||||
}
|
||||
const QPointF center = m_window->geometry().center();
|
||||
QPointF delta = QCursor::pos() - center;
|
||||
if (!m_facingRight)
|
||||
delta.setX(-delta.x());
|
||||
const qreal range = 400.0;
|
||||
const QPointF look(qBound(-1.0, delta.x() / range, 1.0), qBound(-1.0, delta.y() / range, 1.0));
|
||||
if ((look - m_look).manhattanLength() > 0.005) {
|
||||
m_look = look;
|
||||
emit lookChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void CatController::tick()
|
||||
{
|
||||
const qint64 now = m_clock.elapsed();
|
||||
const qreal dt = qMin(now - m_lastTickMs, qint64(100)) / 1000.0;
|
||||
m_lastTickMs = now;
|
||||
|
||||
const bool moving = m_mood == Mood::Walking || m_mood == Mood::Running;
|
||||
if (!moving || m_dragging || m_paused || m_frozen)
|
||||
return;
|
||||
|
||||
const qreal speed = m_settings->walkSpeed() * (m_mood == Mood::Running ? 4.5 : 1.0);
|
||||
const qreal step = speed * dt;
|
||||
qreal next = m_x + (m_facingRight ? step : -step);
|
||||
const qreal maxX = qMax(0.0, m_screenWidth - m_catWidth);
|
||||
if (next >= maxX) {
|
||||
next = maxX;
|
||||
setFacingRight(false);
|
||||
if (m_mood == Mood::Running)
|
||||
holdMood(Mood::Sitting, 2000);
|
||||
} else if (next <= 0) {
|
||||
next = 0;
|
||||
setFacingRight(true);
|
||||
if (m_mood == Mood::Running)
|
||||
holdMood(Mood::Sitting, 2000);
|
||||
}
|
||||
setX(next);
|
||||
}
|
||||
|
||||
void CatController::setMood(Mood mood)
|
||||
{
|
||||
if (mood == m_mood)
|
||||
return;
|
||||
m_mood = mood;
|
||||
emit moodChanged();
|
||||
}
|
||||
|
||||
void CatController::setFacingRight(bool right)
|
||||
{
|
||||
if (right == m_facingRight)
|
||||
return;
|
||||
m_facingRight = right;
|
||||
emit facingRightChanged();
|
||||
}
|
||||
|
||||
void CatController::pickNextMood()
|
||||
{
|
||||
if (m_paused || m_dragging || m_frozen)
|
||||
return;
|
||||
auto *rng = QRandomGenerator::global();
|
||||
const int roll = rng->bounded(100);
|
||||
|
||||
if (roll < 45) {
|
||||
if (rng->bounded(100) < 20)
|
||||
setFacingRight(!m_facingRight);
|
||||
holdMood(Mood::Walking, rng->bounded(3000, 9000));
|
||||
} else if (roll < 55) {
|
||||
holdMood(Mood::Running, rng->bounded(1200, 2600));
|
||||
} else if (roll < 72) {
|
||||
holdMood(Mood::Sitting, rng->bounded(2500, 6000));
|
||||
} else if (roll < 82) {
|
||||
holdMood(Mood::Grooming, rng->bounded(2500, 4500));
|
||||
} else if (roll < 90) {
|
||||
holdMood(Mood::Stretching, rng->bounded(1800, 3000));
|
||||
} else {
|
||||
holdMood(Mood::Sleeping, rng->bounded(8000, 20000));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
#include <QTimer>
|
||||
#include <QElapsedTimer>
|
||||
#include <QPointF>
|
||||
|
||||
class AppSettings;
|
||||
class QWindow;
|
||||
|
||||
// Drives the cat: wanders along the bottom of the screen, occasionally dashes,
|
||||
// sits, grooms, stretches or naps, and reacts to petting and dragging.
|
||||
class CatController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("Provided by C++")
|
||||
|
||||
Q_PROPERTY(qreal x READ x WRITE setX NOTIFY xChanged)
|
||||
Q_PROPERTY(qreal screenWidth READ screenWidth WRITE setScreenWidth NOTIFY screenWidthChanged)
|
||||
Q_PROPERTY(qreal catWidth READ catWidth WRITE setCatWidth NOTIFY catWidthChanged)
|
||||
Q_PROPERTY(bool facingRight READ facingRight NOTIFY facingRightChanged)
|
||||
Q_PROPERTY(Mood mood READ mood NOTIFY moodChanged)
|
||||
Q_PROPERTY(bool paused READ paused WRITE setPaused NOTIFY pausedChanged)
|
||||
Q_PROPERTY(bool dragging READ dragging WRITE setDragging NOTIFY draggingChanged)
|
||||
Q_PROPERTY(bool hovered READ hovered WRITE setHovered NOTIFY hoveredChanged)
|
||||
Q_PROPERTY(QPointF look READ look NOTIFY lookChanged)
|
||||
|
||||
public:
|
||||
enum class Mood { Walking, Running, Sitting, Grooming, Stretching, Sleeping, Loving, Alert };
|
||||
Q_ENUM(Mood)
|
||||
|
||||
explicit CatController(AppSettings *settings, QObject *parent = nullptr);
|
||||
|
||||
void setWindow(QWindow *window);
|
||||
|
||||
qreal x() const { return m_x; }
|
||||
void setX(qreal x);
|
||||
|
||||
qreal screenWidth() const { return m_screenWidth; }
|
||||
void setScreenWidth(qreal width);
|
||||
|
||||
qreal catWidth() const { return m_catWidth; }
|
||||
void setCatWidth(qreal width);
|
||||
|
||||
bool facingRight() const { return m_facingRight; }
|
||||
Mood mood() const { return m_mood; }
|
||||
|
||||
bool paused() const { return m_paused; }
|
||||
void setPaused(bool paused);
|
||||
|
||||
bool dragging() const { return m_dragging; }
|
||||
void setDragging(bool dragging);
|
||||
|
||||
bool hovered() const { return m_hovered; }
|
||||
void setHovered(bool hovered);
|
||||
|
||||
QPointF look() const { return m_look; }
|
||||
|
||||
Q_INVOKABLE void pet();
|
||||
Q_INVOKABLE void doTrick(Mood mood);
|
||||
|
||||
signals:
|
||||
void xChanged();
|
||||
void screenWidthChanged();
|
||||
void catWidthChanged();
|
||||
void facingRightChanged();
|
||||
void moodChanged();
|
||||
void pausedChanged();
|
||||
void draggingChanged();
|
||||
void hoveredChanged();
|
||||
void lookChanged();
|
||||
|
||||
private:
|
||||
void tick();
|
||||
void trackCursor();
|
||||
void setMood(Mood mood);
|
||||
void setFacingRight(bool right);
|
||||
void pickNextMood();
|
||||
void holdMood(Mood mood, int ms);
|
||||
|
||||
AppSettings *m_settings;
|
||||
QWindow *m_window = nullptr;
|
||||
QTimer m_tickTimer;
|
||||
QTimer m_moodTimer;
|
||||
QTimer m_cursorTimer;
|
||||
QElapsedTimer m_clock;
|
||||
qint64 m_lastTickMs = 0;
|
||||
|
||||
qreal m_x = 120;
|
||||
qreal m_screenWidth = 1440;
|
||||
qreal m_catWidth = 120;
|
||||
bool m_facingRight = true;
|
||||
Mood m_mood = Mood::Walking;
|
||||
bool m_paused = false;
|
||||
bool m_dragging = false;
|
||||
bool m_hovered = false;
|
||||
bool m_frozen = false;
|
||||
QPointF m_look;
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
#include <QApplication>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QQuickStyle>
|
||||
#include <QQuickWindow>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
#include "appsettings.h"
|
||||
#include "catcontroller.h"
|
||||
#include "traycontroller.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication::setOrganizationName("workcat");
|
||||
QCoreApplication::setOrganizationDomain("workcat.app");
|
||||
QCoreApplication::setApplicationName("Workcat");
|
||||
QApplication::setQuitOnLastWindowClosed(false);
|
||||
|
||||
// QApplication (not QGuiApplication) so QSystemTrayIcon can show a menu.
|
||||
QApplication app(argc, argv);
|
||||
QQuickStyle::setStyle("Basic");
|
||||
|
||||
AppSettings settings;
|
||||
CatController cat(&settings);
|
||||
TrayController tray(&cat);
|
||||
|
||||
if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) {
|
||||
qWarning("Workcat: Wayland does not let a client place its own window. "
|
||||
"Run with QT_QPA_PLATFORM=xcb for the cat to walk.");
|
||||
}
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
engine.setInitialProperties({
|
||||
{ "cat", QVariant::fromValue(&cat) },
|
||||
{ "settings", QVariant::fromValue(&settings) },
|
||||
});
|
||||
QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &app,
|
||||
[] { QCoreApplication::exit(1); }, Qt::QueuedConnection);
|
||||
engine.loadFromModule("Workcat", "Main");
|
||||
|
||||
if (auto *root = qobject_cast<QQuickWindow *>(engine.rootObjects().value(0))) {
|
||||
cat.setWindow(root);
|
||||
QObject::connect(&tray, &TrayController::settingsRequested, root,
|
||||
[root] { QMetaObject::invokeMethod(root, "openSettings"); });
|
||||
}
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#include "platforminfo.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
|
||||
PlatformInfo::PlatformInfo(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_name(QGuiApplication::platformName())
|
||||
, m_bypass(qEnvironmentVariableIntValue("WORKCAT_X11_BYPASS") != 0)
|
||||
, m_opaque(qEnvironmentVariableIntValue("WORKCAT_OPAQUE") != 0)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
#include <QString>
|
||||
|
||||
// Small facts about the host that QML needs in order to pick window flags.
|
||||
class PlatformInfo : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_NAMED_ELEMENT(Platform)
|
||||
QML_SINGLETON
|
||||
|
||||
Q_PROPERTY(QString name READ name CONSTANT)
|
||||
Q_PROPERTY(bool isX11 READ isX11 CONSTANT)
|
||||
Q_PROPERTY(bool isWayland READ isWayland CONSTANT)
|
||||
Q_PROPERTY(bool bypassWindowManager READ bypassWindowManager CONSTANT)
|
||||
Q_PROPERTY(bool opaqueBackground READ opaqueBackground CONSTANT)
|
||||
|
||||
public:
|
||||
explicit PlatformInfo(QObject *parent = nullptr);
|
||||
|
||||
QString name() const { return m_name; }
|
||||
bool isX11() const { return m_name == QLatin1String("xcb"); }
|
||||
bool isWayland() const { return m_name.startsWith(QLatin1String("wayland")); }
|
||||
|
||||
// WORKCAT_X11_BYPASS=1 turns the cat into an override-redirect window.
|
||||
// Useful on window managers that refuse to keep a Qt::Tool window on top.
|
||||
bool bypassWindowManager() const { return m_bypass; }
|
||||
|
||||
// WORKCAT_OPAQUE=1 paints a solid background instead of relying on a
|
||||
// compositor. Without compositing X11 renders transparency as black.
|
||||
bool opaqueBackground() const { return m_opaque; }
|
||||
|
||||
private:
|
||||
QString m_name;
|
||||
bool m_bypass = false;
|
||||
bool m_opaque = false;
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
#include "traycontroller.h"
|
||||
#include "catcontroller.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
TrayController::TrayController(CatController *cat, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_cat(cat)
|
||||
{
|
||||
QMenu *tricks = m_menu.addMenu(tr("Tricks"));
|
||||
const struct { const char *label; CatController::Mood mood; } entries[] = {
|
||||
{ QT_TR_NOOP("Sit"), CatController::Mood::Sitting },
|
||||
{ QT_TR_NOOP("Groom"), CatController::Mood::Grooming },
|
||||
{ QT_TR_NOOP("Stretch"), CatController::Mood::Stretching },
|
||||
{ QT_TR_NOOP("Dash"), CatController::Mood::Running },
|
||||
{ QT_TR_NOOP("Nap"), CatController::Mood::Sleeping },
|
||||
};
|
||||
for (const auto &e : entries) {
|
||||
const auto mood = e.mood;
|
||||
tricks->addAction(tr(e.label), this, [this, mood] { m_cat->doTrick(mood); });
|
||||
}
|
||||
|
||||
m_pauseAction = m_menu.addAction(tr("Let the cat sleep"));
|
||||
m_pauseAction->setCheckable(true);
|
||||
connect(m_pauseAction, &QAction::toggled, m_cat, &CatController::setPaused);
|
||||
connect(m_cat, &CatController::pausedChanged, this, [this] {
|
||||
m_pauseAction->setChecked(m_cat->paused());
|
||||
});
|
||||
|
||||
m_menu.addAction(tr("Settings…"), this, &TrayController::settingsRequested);
|
||||
m_menu.addSeparator();
|
||||
m_menu.addAction(tr("Quit Workcat"), qApp, &QCoreApplication::quit);
|
||||
|
||||
m_tray.setIcon(makeIcon());
|
||||
m_tray.setToolTip(tr("Workcat"));
|
||||
m_tray.setContextMenu(&m_menu);
|
||||
|
||||
// Not every X11 desktop runs a status-notifier host. Without a tray the
|
||||
// cat itself is the only entry point, so say so instead of failing mutely.
|
||||
if (QSystemTrayIcon::isSystemTrayAvailable())
|
||||
m_tray.show();
|
||||
else
|
||||
qWarning("Workcat: no system tray on this desktop. Right-click the cat for settings.");
|
||||
}
|
||||
|
||||
QIcon TrayController::makeIcon()
|
||||
{
|
||||
QPixmap pm(44, 44);
|
||||
pm.fill(Qt::transparent);
|
||||
QPainter p(&pm);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(Qt::black);
|
||||
|
||||
QPainterPath path;
|
||||
path.addRoundedRect(QRectF(5, 12, 34, 28), 14, 14);
|
||||
path.moveTo(8, 22); path.lineTo(9, 5); path.lineTo(21, 14); path.closeSubpath();
|
||||
path.moveTo(36, 22); path.lineTo(35, 5); path.lineTo(23, 14); path.closeSubpath();
|
||||
p.drawPath(path);
|
||||
|
||||
p.setBrush(Qt::white);
|
||||
p.drawEllipse(QRectF(13, 22, 6, 7));
|
||||
p.drawEllipse(QRectF(25, 22, 6, 7));
|
||||
|
||||
QIcon icon(pm);
|
||||
icon.setIsMask(true);
|
||||
return icon;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QMenu>
|
||||
|
||||
class CatController;
|
||||
|
||||
class TrayController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TrayController(CatController *cat, QObject *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void settingsRequested();
|
||||
|
||||
private:
|
||||
static QIcon makeIcon();
|
||||
|
||||
CatController *m_cat;
|
||||
QSystemTrayIcon m_tray;
|
||||
QMenu m_menu;
|
||||
QAction *m_pauseAction = nullptr;
|
||||
};
|
||||
Reference in new issue
Block a user