diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 20971828a..8be0aff56 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -174,6 +174,8 @@ add_library(citra_qt STATIC EXCLUDE_FROM_ALL multiplayer/state.cpp multiplayer/state.h multiplayer/validation.h + notification_led.cpp + notification_led.h precompiled_headers.h qt_image_interface.cpp qt_image_interface.h diff --git a/src/citra_qt/citra_qt.cpp b/src/citra_qt/citra_qt.cpp index 9bb0b2e34..8c2d373c7 100644 --- a/src/citra_qt/citra_qt.cpp +++ b/src/citra_qt/citra_qt.cpp @@ -352,6 +352,8 @@ GMainWindow::GMainWindow(Core::System& system_) Camera::RegisterFactory("image", std::make_unique()); Camera::RegisterFactory("qt", std::make_unique(qt_cameras)); + system.RegisterInfoLEDColorChanged([this]() { emit InfoLEDColorChanged(); }); + LoadTranslation(); Pica::g_debug_context = Pica::DebugContext::Construct(); @@ -606,6 +608,20 @@ void GMainWindow::InitializeWidgets() { statusBar()->addPermanentWidget(multiplayer_state->GetStatusText()); statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon()); + QFrame* sep = new QFrame(this); + sep->setFrameShape(QFrame::VLine); + sep->setFrameShadow(QFrame::Sunken); + sep->setFixedHeight(16); + statusBar()->addPermanentWidget(sep); + + notification_led = new LedWidget(); + notification_led->setToolTip(tr("Emulated notification LED")); + statusBar()->addPermanentWidget(notification_led); + connect(this, &GMainWindow::InfoLEDColorChanged, this, [this] { + auto led_color = system.GetInfoLEDColor(); + notification_led->setColor(QColor(led_color.r(), led_color.g(), led_color.b())); + }); + statusBar()->setVisible(true); // Removes an ugly inner border from the status bar widgets under Linux @@ -1600,6 +1616,7 @@ void GMainWindow::ShutdownGame() { emu_speed_label->setVisible(false); game_fps_label->setVisible(false); emu_frametime_label->setVisible(false); + notification_led->setColor(QColor(0, 0, 0)); UpdateSaveStates(); diff --git a/src/citra_qt/citra_qt.h b/src/citra_qt/citra_qt.h index cdf9eaff6..f0092cc19 100644 --- a/src/citra_qt/citra_qt.h +++ b/src/citra_qt/citra_qt.h @@ -21,6 +21,7 @@ #include #include "citra_qt/compatibility_list.h" #include "citra_qt/hotkeys.h" +#include "citra_qt/notification_led.h" #include "citra_qt/user_data_migration.h" #include "core/core.h" #include "core/savestate.h" @@ -147,6 +148,7 @@ signals: void CIAInstallReport(Service::AM::InstallStatus status, QString filepath); void CompressFinished(bool is_compress, bool success); void CIAInstallFinished(); + void InfoLEDColorChanged(); // Signal that tells widgets to update icons to use the current theme void UpdateThemedIcons(); @@ -364,6 +366,8 @@ private: MultiplayerState* multiplayer_state = nullptr; + LedWidget* notification_led = nullptr; + // Created before `config` to ensure that emu data directory // isn't created before the check is performed UserDataMigrator user_data_migrator; diff --git a/src/citra_qt/notification_led.cpp b/src/citra_qt/notification_led.cpp new file mode 100644 index 000000000..e7d9e0d33 --- /dev/null +++ b/src/citra_qt/notification_led.cpp @@ -0,0 +1,87 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include "citra_qt/notification_led.h" + +LedWidget::LedWidget(QWidget* parent) : QWidget(parent), color(0, 0, 0) { + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); +} + +QSize LedWidget::sizeHint() const { + return QSize(16, 16); +} + +QSize LedWidget::minimumSizeHint() const { + return QSize(16, 16); +} + +void LedWidget::setColor(const QColor& _color) { + if (color == _color) + return; + + color = _color; + update(); +} + +QColor LedWidget::lerpColor(const QColor& a, const QColor& b, float t) { + t = std::clamp(t, 0.0f, 1.0f); + + return QColor(int(a.red() + (b.red() - a.red()) * t), + int(a.green() + (b.green() - a.green()) * t), + int(a.blue() + (b.blue() - a.blue()) * t)); +} + +QColor LedWidget::blendLedColor(int r, int g, int b) const { + // Default "off" color + const QColor off_color(64, 64, 64); + + // If completely off, just show gray and skip further calculations + if (r == 0 && g == 0 && b == 0) + return off_color; + + // Normalize lit color so hue stays pure + int max_c = std::max({r, g, b}); + QColor lit_color((r * 255) / max_c, (g * 255) / max_c, (b * 255) / max_c); + + // Convert PWM duty to perceived brightness. + // This gives better results as LED RGB values + // are not linear. + constexpr float gamma = 2.4f; + float pwm = max_c / 255.0; + float t = std::powf(pwm, 1.f / gamma); + + return lerpColor(off_color, lit_color, t * 0.8f); +} + +void LedWidget::paintEvent(QPaintEvent*) { + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + + QRectF rect = this->rect().adjusted(0, 2, 0, -2); + + qreal size = std::min(rect.width(), rect.height()); + QRectF circle((rect.center().x() - size / 2.f) - 2, rect.center().y() - size / 2.f, size, size); + + QPointF center = circle.center(); + qreal radius = circle.width() / 2.f; + + QColor base = blendLedColor(color.red(), color.green(), color.blue()); + + QRadialGradient g(center, radius); + + QColor inner = base.lighter(135); + QColor outer = base.darker(125); + + g.setColorAt(0.f, inner); + g.setColorAt(0.7f, base); + g.setColorAt(1.f, outer); + + p.setPen(Qt::NoPen); + p.setBrush(g); + p.drawEllipse(circle); +} diff --git a/src/citra_qt/notification_led.h b/src/citra_qt/notification_led.h new file mode 100644 index 000000000..3a139a202 --- /dev/null +++ b/src/citra_qt/notification_led.h @@ -0,0 +1,30 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +class LedWidget : public QWidget { + Q_OBJECT + +public: + explicit LedWidget(QWidget* parent = nullptr); + + QSize sizeHint() const override; + QSize minimumSizeHint() const override; + + void setColor(const QColor& color); + +protected: + void paintEvent(QPaintEvent* event) override; + +private: + QColor blendLedColor(int r, int g, int b) const; + static QColor lerpColor(const QColor& a, const QColor& b, float t); + +private: + QColor color; +};