Notifications when approaching a screen edge

For each edge an additional "approach" area window is created. When the
mouse enters this approach window, it gets unmapped and a mouse polling
interval is started. If the mouse leaves the approach area again, the
window gets mapped again and the mouse polling is stopped.

During the approaching a signal is emitted with a factor in [0.0,1.0] to
describe how close the mouse is to the edge. 0.0 means far away, 1.0
means triggering the edge. This signal is passed to the effects to allow
using this information. E.g. to provide a glow corner effect or to make
use of it in the cube animation effect to start the animation on desktop
switch.
This commit is contained in:
Martin Gräßlin 2013-01-26 11:50:14 +01:00
parent a8539ff54e
commit 9bab40d995
4 changed files with 263 additions and 7 deletions

View file

@ -140,6 +140,9 @@ EffectsHandlerImpl::EffectsHandlerImpl(Compositor *compositor, Scene *scene)
connect(ws->tabBox(), SIGNAL(tabBoxUpdated()), SIGNAL(tabBoxUpdated()));
connect(ws->tabBox(), SIGNAL(tabBoxClosed()), SIGNAL(tabBoxClosed()));
connect(ws->tabBox(), SIGNAL(tabBoxKeyEvent(QKeyEvent*)), SIGNAL(tabBoxKeyEvent(QKeyEvent*)));
#endif
#ifdef KWIN_BUILD_SCREENEDGES
connect(ScreenEdges::self(), SIGNAL(approaching(ElectricBorder,qreal,QRect)), SIGNAL(screenEdgeApproaching(ElectricBorder,qreal,QRect)));
#endif
// connect all clients
foreach (Client *c, ws->clientList()) {

View file

@ -1162,6 +1162,18 @@ Q_SIGNALS:
* @since 4.10
*/
void stackingOrderChanged();
/**
* This signal is emitted when the user starts to approach the @p border with the mouse.
* The @p factor describes how far away the mouse is in a relative mean. The values are in
* [0.0, 1.0] with 0.0 being emitted when first entered and on leaving. The value 1.0 means that
* the @p border is reached with the mouse. So the values are well suited for animations.
* The signal is always emitted when the mouse cursor position changes.
* @param border The screen edge which is being approached
* @param factor Value in range [0.0,1.0] to describe how close the mouse is to the border
* @param geometry The geometry of the edge which is being approached
* @since 4.11
**/
void screenEdgeApproaching(ElectricBorder border, qreal factor, const QRect &geometry);
protected:
QVector< EffectPair > loaded_effects;

View file

@ -58,6 +58,8 @@ Edge::Edge(ScreenEdges *parent)
, m_border(ElectricNone)
, m_action(ElectricActionNone)
, m_reserved(0)
, m_approaching(false)
, m_lastApproachingFactor(0.0)
{
}
@ -268,6 +270,52 @@ void Edge::pushCursorBack(const QPoint &cursorPos)
QCursor::setPos(x, y);
}
void Edge::setGeometry(const QRect &geometry)
{
if (m_geometry == geometry) {
return;
}
m_geometry = geometry;
int x = m_geometry.x();
int y = m_geometry.y();
int width = m_geometry.width();
int height = m_geometry.height();
// TODO: better not hard coded value
const int size = 20;
if (isCorner()) {
if (isRight()) {
x = x - size +1;
}
if (isBottom()) {
y = y - size +1;
}
width = size;
height = size;
} else {
if (isLeft()) {
y += size + 1;
width = size;
height = height - size * 2;
} else if (isRight()) {
x = x - size + 1;
y += size;
width = size;
height = height - size * 2;
} else if (isTop()) {
x += size;
width = width - size * 2;
height = size;
} else if (isBottom()) {
x += size;
y = y - size +1;
width = width - size * 2;
height = size;
}
}
m_approachGeometry = QRect(x, y, width, height);
doGeometryUpdate();
}
void Edge::doGeometryUpdate()
{
}
@ -280,12 +328,88 @@ void Edge::deactivate()
{
}
void Edge::startApproaching()
{
if (m_approaching) {
return;
}
m_approaching = true;
doStartApproaching();
m_lastApproachingFactor = 0.0;
emit approaching(border(), 0.0, m_approachGeometry);
}
void Edge::doStartApproaching()
{
}
void Edge::stopApproaching()
{
if (!m_approaching) {
return;
}
m_approaching = false;
doStopApproaching();
m_lastApproachingFactor = 0.0;
emit approaching(border(), 0.0, m_approachGeometry);
}
void Edge::doStopApproaching()
{
}
void Edge::updateApproaching(const QPoint &point)
{
if (approachGeometry().contains(point)) {
qreal factor = 0.0;
// manhattan length for our edge
const qreal cornerDistance = 40.0;
const qreal edgeDistance = 20.0;
switch (border()) {
case ElectricTopLeft:
factor = point.manhattanLength() / cornerDistance;
break;
case ElectricTopRight:
factor = (point - approachGeometry().topRight()).manhattanLength() / cornerDistance;
break;
case ElectricBottomRight:
factor = (point - approachGeometry().bottomRight()).manhattanLength() / cornerDistance;
break;
case ElectricBottomLeft:
factor = (point - approachGeometry().bottomLeft()).manhattanLength() / cornerDistance;
break;
case ElectricTop:
factor = qAbs(point.y() - approachGeometry().y()) / edgeDistance;
break;
case ElectricRight:
factor = qAbs(point.x() - approachGeometry().right()) / edgeDistance;
break;
case ElectricBottom:
factor = qAbs(point.y() - approachGeometry().bottom()) / edgeDistance;
break;
case ElectricLeft:
factor = qAbs(point.x() - approachGeometry().x()) / edgeDistance;
break;
default:
break;
}
factor = 1.0 - factor;
if (m_lastApproachingFactor != factor) {
m_lastApproachingFactor = factor;
emit approaching(border(), m_lastApproachingFactor, m_approachGeometry);
}
} else {
stopApproaching();
}
}
/**********************************************************
* ScreenEdges
*********************************************************/
WindowBasedEdge::WindowBasedEdge(ScreenEdges *parent)
: Edge(parent)
, m_window(XCB_WINDOW_NONE)
, m_approachWindow(XCB_WINDOW_NONE)
{
}
@ -297,6 +421,7 @@ WindowBasedEdge::~WindowBasedEdge()
void WindowBasedEdge::activate()
{
createWindow();
createApproachWindow();
}
void WindowBasedEdge::deactivate()
@ -322,17 +447,56 @@ void WindowBasedEdge::createWindow()
atoms->xdnd_aware, XCB_ATOM_ATOM, 32, 1, (unsigned char*)(&version));
}
void WindowBasedEdge::createApproachWindow()
{
if (m_approachWindow != XCB_WINDOW_NONE) {
return;
}
if (!approachGeometry().isValid()) {
return;
}
const uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
const uint32_t values[] = {
true,
XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW
};
const QRect geo = approachGeometry();
m_approachWindow = Xcb::createInputWindow(geo, mask, values);
xcb_map_window(connection(), m_approachWindow);
}
void WindowBasedEdge::destroyWindow()
{
if (m_window != XCB_WINDOW_NONE) {
xcb_destroy_window(connection(), m_window);
m_window = XCB_WINDOW_NONE;
}
if (m_approachWindow != XCB_WINDOW_NONE) {
xcb_destroy_window(connection(), m_approachWindow);
m_approachWindow = XCB_WINDOW_NONE;
}
}
void WindowBasedEdge::doGeometryUpdate()
{
Xcb::moveResizeWindow(m_window, geometry());
Xcb::moveResizeWindow(m_approachWindow, approachGeometry());
}
void WindowBasedEdge::doStartApproaching()
{
xcb_unmap_window(connection(), m_approachWindow);
connect(edges(), SIGNAL(mousePollingTimerEvent(QPoint)), SLOT(updateApproaching(QPoint)));
edges()->startMousePolling();
}
void WindowBasedEdge::doStopApproaching()
{
disconnect(edges(), SIGNAL(mousePollingTimerEvent(QPoint)), this, SLOT(updateApproaching(QPoint)));
edges()->stopMousePolling();
if (m_approachWindow != XCB_WINDOW_NONE) {
xcb_map_window(connection(), m_approachWindow);
}
}
/**********************************************************
@ -362,7 +526,10 @@ ScreenEdges::ScreenEdges(QObject *parent)
, m_actionBottom(ElectricActionNone)
, m_actionBottomLeft(ElectricActionNone)
, m_actionLeft(ElectricActionNone)
, m_mousePolling(0)
, m_mousePollingTimer(new QTimer(this))
{
connect(m_mousePollingTimer, SIGNAL(timeout()), SLOT(performMousePoll()));
}
ScreenEdges::~ScreenEdges()
@ -704,6 +871,7 @@ WindowBasedEdge *ScreenEdges::createEdge(ElectricBorder border, int x, int y, in
}
}
}
connect(edge, SIGNAL(approaching(ElectricBorder,qreal,QRect)), SIGNAL(approaching(ElectricBorder,qreal,QRect)));
return edge;
}
@ -782,10 +950,18 @@ bool ScreenEdges::isEntered(XEvent* e)
if (e->type == EnterNotify) {
for (QList<WindowBasedEdge*>::iterator it = m_edges.begin(); it != m_edges.end(); ++it) {
WindowBasedEdge *edge = *it;
if (edge->isReserved() && edge->window() == e->xcrossing.window) {
if (!edge->isReserved()) {
continue;
}
if (edge->window() == e->xcrossing.window) {
edge->check(QPoint(e->xcrossing.x_root, e->xcrossing.y_root), QDateTime::fromMSecsSinceEpoch(e->xcrossing.time));
return true;
}
if (edge->approachWindow() == e->xcrossing.window) {
edge->startApproaching();
// TODO: if it's a corner, it should also trigger for other windows
return true;
}
}
}
if (e->type == ClientMessage) {
@ -808,6 +984,28 @@ void ScreenEdges::ensureOnTop()
Xcb::restackWindowsWithRaise(windows());
}
void ScreenEdges::startMousePolling()
{
m_mousePolling++;
if (m_mousePolling == 1) {
m_mousePollingTimer->start(100); // TODO: How often do we really need to poll?
}
}
void ScreenEdges::stopMousePolling()
{
m_mousePolling--;
if (m_mousePolling == 0) {
m_mousePollingTimer->stop();
}
}
void ScreenEdges::performMousePoll()
{
Workspace::self()->checkCursorPos();
emit mousePollingTimerEvent(Workspace::self()->cursorPos());
}
/*
* NOTICE THIS IS A HACK
* or at least a quite cumbersome way to handle conflictive electric borders
@ -882,6 +1080,11 @@ QVector< xcb_window_t > ScreenEdges::windows() const
if (w != XCB_WINDOW_NONE) {
wins.append(w);
}
// TODO: lambda
w = (*it)->approachWindow();
if (w != XCB_WINDOW_NONE) {
wins.append(w);
}
}
return wins;
}

View file

@ -57,10 +57,13 @@ public:
bool triggersFor(const QPoint &cursorPos) const;
void check(const QPoint &cursorPos, const QDateTime &triggerTime, bool forceNoPushBack = false);
bool isReserved() const;
const QRect &approachGeometry() const;
ElectricBorder border() const;
void reserve(QObject *object, const char *slot);
const QHash<QObject *, QByteArray> &callBacks() const;
void startApproaching();
void stopApproaching();
public Q_SLOTS:
void reserve();
@ -69,6 +72,9 @@ public Q_SLOTS:
void setBorder(ElectricBorder border);
void setAction(ElectricBorderAction action);
void setGeometry(const QRect &geometry);
void updateApproaching(const QPoint &point);
Q_SIGNALS:
void approaching(ElectricBorder border, qreal factor, const QRect &geometry);
protected:
ScreenEdges *edges();
const ScreenEdges *edges() const;
@ -76,6 +82,8 @@ protected:
virtual void doGeometryUpdate();
virtual void activate();
virtual void deactivate();
virtual void doStartApproaching();
virtual void doStopApproaching();
private:
bool canActivate(const QPoint &cursorPos, const QDateTime &triggerTime);
void handle(const QPoint &cursorPos);
@ -88,10 +96,13 @@ private:
ElectricBorderAction m_action;
int m_reserved;
QRect m_geometry;
QRect m_approachGeometry;
QDateTime m_lastTrigger;
QDateTime m_lastReset;
QPoint m_triggeredPoint;
QHash<QObject *, QByteArray> m_callBacks;
bool m_approaching;
qreal m_lastApproachingFactor;
};
class WindowBasedEdge : public Edge
@ -102,16 +113,25 @@ public:
virtual ~WindowBasedEdge();
xcb_window_t window() const;
/**
* The approach window is a special window to notice when get close to the screen border but
* not yet triggering the border.
**/
xcb_window_t approachWindow() const;
protected:
virtual void doGeometryUpdate();
virtual void activate();
virtual void deactivate();
virtual void doStartApproaching();
virtual void doStopApproaching();
private:
void destroyWindow();
void createWindow();
void createApproachWindow();
xcb_window_t m_window;
xcb_window_t m_approachWindow;
};
/**
@ -261,6 +281,8 @@ public:
ElectricBorderAction actionBottom() const;
ElectricBorderAction actionBottomLeft() const;
ElectricBorderAction actionLeft() const;
void startMousePolling();
void stopMousePolling();
/**
* Singleton getter for this manager.
@ -286,6 +308,19 @@ public Q_SLOTS:
* Recreates all edges e.g. after the screen size changes.
**/
void recreateEdges();
Q_SIGNALS:
/**
* Signal emitted during approaching of mouse towards @p border. The @p factor indicates how
* far away the mouse is from the approaching area. The values are clamped into [0.0,1.0] with
* @c 0.0 meaning far away from the border, @c 1.0 in trigger distance.
**/
void approaching(ElectricBorder border, qreal factor, const QRect &geometry);
void mousePollingTimerEvent(QPoint cursorPos);
private Q_SLOTS:
void performMousePoll();
private:
enum { ElectricDisabled = 0, ElectricMoveOnly = 1, ElectricAlways = 2 };
void setDesktopSwitching(bool enable);
@ -314,6 +349,8 @@ private:
ElectricBorderAction m_actionBottom;
ElectricBorderAction m_actionBottomLeft;
ElectricBorderAction m_actionLeft;
int m_mousePolling;
QTimer *m_mousePollingTimer;
static ScreenEdges *s_self;
};
@ -388,13 +425,9 @@ inline const QRect &Edge::geometry() const
return m_geometry;
}
inline void Edge::setGeometry(const QRect &geometry)
inline const QRect &Edge::approachGeometry() const
{
if (m_geometry == geometry) {
return;
}
m_geometry = geometry;
doGeometryUpdate();
return m_approachGeometry;
}
inline ElectricBorder Edge::border() const
@ -416,6 +449,11 @@ inline xcb_window_t WindowBasedEdge::window() const
return m_window;
}
inline xcb_window_t WindowBasedEdge::approachWindow() const
{
return m_approachWindow;
}
/**********************************************************
* Inlines ScreenEdges
*********************************************************/