From dbea569c88de01af838c19e47019c6cafb4d1e95 Mon Sep 17 00:00:00 2001 From: collecting Date: Wed, 29 Oct 2025 05:25:07 +0000 Subject: [PATCH] feat: Controller Overlay --- src/citron/controller_overlay.cpp | 123 ++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 src/citron/controller_overlay.cpp diff --git a/src/citron/controller_overlay.cpp b/src/citron/controller_overlay.cpp new file mode 100644 index 000000000..c843669bb --- /dev/null +++ b/src/citron/controller_overlay.cpp @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "citron/controller_overlay.h" +#include "citron/configuration/configure_input_player_widget.h" +#include "citron/main.h" +#include "core/core.h" +#include "hid_core/hid_core.h" + +#include +#include +#include +#include +#include +#include // Required for Wayland dragging +#include + +namespace { +// Helper to get the active controller for Player 1 +Core::HID::EmulatedController* GetPlayer1Controller(Core::System* system) { + if (!system) return nullptr; + Core::HID::HIDCore& hid_core = system->HIDCore(); + auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); + if (handheld && handheld->IsConnected()) { + return handheld; + } + return hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); +} +} + +ControllerOverlay::ControllerOverlay(GMainWindow* parent) + : QWidget(parent, Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint), + main_window(parent) { + + setAttribute(Qt::WA_TranslucentBackground); + + auto* layout = new QGridLayout(this); + setLayout(layout); + // Set margins to 0 so the controller can go right to the edge of the resizable window + layout->setContentsMargins(0, 0, 0, 0); + + // Create the widget that draws the controller and make it transparent + controller_widget = new PlayerControlPreview(this); + controller_widget->setAttribute(Qt::WA_TranslucentBackground); + + // Disable the raw joystick (deadzone) visualization + controller_widget->SetRawJoystickVisible(false); + + // Allow the widget to expand and shrink with the window + controller_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + layout->addWidget(controller_widget, 0, 0); + + // Add a size grip for resizing + size_grip = new QSizeGrip(this); + layout->addWidget(size_grip, 0, 0, Qt::AlignBottom | Qt::AlignRight); + + // Start the timer for continuous updates + connect(&update_timer, &QTimer::timeout, this, &ControllerOverlay::UpdateControllerState); + update_timer.start(16); // ~60 FPS + + // Set a minimum size and a default starting size + setMinimumSize(225, 175); + resize(450, 350); +} + +ControllerOverlay::~ControllerOverlay() = default; + +void ControllerOverlay::UpdateControllerState() { + Core::System* system = main_window->GetSystem(); + Core::HID::EmulatedController* controller = GetPlayer1Controller(system); + if (controller_widget && controller) { + controller_widget->SetController(controller); + controller_widget->UpdateInput(); + } +} + +// The paint event is now empty, which makes the background fully transparent. +void ControllerOverlay::paintEvent(QPaintEvent* event) { + Q_UNUSED(event); + // Intentionally left blank to achieve a fully transparent window background. +} + +// These functions handle dragging the frameless window +void ControllerOverlay::mousePressEvent(QMouseEvent* event) { + if (event->button() == Qt::LeftButton && !size_grip->geometry().contains(event->pos())) { +#if defined(Q_OS_LINUX) + // Use system move on Wayland/Linux for proper dragging + if (windowHandle()) { + windowHandle()->startSystemMove(); + } +#else + // Original dragging implementation for other platforms (Windows, etc.) + is_dragging = true; + drag_start_pos = event->globalPosition().toPoint() - this->pos(); +#endif + event->accept(); + } +} + +void ControllerOverlay::mouseMoveEvent(QMouseEvent* event) { +#if !defined(Q_OS_LINUX) + if (is_dragging) { + move(event->globalPosition().toPoint() - drag_start_pos); + event->accept(); + } +#else + // On Linux, the window manager handles the move, so we do nothing here. + Q_UNUSED(event); +#endif +} + +void ControllerOverlay::mouseReleaseEvent(QMouseEvent* event) { + if (event->button() == Qt::LeftButton) { + is_dragging = false; + event->accept(); + } +} + +void ControllerOverlay::resizeEvent(QResizeEvent* event) { + QWidget::resizeEvent(event); + // This ensures the layout and its widgets (like the size grip) are correctly repositioned on resize. + layout()->update(); +}