You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Framework/AzQtComponents/AzQtComponents/Components/InteractiveWindowGeometryCh...

390 lines
13 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzQtComponents/Components/InteractiveWindowGeometryChanger.h>
#include <AzQtComponents/Utilities/QtWindowUtilities.h>
#include <QApplication>
#include <QKeyEvent>
#include <QDebug>
namespace AzQtComponents
{
enum
{
ChangeIncrement = 10 // How many pixels are added each time we move or resize
};
InteractiveWindowGeometryChanger::InteractiveWindowGeometryChanger(QWindow* target, QObject *parent)
: QObject(parent)
, m_targetWindow(target)
, m_originalCursorPos(target ? target->mapFromGlobal(QCursor::pos()) : QPoint())
{
if (m_targetWindow)
{
qApp->setOverrideCursor(Qt::SizeAllCursor);
qApp->installEventFilter(this);
m_targetWindow->setMouseGrabEnabled(true);
// Dismiss the changer if someone does intrusive stuff programatically, like maximizing the window
connect(target, &QWindow::visibilityChanged, this, &QObject::deleteLater);
connect(target, &QWindow::windowStateChanged, this, &QObject::deleteLater);
}
else
{
// this doesn't happen
qWarning() << Q_FUNC_INFO << "target window is null";
deleteLater();
Q_ASSERT(false);
}
}
InteractiveWindowGeometryChanger::~InteractiveWindowGeometryChanger()
{
m_targetWindow->setMouseGrabEnabled(false);
qApp->restoreOverrideCursor();
// Restore original cursor position
restoreCursorPosition();
}
void InteractiveWindowGeometryChanger::restoreCursorPosition()
{
if (m_targetWindow && m_restoreCursorAtExit)
{
AzQtComponents::SetCursorPos(m_targetWindow->mapToGlobal(m_originalCursorPos));
}
}
bool InteractiveWindowGeometryChanger::eventFilter(QObject* watched, QEvent* ev)
{
if (ev->type() == QEvent::KeyPress)
{
if (!m_targetWindow)
{
// The window was deleted by external factors, dismiss our changer
deleteLater();
return true;
}
auto keyPressEv = static_cast<QKeyEvent*>(ev);
switch (keyPressEv->key())
{
case Qt::Key_Left:
case Qt::Key_Up:
case Qt::Key_Right:
case Qt::Key_Down:
handleKeyPress(keyPressEv);
return true;
case Qt::Key_Escape:
case Qt::Key_Enter:
case Qt::Key_Return:
deleteLater();
return true; // consume it
default:
return QObject::eventFilter(watched, ev);
}
}
else if (ev->type() == QEvent::MouseButtonPress ||
ev->type() == QEvent::MouseButtonRelease ||
ev->type() == QEvent::MouseButtonDblClick)
{
// Any mouse click dismisses the geometry changer
deleteLater();
// We don't want to restore the cursor pos, the user just clicked somewhere, would be odd to move it
m_restoreCursorAtExit = false;
}
else if (ev->type() == QEvent::MouseMove)
{
handleMouseMove(static_cast<QMouseEvent*>(ev));
}
return QObject::eventFilter(watched, ev);
}
InteractiveWindowResizer::InteractiveWindowResizer(QWindow* target, QObject* parent)
: InteractiveWindowGeometryChanger(target, parent)
{
}
InteractiveWindowResizer::SideToResize InteractiveWindowResizer::keyToSide(int key) const
{
switch (key)
{
case Qt::Key_Left:
return LeftSide;
case Qt::Key_Up:
return TopSide;
case Qt::Key_Right:
return RightSide;
case Qt::Key_Down:
return BottomSide;
default:
return NoneSide;
}
}
bool InteractiveWindowResizer::sideIsVertical(SidesToResize side) const
{
return side & (TopSide | BottomSide);
}
bool InteractiveWindowResizer::sideIsHorizontal(SidesToResize side) const
{
return side & (LeftSide | RightSide);
}
bool InteractiveWindowResizer::sideIsCorner(SidesToResize side) const
{
return sideIsHorizontal(side) && sideIsVertical(side);
}
void InteractiveWindowResizer::handleKeyPress(QKeyEvent* ev)
{
if (m_sideToResize == NoneSide)
{
// First arrow press just determines which side we're going to resize
m_sideToResize = SidesToResize(keyToSide(ev->key()));
updateCursor();
return;
}
else if (!sideIsCorner(m_sideToResize))
{
// When resizing left or right and the user presses up or down then we start resizing
// a corner instead
if (sideIsHorizontal(m_sideToResize) && sideIsVertical(keyToSide(ev->key())))
{
// We're reisizing left or right, but user pressed up or down, so we go to corner instead
m_sideToResize |= keyToSide(ev->key());
updateCursor();
return;
}
else if (sideIsVertical(m_sideToResize) && sideIsHorizontal(keyToSide(ev->key())))
{
// We're resizing top or bottom, but user pressed left or right, so we go to corner instead
m_sideToResize |= keyToSide(ev->key());
updateCursor();
return;
}
}
// Now we do the actual resizing:
QRect geometry = m_targetWindow->geometry();
// const QRect originalGeometry = geometry;
int dx1 = 0, dx2 = 0, dy1 = 0, dy2 = 0;
bool horizontal = false;
int signedness = 0;
switch (ev->key())
{
case Qt::Key_Left:
horizontal = true;
signedness = -1;
break;
case Qt::Key_Right:
horizontal = true;
signedness = 1;
break;
case Qt::Key_Down:
horizontal = false;
signedness = 1;
break;
case Qt::Key_Up:
horizontal = false;
signedness = -1;
break;
default:
break;
}
if (horizontal)
{
if (m_sideToResize & LeftSide)
{
dx1 = ChangeIncrement * signedness;
}
else if (m_sideToResize & RightSide)
{
dx2 = ChangeIncrement * signedness;
}
}
else
{
if (m_sideToResize & TopSide)
{
dy1 = ChangeIncrement * signedness;
}
else if (m_sideToResize & BottomSide)
{
dy2 = ChangeIncrement * signedness;
}
}
geometry.adjust(dx1, dy1, dx2, dy2);
if (geometry.height() >= m_targetWindow->minimumHeight() &&
geometry.width() >= m_targetWindow->minimumWidth() &&
geometry.height() <= m_targetWindow->maximumHeight() &&
geometry.width() <= m_targetWindow->maximumWidth())
{
EnsureGeometryWithinScreenTop(geometry);
m_targetWindow->setGeometry(geometry);
updateCursor(); // Position changed, move cursor to border again
}
}
void InteractiveWindowResizer::handleMouseMove(QMouseEvent* ev)
{
if (m_sideToResize == NoneSide)
{
// First arrow press just determines which side we're going to resize, so nothing will happen here
return;
}
// Now we do the actual resizing:
QRect geometry = m_targetWindow->geometry();
if (m_sideToResize & LeftSide)
{
geometry.setLeft(ev->globalX() - 1);
}
if (m_sideToResize & RightSide)
{
geometry.setRight(ev->globalX());
}
if (m_sideToResize & TopSide)
{
geometry.setTop(ev->globalY() - 1);
}
if (m_sideToResize & BottomSide)
{
geometry.setBottom(ev->globalY());
}
if (geometry.height() >= m_targetWindow->minimumHeight() &&
geometry.width() >= m_targetWindow->minimumWidth() &&
geometry.height() <= m_targetWindow->maximumHeight() &&
geometry.width() <= m_targetWindow->maximumWidth())
{
EnsureGeometryWithinScreenTop(geometry);
m_targetWindow->setGeometry(geometry);
updateCursor(); // Position changed, move cursor to border again
}
}
void InteractiveWindowResizer::updateCursor()
{
// When pressing "Size" in the context menu, the first arrow key press will change the cursor
// shape and position it in one of the edges, or corner
const auto s = m_sideToResize; // Less verbose
QPoint newPos = m_targetWindow->position();
const int x = newPos.x();
const int y = newPos.y();
const int width = m_targetWindow->width();
const int height = m_targetWindow->height();
// Restore our previous cursor override so there's only one override stacked
qApp->restoreOverrideCursor();
// The magic +1/-1 bellow is because of QTBUG-58590, shape isn't set otherwise for modal windows
if (((s & LeftSide) && (s & TopSide)) ||
((s & BottomSide) && (s & RightSide))) // Corner
{
qApp->setOverrideCursor(Qt::SizeFDiagCursor);
newPos.setX((s & LeftSide) ? (x + 1) : (x + width - 1));
newPos.setY((s & TopSide) ? (y + 1) : (y + height - 1));
}
else if (((s & LeftSide) && (s & BottomSide)) ||
((s & RightSide) && (s & TopSide))) // Corner
{
qApp->setOverrideCursor(Qt::SizeBDiagCursor);
newPos.setX((s & LeftSide) ? (x + 1) : (x + width - 1));
newPos.setY((s & TopSide) ? (y + 1) : (y + height - 1));
}
else if (s & (LeftSide | RightSide))
{
qApp->setOverrideCursor(Qt::SizeHorCursor);
newPos.setY(y + height / 2);
newPos.setX((s & LeftSide) ? (x + 1) : (x + width - 1));
}
else if (s & (TopSide | BottomSide))
{
qApp->setOverrideCursor(Qt::SizeVerCursor);
newPos.setX(x + width / 2);
newPos.setY((s & TopSide) ? (y + 1) : (y + height - 1));
}
AzQtComponents::SetCursorPos(newPos);
}
InteractiveWindowMover::InteractiveWindowMover(QWindow* target, QObject* parent)
: InteractiveWindowGeometryChanger(target, parent)
{
}
void InteractiveWindowMover::handleKeyPress(QKeyEvent* ev)
{
m_arrowAlreadyPressed = true;
QPoint offset(0, 0);
switch (ev->key())
{
case Qt::Key_Left:
offset += QPoint(-ChangeIncrement, 0);
break;
case Qt::Key_Up:
offset += QPoint(0, -ChangeIncrement);
break;
case Qt::Key_Right:
offset += QPoint(ChangeIncrement, 0);
break;
case Qt::Key_Down:
offset += QPoint(0, ChangeIncrement);
break;
default:
Q_ASSERT(false);
return;
}
QRect geometry = m_targetWindow->geometry().translated(offset);
EnsureGeometryWithinScreenTop(geometry);
m_targetWindow->setGeometry(geometry);
// Mouse cursor travels too while we use the arrow keys
restoreCursorPosition();
}
void InteractiveWindowMover::handleMouseMove(QMouseEvent*)
{
if (!m_arrowAlreadyPressed)
{
// Mouse only moves the window if one arrow has been pressed. That's how Windows does it.
return;
}
const QPoint newPos = m_targetWindow->mapFromGlobal(QCursor::pos());
const QPoint offset = newPos - m_originalCursorPos;
QRect geometry = m_targetWindow->geometry().translated(offset);
EnsureGeometryWithinScreenTop(geometry);
m_targetWindow->setGeometry(geometry);
}
} // namespace AzQtComponents