Initial WindowGrid KWin effect for Plasma 6
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
4da140193f
9 files changed
+1291
No files matched your search
+389
@@ -0,0 +1,389 @@
|
||||
/*
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "windowgrid.h"
|
||||
|
||||
#include "core/output.h"
|
||||
#include "core/inputdevice.h"
|
||||
#include "core/rendertarget.h"
|
||||
#include "core/renderviewport.h"
|
||||
#include "effect/effecthandler.h"
|
||||
#include "effect/effectwindow.h"
|
||||
#include "input_event.h"
|
||||
#include "opengl/glutils.h"
|
||||
#include "window.h"
|
||||
|
||||
#include <KConfigGroup>
|
||||
#include <KSharedConfig>
|
||||
|
||||
#include <QKeyEvent>
|
||||
#include <QVector2D>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
||||
WindowGridEffect::WindowGridEffect()
|
||||
{
|
||||
reconfigure(ReconfigureAll);
|
||||
|
||||
const auto windows = effects->stackingOrder();
|
||||
for (EffectWindow *w : windows) {
|
||||
watchWindow(w);
|
||||
}
|
||||
connect(effects, &EffectsHandler::windowAdded, this, &WindowGridEffect::watchWindow);
|
||||
}
|
||||
|
||||
WindowGridEffect::~WindowGridEffect()
|
||||
{
|
||||
if (m_state != State::Idle) {
|
||||
finish(false);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowGridEffect::reconfigure(ReconfigureFlags)
|
||||
{
|
||||
KSharedConfig::openConfig(QStringLiteral("kwinrc"))->reparseConfiguration();
|
||||
const KConfigGroup cfg = KSharedConfig::openConfig(QStringLiteral("kwinrc"))->group(QStringLiteral("Effect-windowgrid"));
|
||||
m_cfgColumns = std::clamp(cfg.readEntry("Columns", 6), 1, 64);
|
||||
m_cfgRows = std::clamp(cfg.readEntry("Rows", 4), 1, 64);
|
||||
m_useWorkArea = cfg.readEntry("UseWorkArea", true);
|
||||
m_gridColor = cfg.readEntry("GridColor", QColor(255, 255, 255));
|
||||
m_selectionColor = cfg.readEntry("SelectionColor", QColor(0x3d, 0xae, 0xe9));
|
||||
m_opacity = std::clamp(cfg.readEntry("Opacity", 0.35), 0.0, 1.0);
|
||||
}
|
||||
|
||||
bool WindowGridEffect::isActive() const
|
||||
{
|
||||
return m_state != State::Idle;
|
||||
}
|
||||
|
||||
void WindowGridEffect::watchWindow(EffectWindow *w)
|
||||
{
|
||||
connect(w, &EffectWindow::windowStartUserMovedResized, this, &WindowGridEffect::onMoveStarted);
|
||||
connect(w, &EffectWindow::windowFinishUserMovedResized, this, &WindowGridEffect::onMoveFinished);
|
||||
}
|
||||
|
||||
void WindowGridEffect::onMoveStarted(EffectWindow *w)
|
||||
{
|
||||
if (m_state != State::Idle || !w->isUserMove() || !w->isMovable()) {
|
||||
return;
|
||||
}
|
||||
m_state = State::Native;
|
||||
m_window = w;
|
||||
m_originalGeometry = w->frameGeometry();
|
||||
m_cursor = effects->cursorPos();
|
||||
m_rightHeld = false;
|
||||
m_spaceHeld = false;
|
||||
m_mouseConnection = connect(effects, &EffectsHandler::mouseChanged, this, &WindowGridEffect::onMouseChanged);
|
||||
m_keyboardGrabbed = effects->grabKeyboard(this);
|
||||
}
|
||||
|
||||
void WindowGridEffect::onMoveFinished(EffectWindow *w)
|
||||
{
|
||||
if (m_state == State::Native && w == m_window) {
|
||||
finish(false);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowGridEffect::onMouseChanged(const QPointF &pos, const QPointF &, Qt::MouseButtons buttons, Qt::MouseButtons oldbuttons, Qt::KeyboardModifiers, Qt::KeyboardModifiers)
|
||||
{
|
||||
if (m_state != State::Native || !m_window) {
|
||||
return;
|
||||
}
|
||||
m_cursor = pos;
|
||||
const auto pressed = [&](Qt::MouseButton b) {
|
||||
return (buttons & b) && !(oldbuttons & b);
|
||||
};
|
||||
if (pressed(Qt::RightButton)) {
|
||||
enterGrid(false);
|
||||
} else if (pressed(Qt::MiddleButton)) {
|
||||
m_grabOffset = pos - m_window->frameGeometry().topLeft();
|
||||
if (m_mouseConnection) {
|
||||
disconnect(m_mouseConnection);
|
||||
}
|
||||
m_state = State::Moving;
|
||||
effects->startMouseInterception(this, Qt::SizeAllCursor);
|
||||
effects->addRepaintFull();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowGridEffect::enterGrid(bool viaSpace)
|
||||
{
|
||||
if (!m_window) {
|
||||
return;
|
||||
}
|
||||
if (m_mouseConnection) {
|
||||
disconnect(m_mouseConnection);
|
||||
}
|
||||
m_state = State::Grid;
|
||||
m_screen = nullptr;
|
||||
updateArea(m_cursor);
|
||||
// taking over the pointer ends KWin's own interactive move
|
||||
effects->startMouseInterception(this, Qt::SizeAllCursor);
|
||||
setSelecting(!viaSpace, viaSpace, m_cursor);
|
||||
effects->addRepaintFull();
|
||||
}
|
||||
|
||||
void WindowGridEffect::finish(bool restoreGeometry)
|
||||
{
|
||||
const bool intercepting = m_state == State::Grid || m_state == State::Moving;
|
||||
if (restoreGeometry && m_window && intercepting) {
|
||||
m_window->window()->moveResize(RectF(m_originalGeometry.x(), m_originalGeometry.y(), m_originalGeometry.width(), m_originalGeometry.height()));
|
||||
}
|
||||
if (m_mouseConnection) {
|
||||
disconnect(m_mouseConnection);
|
||||
}
|
||||
if (intercepting) {
|
||||
effects->stopMouseInterception(this);
|
||||
}
|
||||
if (m_keyboardGrabbed) {
|
||||
effects->ungrabKeyboard();
|
||||
m_keyboardGrabbed = false;
|
||||
}
|
||||
m_state = State::Idle;
|
||||
m_window = nullptr;
|
||||
m_screen = nullptr;
|
||||
m_rightHeld = false;
|
||||
m_spaceHeld = false;
|
||||
effects->addRepaintFull();
|
||||
}
|
||||
|
||||
void WindowGridEffect::updateArea(const QPointF &pos)
|
||||
{
|
||||
LogicalOutput *screen = effects->screenAt(pos.toPoint());
|
||||
if (!screen || screen == m_screen) {
|
||||
return;
|
||||
}
|
||||
const bool first = m_screen == nullptr;
|
||||
m_screen = screen;
|
||||
m_area = m_useWorkArea ? QRectF(effects->clientArea(MaximizeArea, screen)) : QRectF(screen->geometryF());
|
||||
// portrait monitors get the grid transposed
|
||||
if (m_area.height() > m_area.width()) {
|
||||
m_cols = m_cfgRows;
|
||||
m_rows = m_cfgColumns;
|
||||
} else {
|
||||
m_cols = m_cfgColumns;
|
||||
m_rows = m_cfgRows;
|
||||
}
|
||||
if (!first) {
|
||||
m_start = m_end = cellAt(pos);
|
||||
}
|
||||
}
|
||||
|
||||
WindowGridEffect::Cell WindowGridEffect::cellAt(const QPointF &pos) const
|
||||
{
|
||||
const double cw = m_area.width() / m_cols;
|
||||
const double ch = m_area.height() / m_rows;
|
||||
Cell c;
|
||||
c.col = std::clamp(int(std::floor((pos.x() - m_area.x()) / cw)), 0, m_cols - 1);
|
||||
c.row = std::clamp(int(std::floor((pos.y() - m_area.y()) / ch)), 0, m_rows - 1);
|
||||
return c;
|
||||
}
|
||||
|
||||
QRectF WindowGridEffect::cellRect(Cell a, Cell b) const
|
||||
{
|
||||
const double cw = m_area.width() / m_cols;
|
||||
const double ch = m_area.height() / m_rows;
|
||||
const int c0 = std::min(a.col, b.col);
|
||||
const int c1 = std::max(a.col, b.col);
|
||||
const int r0 = std::min(a.row, b.row);
|
||||
const int r1 = std::max(a.row, b.row);
|
||||
const double x0 = std::round(m_area.x() + c0 * cw);
|
||||
const double y0 = std::round(m_area.y() + r0 * ch);
|
||||
const double x1 = std::round(m_area.x() + (c1 + 1) * cw);
|
||||
const double y1 = std::round(m_area.y() + (r1 + 1) * ch);
|
||||
return QRectF(x0, y0, x1 - x0, y1 - y0);
|
||||
}
|
||||
|
||||
void WindowGridEffect::setSelecting(bool rightButton, bool space, const QPointF &pos)
|
||||
{
|
||||
m_rightHeld = rightButton;
|
||||
m_spaceHeld = space;
|
||||
if (m_rightHeld || m_spaceHeld) {
|
||||
// while the selector is held the start cell follows the cursor
|
||||
m_start = m_end = cellAt(pos);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowGridEffect::applySelection()
|
||||
{
|
||||
if (!m_window) {
|
||||
return;
|
||||
}
|
||||
const QRectF r = cellRect(m_start, m_end);
|
||||
m_window->window()->moveResize(RectF(r.x(), r.y(), r.width(), r.height()));
|
||||
}
|
||||
|
||||
void WindowGridEffect::moveWithCursor(const QPointF &pos)
|
||||
{
|
||||
if (!m_window) {
|
||||
return;
|
||||
}
|
||||
const QPointF tl = pos - m_grabOffset;
|
||||
const QSizeF size = m_window->frameGeometry().size();
|
||||
m_window->window()->moveResize(RectF(tl.x(), tl.y(), size.width(), size.height()));
|
||||
}
|
||||
|
||||
void WindowGridEffect::pointerMotion(PointerMotionEvent *event)
|
||||
{
|
||||
m_cursor = event->position;
|
||||
if (m_state == State::Moving) {
|
||||
moveWithCursor(m_cursor);
|
||||
} else if (m_state == State::Grid) {
|
||||
updateArea(m_cursor);
|
||||
if (m_rightHeld || m_spaceHeld) {
|
||||
m_start = m_end = cellAt(m_cursor);
|
||||
} else {
|
||||
m_end = cellAt(m_cursor);
|
||||
applySelection();
|
||||
}
|
||||
}
|
||||
effects->addRepaintFull();
|
||||
}
|
||||
|
||||
void WindowGridEffect::pointerButton(PointerButtonEvent *event)
|
||||
{
|
||||
const bool down = event->state == PointerButtonState::Pressed;
|
||||
m_cursor = event->position;
|
||||
switch (event->button) {
|
||||
case Qt::LeftButton:
|
||||
if (!down) {
|
||||
if (m_state == State::Grid) {
|
||||
if (m_rightHeld || m_spaceHeld) {
|
||||
m_end = m_start;
|
||||
}
|
||||
applySelection();
|
||||
}
|
||||
finish(false);
|
||||
}
|
||||
return;
|
||||
case Qt::RightButton:
|
||||
if (m_state == State::Grid) {
|
||||
if (down) {
|
||||
setSelecting(true, m_spaceHeld, m_cursor);
|
||||
} else {
|
||||
m_rightHeld = false;
|
||||
if (!m_spaceHeld) {
|
||||
m_end = cellAt(m_cursor);
|
||||
applySelection();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Qt::MiddleButton:
|
||||
if (down && m_state == State::Grid && m_window) {
|
||||
m_grabOffset = m_cursor - m_window->frameGeometry().topLeft();
|
||||
m_state = State::Moving;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
effects->addRepaintFull();
|
||||
}
|
||||
|
||||
void WindowGridEffect::grabbedKeyboardEvent(QKeyEvent *e)
|
||||
{
|
||||
if (e->isAutoRepeat()) {
|
||||
return;
|
||||
}
|
||||
const bool down = e->type() == QEvent::KeyPress;
|
||||
if (e->key() == Qt::Key_Escape && down) {
|
||||
if (m_state == State::Native && m_window) {
|
||||
m_window->window()->cancelInteractiveMoveResize();
|
||||
} else if (m_state != State::Idle) {
|
||||
finish(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e->key() != Qt::Key_Space) {
|
||||
return;
|
||||
}
|
||||
if (m_state == State::Native && down) {
|
||||
enterGrid(true);
|
||||
} else if (m_state == State::Grid) {
|
||||
if (down) {
|
||||
setSelecting(m_rightHeld, true, m_cursor);
|
||||
} else {
|
||||
m_spaceHeld = false;
|
||||
if (!m_rightHeld) {
|
||||
m_end = cellAt(m_cursor);
|
||||
applySelection();
|
||||
}
|
||||
}
|
||||
effects->addRepaintFull();
|
||||
}
|
||||
}
|
||||
|
||||
bool WindowGridEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const Region &deviceRegion, LogicalOutput *screen)
|
||||
{
|
||||
if (!effects->paintScreen(renderTarget, viewport, mask, deviceRegion, screen)) {
|
||||
return false;
|
||||
}
|
||||
if (m_state != State::Grid || screen != m_screen) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const double scale = viewport.scale();
|
||||
const double cw = m_area.width() / m_cols;
|
||||
const double ch = m_area.height() / m_rows;
|
||||
const double line = std::max(1.0, scale);
|
||||
|
||||
const auto addRect = [&](QList<QVector2D> &v, double x, double y, double w, double h) {
|
||||
const double x0 = x * scale;
|
||||
const double y0 = y * scale;
|
||||
const double x1 = (x + w) * scale;
|
||||
const double y1 = (y + h) * scale;
|
||||
v.push_back(QVector2D(x1, y0));
|
||||
v.push_back(QVector2D(x0, y0));
|
||||
v.push_back(QVector2D(x0, y1));
|
||||
v.push_back(QVector2D(x0, y1));
|
||||
v.push_back(QVector2D(x1, y1));
|
||||
v.push_back(QVector2D(x1, y0));
|
||||
};
|
||||
|
||||
// selection fill, then the two corner cells a bit stronger, then the grid lines
|
||||
QList<QVector2D> fill;
|
||||
QList<QVector2D> ends;
|
||||
QList<QVector2D> lines;
|
||||
const QRectF sel = cellRect(m_start, m_end);
|
||||
addRect(fill, sel.x(), sel.y(), sel.width(), sel.height());
|
||||
for (const Cell &c : {m_start, m_end}) {
|
||||
const QRectF r = cellRect(c, c);
|
||||
addRect(ends, r.x(), r.y(), r.width(), r.height());
|
||||
}
|
||||
for (int i = 0; i <= m_cols; ++i) {
|
||||
addRect(lines, std::round(m_area.x() + i * cw) - line / (2 * scale), m_area.y(), line / scale, m_area.height());
|
||||
}
|
||||
for (int j = 0; j <= m_rows; ++j) {
|
||||
addRect(lines, m_area.x(), std::round(m_area.y() + j * ch) - line / (2 * scale), m_area.width(), line / scale);
|
||||
}
|
||||
|
||||
GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
|
||||
ShaderBinder binder(ShaderTrait::UniformColor | ShaderTrait::TransformColorspace);
|
||||
binder.shader()->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, viewport.projectionMatrix());
|
||||
binder.shader()->setColorspaceUniforms(ColorDescription::sRGB, renderTarget.colorDescription(), RenderingIntent::Perceptual);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
const auto draw = [&](const QList<QVector2D> &verts, QColor color, double alpha) {
|
||||
// premultiplied, matching the blend function above
|
||||
color.setRgbF(color.redF() * alpha, color.greenF() * alpha, color.blueF() * alpha, alpha);
|
||||
binder.shader()->setUniform(GLShader::ColorUniform::Color, color);
|
||||
vbo->reset();
|
||||
vbo->setVertices(verts);
|
||||
vbo->render(GL_TRIANGLES);
|
||||
};
|
||||
draw(fill, m_selectionColor, m_opacity);
|
||||
draw(ends, m_selectionColor, m_opacity);
|
||||
draw(lines, m_gridColor, std::min(1.0, m_opacity * 1.5));
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace KWin
|
||||
Reference in new issue
Block a user