@@ -32,9 +32,16 @@
#include " DolphinQt/Resources.h"
#include " DolphinQt/Settings.h"
#include " InputCommon/ControllerInterface/ControllerInterface.h"
#include " VideoCommon/RenderBase.h"
#include " VideoCommon/VideoConfig.h"
#ifdef _WIN32
#include < WinUser.h>
#include < windef.h>
#endif
RenderWidget::RenderWidget (QWidget* parent) : QWidget(parent)
{
setWindowTitle (QStringLiteral (" Dolphin" ));
@@ -79,7 +86,10 @@ RenderWidget::RenderWidget(QWidget* parent) : QWidget(parent)
connect (&Settings::Instance (), &Settings::HideCursorChanged, this ,
&RenderWidget::OnHideCursorChanged);
connect (&Settings::Instance (), &Settings::LockCursorChanged, this ,
&RenderWidget::OnLockCursorChanged);
OnHideCursorChanged ();
OnLockCursorChanged ();
connect (&Settings::Instance (), &Settings::KeepWindowOnTopChanged, this ,
&RenderWidget::OnKeepOnTopChanged);
OnKeepOnTopChanged (Settings::Instance ().IsKeepWindowOnTopEnabled ());
@@ -128,7 +138,33 @@ void RenderWidget::dropEvent(QDropEvent* event)
void RenderWidget::OnHideCursorChanged ()
{
setCursor (Settings::Instance ().GetHideCursor () ? Qt::BlankCursor : Qt::ArrowCursor);
UpdateCursor ();
}
void RenderWidget::OnLockCursorChanged ()
{
SetCursorLocked (false );
UpdateCursor ();
}
// Calling this at any time will set the cursor (image) to the correct state
void RenderWidget::UpdateCursor ()
{
if (!Settings::Instance ().GetLockCursor ())
{
// Only hide if the cursor is automatically locking (it will hide on lock).
// "Unhide" the cursor if we lost focus, otherwise it will disappear when hovering
// on top of the game window in the background
const bool keep_on_top = (windowFlags () & Qt::WindowStaysOnTopHint) != 0 ;
const bool should_hide =
Settings::Instance ().GetHideCursor () &&
(keep_on_top || SConfig::GetInstance ().m_BackgroundInput || isActiveWindow ());
setCursor (should_hide ? Qt::BlankCursor : Qt::ArrowCursor);
}
else
{
setCursor ((m_cursor_locked && Settings::Instance ().GetHideCursor ()) ? Qt::BlankCursor :
Qt::ArrowCursor);
}
}
void RenderWidget::OnKeepOnTopChanged (bool top)
@@ -138,14 +174,22 @@ void RenderWidget::OnKeepOnTopChanged(bool top)
setWindowFlags (top ? windowFlags () | Qt::WindowStaysOnTopHint :
windowFlags () & ~Qt::WindowStaysOnTopHint);
m_dont_lock_cursor_on_show = true ;
if (was_visible)
show ();
m_dont_lock_cursor_on_show = false ;
UpdateCursor ();
}
void RenderWidget::HandleCursorTimer ()
{
if (isActiveWindow ())
if (!isActiveWindow ())
return ;
if (!Settings::Instance ().GetLockCursor () || m_cursor_locked)
{
setCursor (Qt::BlankCursor);
}
}
void RenderWidget::showFullScreen ()
@@ -159,6 +203,138 @@ void RenderWidget::showFullScreen()
emit SizeChanged (width () * dpr, height () * dpr);
}
// Lock the cursor within the window/widget internal borders, including the aspect ratio if wanted
void RenderWidget::SetCursorLocked (bool locked, bool follow_aspect_ratio)
{
// It seems like QT doesn't scale the window frame correctly with some DPIs
// so it might happen that the locked cursor can be on the frame of the window,
// being able to resize it, but that is a minor problem.
// As a hack, if necessary, we could always scale down the size by 2 pixel, to a min of 1 given
// that the size can be 0 already. We probably shouldn't scale axes already scaled by aspect ratio
QRect render_rect = geometry ();
if (parentWidget ())
{
render_rect.moveTopLeft (parentWidget ()->mapToGlobal (render_rect.topLeft ()));
}
auto scale = devicePixelRatioF (); // Seems to always be rounded on Win. Should we round results?
QPoint screen_offset = QPoint (0 , 0 );
if (window ()->windowHandle () && window ()->windowHandle ()->screen ())
{
screen_offset = window ()->windowHandle ()->screen ()->geometry ().topLeft ();
}
render_rect.moveTopLeft (((render_rect.topLeft () - screen_offset) * scale) + screen_offset);
render_rect.setSize (render_rect.size () * scale);
if (follow_aspect_ratio)
{
// TODO: SetCursorLocked() should be re-called every time this value is changed?
// This might cause imprecisions of one pixel (but it won't cause the cursor to go over borders)
Common::Vec2 aspect_ratio = g_controller_interface.GetWindowInputScale ();
if (aspect_ratio.x > 1 .f )
{
const float new_half_width = float (render_rect.width ()) / (aspect_ratio.x * 2 .f );
// Only ceil if it was >= 0.25
const float ceiled_new_half_width = std::ceil (std::round (new_half_width * 2 .f ) / 2 .f );
const int x_center = render_rect.center ().x ();
// Make a guess on which one to floor and ceil.
// For more precision, we should have kept the rounding point scale from above as well.
render_rect.setLeft (x_center - std::floor (new_half_width));
render_rect.setRight (x_center + ceiled_new_half_width);
}
if (aspect_ratio.y > 1 .f )
{
const float new_half_height = render_rect.height () / (aspect_ratio.y * 2 .f );
const float ceiled_new_half_height = std::ceil (std::round (new_half_height * 2 .f ) / 2 .f );
const int y_center = render_rect.center ().y ();
render_rect.setTop (y_center - std::floor (new_half_height));
render_rect.setBottom (y_center + ceiled_new_half_height);
}
}
if (locked)
{
#ifdef _WIN32
RECT rect;
rect.left = render_rect.left ();
rect.right = render_rect.right ();
rect.top = render_rect.top ();
rect.bottom = render_rect.bottom ();
if (ClipCursor (&rect))
#else
// TODO: implement on other platforms. Probably XGrabPointer on Linux.
// The setting is hidden in the UI if not implemented
if (false )
#endif
{
m_cursor_locked = true ;
if (Settings::Instance ().GetHideCursor ())
{
setCursor (Qt::BlankCursor);
}
Host::GetInstance ()->SetRenderFullFocus (true );
}
}
else
{
#ifdef _WIN32
ClipCursor (nullptr );
#endif
if (m_cursor_locked)
{
m_cursor_locked = false ;
if (!Settings::Instance ().GetLockCursor ())
{
return ;
}
// Center the mouse in the window if it's still active
// Leave it where it was otherwise, e.g. a prompt has opened or we alt tabbed.
if (isActiveWindow ())
{
cursor ().setPos (render_rect.left () + render_rect.width () / 2 ,
render_rect.top () + render_rect.height () / 2 );
}
// Show the cursor or the user won't know the mouse is now unlocked
setCursor (Qt::ArrowCursor);
Host::GetInstance ()->SetRenderFullFocus (false );
}
}
}
void RenderWidget::SetCursorLockedOnNextActivation (bool locked)
{
if (Settings::Instance ().GetLockCursor ())
{
m_lock_cursor_on_next_activation = locked;
return ;
}
m_lock_cursor_on_next_activation = false ;
}
void RenderWidget::SetWaitingForMessageBox (bool waiting_for_message_box)
{
if (m_waiting_for_message_box == waiting_for_message_box)
{
return ;
}
m_waiting_for_message_box = waiting_for_message_box;
if (!m_waiting_for_message_box && m_lock_cursor_on_next_activation && isActiveWindow ())
{
if (Settings::Instance ().GetLockCursor ())
{
SetCursorLocked (true );
}
m_lock_cursor_on_next_activation = false ;
}
}
bool RenderWidget::event (QEvent* event)
{
PassEventToImGui (event);
@@ -178,23 +354,67 @@ bool RenderWidget::event(QEvent* event)
break ;
}
// Needed in case a new window open and it moves the mouse
case QEvent::WindowBlocked:
SetCursorLocked (false );
break ;
case QEvent::MouseButtonPress:
if (! Settings::Instance (). GetHideCursor () && isActiveWindow ())
if (isActiveWindow ())
{
setCursor (Qt::ArrowCursor);
m_mouse_timer->start (MOUSE_HIDE_DELAY);
// Lock the cursor with any mouse button click (behave the same as window focus change).
// This event is occasionally missed because isActiveWindow is laggy
if (Settings::Instance ().GetLockCursor () && event->type () == QEvent::MouseButtonPress)
{
SetCursorLocked (true );
}
// Unhide on movement
if (!Settings::Instance ().GetHideCursor ())
{
setCursor (Qt::ArrowCursor);
m_mouse_timer->start (MOUSE_HIDE_DELAY);
}
}
break ;
case QEvent::WinIdChange:
emit HandleChanged (reinterpret_cast <void *>(winId ()));
break ;
case QEvent::Show:
// Don't do if "stay on top" changed (or was true)
if (Settings::Instance ().GetLockCursor () && Settings::Instance ().GetHideCursor () &&
!m_dont_lock_cursor_on_show)
{
// Auto lock when this window is shown (it was hidden)
if (isActiveWindow ())
SetCursorLocked (true );
else
SetCursorLockedOnNextActivation ();
}
break ;
// Note that this event in Windows is not always aligned to the window that is highlighted,
// it's the window that has keyboard and mouse focus
case QEvent::WindowActivate:
if (SConfig::GetInstance ().m_PauseOnFocusLost && Core::GetState () == Core::State::Paused)
Core::SetState (Core::State::Running);
UpdateCursor ();
// Avoid "race conditions" with message boxes
if (m_lock_cursor_on_next_activation && !m_waiting_for_message_box)
{
if (Settings::Instance ().GetLockCursor ())
{
SetCursorLocked (true );
}
m_lock_cursor_on_next_activation = false ;
}
emit FocusChanged (true );
break ;
case QEvent::WindowDeactivate:
SetCursorLocked (false );
UpdateCursor ();
if (SConfig::GetInstance ().m_PauseOnFocusLost && Core::GetState () == Core::State::Running)
{
// If we are declared as the CPU thread, it means that the real CPU thread is waiting
@@ -206,8 +426,13 @@ bool RenderWidget::event(QEvent* event)
emit FocusChanged (false );
break ;
case QEvent::Move:
SetCursorLocked (m_cursor_locked);
break ;
case QEvent::Resize:
{
SetCursorLocked (m_cursor_locked);
const QResizeEvent* se = static_cast <QResizeEvent*>(event);
QSize new_size = se->size ();
@@ -218,14 +443,18 @@ bool RenderWidget::event(QEvent* event)
emit SizeChanged (new_size.width () * dpr, new_size.height () * dpr);
break ;
}
// Happens when we add/remove the widget from the main window instead of the dedicated one
case QEvent::ParentChange:
SetCursorLocked (false );
break ;
case QEvent::WindowStateChange:
// Lock the mouse again when fullscreen changes (we might have missed some events)
SetCursorLocked (m_cursor_locked || (isFullScreen () && Settings::Instance ().GetLockCursor ()));
emit StateChanged (isFullScreen ());
break ;
case QEvent::Close:
emit Closed ();
break ;
default :
break ;
}
return QWidget::event (event);
}