#include "group.h"
/**
*
* Beryl group plugin
*
* tab.c
*
* Copyright : (C) 2006 by Patrick Niklaus, Roi Cohen, Danny Baumann
* Authors: Patrick Niklaus <patrick.niklaus@googlemail.com>
* Roi Cohen <roico@beryl-project.org>
* Danny Baumann <maniac@beryl-project.org>
*
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
**/
/*
* groupGetCurrentMousePosition
*
*/
Bool groupGetCurrentMousePosition(CompScreen *s, int *x, int *y)
{
unsigned int rmask;
int mouseX, mouseY, winX, winY;
Window root;
Window child;
Bool result;
result = XQueryPointer (s->display->display, s->root, &root,
&child, &mouseX, &mouseY, &winX, &winY, &rmask);
if (result) {
(*x) = mouseX;
(*y) = mouseY;
}
return result;
}
/*
* groupGetWindowTitle - mostely copied from state.c
*
*/
static char *groupGetWindowTitle(CompWindow *w)
{
Atom type;
int format;
unsigned long nitems;
unsigned long bytes_after;
unsigned long *val;
Display *d = w->screen->display->display;
char *retval;
int result;
Atom utf8_string;
utf8_string = XInternAtom(d, "UTF8_STRING", 0);
type = None;
val = NULL;
result = XGetWindowProperty(d, w->id,
XInternAtom(d, "_NET_WM_NAME", 0), 0, LONG_MAX, False,
utf8_string, &type, &format, &nitems, &bytes_after,
(unsigned char **)&val);
if (result != Success)
return NULL;
if (type != utf8_string || format != 8 || nitems == 0)
{
if (val)
XFree(val);
return NULL;
}
retval = (char *)malloc(sizeof(char) * (nitems + 1));
strncpy(retval, (char *)val, nitems);
retval[nitems] = '\0';
XFree(val);
return retval;
}
/*
* groupGetClippingRegion
*
*/
Region groupGetClippingRegion(CompWindow *w)
{
Region clip = XCreateRegion();
CompWindow *cw;
for(cw = w->next; cw; cw = cw->next)
{
if (!cw->invisible && !(cw->state & (CompWindowStateHiddenMask | CompWindowStateOffscreenMask))) {
Region buf = XCreateRegion();
XRectangle rect;
rect.x = WIN_REAL_X(cw);
rect.y = WIN_REAL_Y(cw);
rect.width = WIN_REAL_WIDTH(cw);
rect.height = WIN_REAL_HEIGHT(cw);
XUnionRectWithRegion(&rect, buf, buf);
XUnionRegion(clip, buf, clip);
XDestroyRegion(buf);
}
}
return clip;
}
/*
* groupTabBarTimeout
*
*/
static Bool
groupTabBarTimeout(void *data)
{
GroupSelection *group = (GroupSelection *) data;
groupTabSetVisibility(group, FALSE, PERMANENT);
group->tabBar->timeoutHandle = 0;
return FALSE; //This will free the timer.
}
/*
* groupCheckForVisibleTabBars
*
*/
void
groupCheckForVisibleTabBars(CompScreen *s)
{
GroupSelection *group;
GROUP_SCREEN(s);
gs->tabBarVisible = FALSE;
for (group = gs->groups; group; group = group->next) {
if (group->tabBar && (group->tabBar->state != PaintOff)) {
gs->tabBarVisible = TRUE;
break;
}
}
}
/*
* groupTabSetVisibility
*
*/
void groupTabSetVisibility(GroupSelection *group, Bool visible, unsigned int mask)
{
// fixes bad crash...
if (!group || !group->windows || !group->tabBar || !HAS_TOP_WIN(group))
return;
GroupTabBar *bar = group->tabBar;
CompWindow *topTab = TOP_TAB(group);
GROUP_SCREEN(group->screen);
PaintState oldState;
oldState = bar->state;
/* hide tab bars for invisible top windows */
if ((topTab->state & (CompWindowStateHiddenMask |
CompWindowStateOffscreenMask)) || topTab->invisible)
{
bar->state = PaintOff;
groupSwitchTopTabInput(group, TRUE);
}
else if (visible && bar->state != PaintPermanentOn &&
(mask & PERMANENT))
{
bar->state = PaintPermanentOn;
groupSwitchTopTabInput(group, FALSE);
}
else if (visible &&
(bar->state == PaintOff || bar->state == PaintFadeOut))
{
bar->state = PaintFadeIn;
groupSwitchTopTabInput(group, FALSE);
}
else if (!visible && (bar->state != PaintPermanentOn || (mask & PERMANENT)) &&
(bar->state == PaintOn || bar->state == PaintPermanentOn || bar->state == PaintFadeIn))
{
bar->state = PaintFadeOut;
groupSwitchTopTabInput(group, TRUE);
}
if (bar->state != oldState && bar->state != PaintPermanentOn) // FIXME remove that when we have a new state for PaintPermanentFadeIn
bar->animationTime = (gs->opt[GROUP_SCREEN_OPTION_FADE_TIME].value.f * 1000) -
bar->animationTime;
groupCheckForVisibleTabBars(group->screen);
}
/*
* groupGetDrawOffsetForSlot
*
*/
void
groupGetDrawOffsetForSlot(GroupTabBarSlot *slot, int *hoffset, int *voffset)
{
if (!slot || !slot->window)
return;
CompWindow *w = slot->window;
GROUP_WINDOW(w);
GROUP_SCREEN(w->screen);
if (slot != gs->draggedSlot) {
if (hoffset)
*hoffset = 0;
if (voffset)
*voffset = 0;
return;
}
int vx, vy;
int oldX = w->serverX;
int oldY = w->serverY;
if (gw->group) {
w->serverX = WIN_X(TOP_TAB(gw->group)) + WIN_WIDTH(TOP_TAB(gw->group)) / 2 - WIN_WIDTH(w) / 2;
w->serverY = WIN_Y(TOP_TAB(gw->group)) + WIN_HEIGHT(TOP_TAB(gw->group)) / 2 - WIN_HEIGHT(w) / 2;
}
defaultViewportForWindow(w, &vx, &vy);
if (hoffset)
*hoffset = ((w->screen->x - vx) % w->screen->hsize) * w->screen->width;
if (voffset)
*voffset = ((w->screen->y - vy) % w->screen->vsize) * w->screen->height;
w->serverX = oldX;
w->serverY = oldY;
}
/*
* groupHandleHoverDetection
*
*/
void groupHandleHoverDetection(GroupSelection *group)
{
GROUP_SCREEN(group->screen);
GroupTabBar *bar = group->tabBar;
if (!HAS_TOP_WIN(group))
return;
CompWindow *topTab = TOP_TAB(group);
if (bar->state != PaintOff) { // Tab-bar is visible.
int mouseX, mouseY;
Bool mouseOnScreen;
mouseOnScreen = groupGetCurrentMousePosition(group->screen, &mouseX, &mouseY);
if (mouseOnScreen && !(bar->hoveredSlot && XPointInRegion(bar->hoveredSlot->region, mouseX, mouseY)))
{
bar->hoveredSlot = NULL;
Region clip;
clip = groupGetClippingRegion(topTab);
GroupTabBarSlot *slot;
for (slot = bar->slots; slot; slot = slot->next)
{
Region reg = XCreateRegion();
XSubtractRegion(slot->region, clip, reg);
if (XPointInRegion(reg, mouseX, mouseY))
{
bar->hoveredSlot = slot;
break;
}
XDestroyRegion(reg);
}
XDestroyRegion(clip);
if ((bar->textLayer->state == PaintFadeIn || bar->textLayer->state == PaintOn) &&
bar->hoveredSlot != bar->textSlot)
{
bar->textLayer->animationTime = (gs->opt[GROUP_SCREEN_OPTION_FADE_TEXT_TIME].value.f * 1000)
- bar->textLayer->animationTime;
bar->textLayer->state = PaintFadeOut;
}
else if (bar->textLayer->state == PaintFadeOut && bar->hoveredSlot == bar->textSlot && bar->hoveredSlot)
{
bar->textLayer->animationTime = (gs->opt[GROUP_SCREEN_OPTION_FADE_TEXT_TIME].value.f * 1000)
- bar->textLayer->animationTime;
bar->textLayer->state = PaintFadeIn;
}
}
}
}
/*
* groupHandleTabBarFade
*
*/
void groupHandleTabBarFade(GroupSelection *group, int msSinceLastPaint)
{
GroupTabBar *bar = group->tabBar;
if ((bar->state == PaintFadeIn || bar->state == PaintFadeOut) &&
bar->animationTime > 0)
{
bar->animationTime -= msSinceLastPaint;
if (bar->animationTime < 0)
bar->animationTime = 0;
if (bar->animationTime == 0)
{
if (bar->state == PaintFadeIn) {
bar->state = PaintOn;
groupCheckForVisibleTabBars(group->screen);
}
else if (bar->state == PaintFadeOut) {
bar->state = PaintOff;
groupCheckForVisibleTabBars(group->screen);
if (bar->textLayer) {
// Tab-bar is no longer painted, clean up text animation variables.
bar->textLayer->animationTime = 0;
bar->textLayer->state = PaintOff;
bar->textSlot = bar->hoveredSlot = NULL;
groupRenderWindowTitle(group);
}
}
}
}
}
/*
* groupHanldeTextFade
*
*/
void groupHandleTextFade(GroupSelection *group, int msSinceLastPaint)
{
GROUP_SCREEN(group->screen);
GroupTabBar *bar = group->tabBar;
GroupCairoLayer *textLayer = bar->textLayer;
if (!textLayer)
return;
if ((textLayer->state == PaintFadeIn || textLayer->state == PaintFadeOut) &&
textLayer->animationTime > 0)
{
textLayer->animationTime -= msSinceLastPaint;
if (textLayer->animationTime < 0)
textLayer->animationTime = 0;
if (textLayer->animationTime == 0) {
if (textLayer->state == PaintFadeIn)
textLayer->state = PaintOn;
else if (textLayer->state == PaintFadeOut)
textLayer->state = PaintOff;
}
}
if (textLayer->state == PaintOff && bar->hoveredSlot)
{
// Start text animation for the new hovered slot.
bar->textSlot = bar->hoveredSlot;
textLayer->state = PaintFadeIn;
textLayer->animationTime = (gs->opt[GROUP_SCREEN_OPTION_FADE_TEXT_TIME].value.f * 1000);
groupRenderWindowTitle(group);
}
else if (textLayer->state == PaintOff && bar->textSlot)
{
// Clean Up.
bar->textSlot = NULL;
groupRenderWindowTitle(group);
}
}
/*
* groupHandleTabChange
*
*/
static void
groupHandleTabChange(CompScreen *s, GroupSelection *group)
{
GROUP_SCREEN(s);
if (!group || !HAS_TOP_WIN(group) || !group->changeTab)
return;
if (screenGrabExist(s, "rotate", "plane", 0))
return;
CompWindow* topTab = TOP_TAB(group);
if(group->tabbingState != PaintOff)
{
// if the previous top-tab window is being removed from the group, move the new top-tab window onscreen.
if(group->ungroupState == UngroupSingle && group->prevTopTab == NULL)
{
gs->queued = TRUE;
moveWindowOnscreen(topTab);
moveWindow(topTab, group->oldTopTabCenterX - WIN_X(topTab) - WIN_WIDTH(topTab) / 2,
group->oldTopTabCenterY - WIN_Y(topTab) - WIN_HEIGHT(topTab) / 2,
TRUE, TRUE);
syncWindowPosition(topTab);
gs->queued = FALSE;
// recalc here is needed (for y value)!
groupRecalcTabBarPos(group, (group->tabBar->region->extents.x1 + group->tabBar->region->extents.x2) / 2,
WIN_REAL_X(topTab),
WIN_REAL_X(topTab) + WIN_REAL_WIDTH(topTab));
group->prevTopTab = group->topTab;
}
return;
}
gs->queued = TRUE;
moveWindowOnscreen(topTab);
moveWindow(topTab, group->oldTopTabCenterX - WIN_X(topTab) - WIN_WIDTH(topTab) / 2,
group->oldTopTabCenterY - WIN_Y(topTab) - WIN_HEIGHT(topTab) / 2,
TRUE, TRUE);
syncWindowPosition(topTab);
gs->queued = FALSE;
activateWindow (topTab);
if(group->prevTopTab) {
//we use only the half time here - the second half will be PaintFadeOut
group->changeAnimationTime = gs->opt[GROUP_SCREEN_OPTION_CHANGE_ANIMATION_TIME].value.f * 500;
group->changeState = PaintFadeIn;
group->changeTab = FALSE;
}
else //No window to do animation with.
{
group->prevTopTab = group->topTab;
group->changeTab = FALSE;
}
return;
}
/*
* groupHandleAnimation
*
*/
static void
groupHandleAnimation(CompScreen *s, GroupSelection *group)
{
GROUP_SCREEN(s);
if(group->tabbingState != PaintOff || !HAS_TOP_WIN(group))
return;
if (screenGrabExist(s, "rotate", "plane", 0))
return;
if(group->changeState == PaintFadeIn && group->changeAnimationTime <= 0) {
// recalc here is needed (for y value)!
groupRecalcTabBarPos(group, (group->tabBar->region->extents.x1 + group->tabBar->region->extents.x2) / 2,
WIN_REAL_X(TOP_TAB(group)),
WIN_REAL_X(TOP_TAB(group)) + WIN_REAL_WIDTH(TOP_TAB(group)));
group->changeAnimationTime = gs->opt[GROUP_SCREEN_OPTION_CHANGE_ANIMATION_TIME].value.f * 500 + group->changeAnimationTime;
group->changeState = PaintFadeOut;
}
if (group->changeState == PaintFadeOut && group->changeAnimationTime <= 0)
{
if (group->prevTopTab)
moveWindowOffscreen(PREV_TOP_TAB(group));
group->prevTopTab = group->topTab;
group->changeState = PaintOff;
group->changeAnimationTime = 0;
if (group->nextTopTab) {
groupChangeTab(group->nextTopTab, group->nextDirection);
group->nextTopTab = NULL;
groupHandleTabChange(s, group);
}
else if (gs->opt[GROUP_SCREEN_OPTION_VISIBILITY_TIME].value.f != 0.0f) {
groupTabSetVisibility (group, TRUE, PERMANENT | SHOW_BAR_INSTANTLY_MASK);
if (group->tabBar->timeoutHandle)
compRemoveTimeout(group->tabBar->timeoutHandle);
group->tabBar->timeoutHandle = compAddTimeout (
gs->opt[GROUP_SCREEN_OPTION_VISIBILITY_TIME].value.f * 1000,
groupTabBarTimeout, group);
}
}
return;
}
/*
* groupHandleTab
*
*/
static void
groupHandleTab(CompScreen *s, GroupSelection *group)
{
if (group->tabbingState == PaintOff || group->doTabbing ||
!HAS_TOP_WIN(group) || !group->changeTab)
return;
GroupTabBarSlot *slot;
for(slot = group->tabBar->slots; slot; slot = slot->next) {
if (!slot->window)
continue;
CompWindow *w = slot->window;
GROUP_WINDOW(w);
if (slot == group->topTab || !(gw->animateState & FINISHED_ANIMATION) || gw->ungroup)
continue;
moveWindowOffscreen(w);
}
group->changeTab = FALSE;
group->prevTopTab = group->topTab;
return;
}
/*
* groupHandleTabbingAnimation
*
*/
static void
groupHandleTabbingAnimation(CompScreen *s, GroupSelection *group)
{
int i;
if (group->tabbingState == PaintOff || group->doTabbing)
return;
// Not animated any more.
group->tabbingState = PaintOff;
groupSyncWindows(group);
for(i = 0; i < group->nWins; i++) {
CompWindow *w = group->windows[i];
GROUP_WINDOW(w);
gw->animateState = 0;
}
return;
}
/*
* groupHandleUntab
*
*/
static void
groupHandleUntab(CompScreen *s, GroupSelection *group)
{
if (group->tabbingState == PaintOff || !group->doTabbing)
return;
if (group->topTab || !group->changeTab)
return;
groupDeleteTabBar(group);
group->changeAnimationTime = 0;
group->changeState = PaintOff;
group->nextTopTab = NULL;
group->changeTab = FALSE;
group->prevTopTab = group->topTab;
return;
}
/*
* groupHandleUngroup
*
*/
static Bool
groupHandleUngroup(CompScreen *s, GroupSelection *group)
{
int i;
GROUP_SCREEN(s);
if((group->ungroupState == UngroupSingle) && group->doTabbing && group->changeTab)
{
for(i = 0; i < group->nWins; i++) {
CompWindow *w = group->windows[i];
GROUP_WINDOW(w);
if (gw->ungroup)
{
gs->queued = TRUE;
moveWindowOnscreen(w);
moveWindow(w, group->oldTopTabCenterX - WIN_X(w) - WIN_WIDTH(w) / 2,
group->oldTopTabCenterY - WIN_Y(w) - WIN_HEIGHT(w) / 2,
TRUE, TRUE);
syncWindowPosition(w);
gs->queued = FALSE;
}
}
group->changeTab = FALSE;
}
if ((group->ungroupState == UngroupSingle) && !group->doTabbing) {
Bool morePending;
do {
morePending = FALSE;
for(i = 0;i < group->nWins; i++) {
CompWindow *w = group->windows[i];
GROUP_WINDOW(w);
if (gw->ungroup) {
groupDeleteGroupWindow(w, TRUE);
gw->ungroup = FALSE;
morePending = TRUE;
}
}
} while (morePending);
group->ungroupState = UngroupNone;
}
if (group->prev)
{
if ((group->prev->ungroupState == UngroupAll) && !group->prev->doTabbing)
groupDeleteGroup(group->prev);
}
if (!group->next) {
if ((group->ungroupState == UngroupAll) && !group->doTabbing) {
groupDeleteGroup(group);
return FALSE;
}
}
return TRUE;
}
/*
* groupHandleChanges
*
*/
void
groupHandleChanges(CompScreen* s)
{
GROUP_SCREEN(s);
GroupSelection *group;
for(group = gs->groups; group; group = group ? group->next : NULL)
{
groupHandleUntab(s, group);
groupHandleTab(s, group);
groupHandleTabbingAnimation(s, group);
groupHandleTabChange(s, group);
groupHandleAnimation(s, group);
if (!groupHandleUngroup(s, group))
group = NULL;
}
}
/* adjust velocity for each animation step (adapted from the scale plugin) */
static int
adjustTabVelocity(CompWindow * w)
{
float dx, dy, adjust, amount;
float x1, y1;
GROUP_WINDOW(w);
x1 = y1 = 0.0;
if (!(gw->animateState & IS_ANIMATED))
return 0;
x1 = gw->destination.x;
y1 = gw->destination.y;
dx = x1 - (w->serverX + gw->tx);
adjust = dx * 0.15f;
amount = fabs(dx) * 1.5f;
if (amount < 0.5f)
amount = 0.5f;
else if (amount > 5.0f)
amount = 5.0f;
gw->xVelocity = (amount * gw->xVelocity + adjust) / (amount + 1.0f);
dy = y1 - (w->serverY + gw->ty);
adjust = dy * 0.15f;
amount = fabs(dy) * 1.5f;
if (amount < 0.5f)
amount = 0.5f;
else if (amount > 5.0f)
amount = 5.0f;
gw->yVelocity = (amount * gw->yVelocity + adjust) / (amount + 1.0f);
if (fabs(dx) < 0.1f && fabs(gw->xVelocity) < 0.2f &&
fabs(dy) < 0.1f && fabs(gw->yVelocity) < 0.2f)
{
gw->xVelocity = gw->yVelocity = 0.0f;
gw->tx = x1 - w->serverX;
gw->ty = y1 - w->serverY;
return 0;
}
return 1;
}
/*
* groupDrawTabAnimation
*
*/
void groupDrawTabAnimation(CompScreen * s, int msSinceLastPaint)
{
GROUP_SCREEN(s);
int i;
GroupSelection *group;
for(group = gs->groups; group; group = group->next)
{
if(group->tabbingState == PaintOff)
continue;
int steps, dx, dy;
float amount, chunk;
amount = msSinceLastPaint * 0.05f *
gs->opt[GROUP_SCREEN_OPTION_TABBING_SPEED].value.f;
steps = amount / (0.5f *
gs->opt[GROUP_SCREEN_OPTION_TABBING_TIMESTEP].value.f);
if (!steps)
steps = 1;
chunk = amount / (float)steps;
while (steps--) {
group->doTabbing = FALSE;
for(i = 0; i < group->nWins; i++) {
CompWindow *cw = group->windows[i];
if(!cw)
continue;
GROUP_WINDOW(cw);
if (!(gw->animateState & IS_ANIMATED))
continue;
if (!adjustTabVelocity(cw))
{
gw->animateState |= FINISHED_ANIMATION;
gw->animateState &= ~(IS_ANIMATED);
}
gw->tx += gw->xVelocity * chunk;
gw->ty += gw->yVelocity * chunk;
dx = (cw->serverX + gw->tx) - cw->attrib.x;
dy = (cw->serverY + gw->ty) - cw->attrib.y;
group->doTabbing |= (gw->animateState & IS_ANIMATED);
gs->queued = TRUE;
moveWindow(cw, dx, dy, FALSE, FALSE);
gs->queued = FALSE;
}
if (!group->doTabbing)
break;
}
}
}
/*
* groupUpdateTabBars
*
*/
Bool groupUpdateTabBars(void *display)
{
CompDisplay *d = (CompDisplay*) display;
GroupTabBar *bar;
CompWindow *topTab;
GroupSelection *group;
Bool titleBarMouseOver;
Bool tabBarMouseOver;
int mouseX, mouseY;
CompScreen *s;
for (s = d->screens; s; s = s->next) {
if (groupGetCurrentMousePosition(s, &mouseX, &mouseY))
break;
}
if (!s)
return TRUE;
GROUP_SCREEN(s);
for(group = gs->groups; group; group = group->next)
{
if(!group->tabBar || !HAS_TOP_WIN(group))
continue;
topTab = TOP_TAB(group);
if (topTab->state & (CompWindowStateHiddenMask | CompWindowStateOffscreenMask))
continue;
if (topTab->invisible)
continue;
bar = group->tabBar;
// create clipping region
Region clip = groupGetClippingRegion(topTab);
// title bar
Region reg = XCreateRegion();
XRectangle rect;
rect.x = WIN_X(topTab) - topTab->input.left;
rect.y = WIN_Y(topTab) - topTab->input.top;
rect.width = WIN_WIDTH(topTab) + topTab->input.right;
rect.height = WIN_Y(topTab) - rect.y;
XUnionRectWithRegion(&rect, reg, reg);
// clip title bar with stack
XSubtractRegion(reg, clip, reg);
titleBarMouseOver = XPointInRegion(reg, mouseX, mouseY);
XDestroyRegion(reg);
// clip tab bar with stack
reg = XCreateRegion();
XSubtractRegion(bar->region, clip, reg);
if (bar->state != PaintOff && bar->state != PaintFadeOut)
tabBarMouseOver = XPointInRegion(reg, mouseX, mouseY);
else
tabBarMouseOver = FALSE;
if ((bar->state == PaintOff || bar->state == PaintFadeOut) && titleBarMouseOver) {
groupRecalcTabBarPos(group, mouseX, WIN_REAL_X(topTab),
WIN_REAL_X(topTab) + WIN_REAL_WIDTH(topTab));
addWindowDamage(topTab);
}
XDestroyRegion(reg);
XDestroyRegion(clip);
groupTabSetVisibility (group, titleBarMouseOver | tabBarMouseOver, 0);
}
return TRUE;
}
/*
* groupGetConstrainRegion
*
*/
static Region groupGetConstrainRegion(CompScreen *s)
{
CompWindow *w;
Region region;
REGION r;
int i;
region = XCreateRegion ();
if (!region)
return NULL;
for (i = 0;i < s->nOutputDev; i++)
XUnionRegion (&s->outputDev[i].region, region, region);
r.rects = &r.extents;
r.numRects = r.size = 1;
for (w = s->windows; w; w = w->next) {
if (!w->mapNum)
continue;
if (w->struts) {
r.extents.x1 = w->struts->top.x;
r.extents.y1 = w->struts->top.y;
r.extents.x2 = r.extents.x1 + w->struts->top.width;
r.extents.y2 = r.extents.y1 + w->struts->top.height;
XSubtractRegion (region, &r, region);
r.extents.x1 = w->struts->bottom.x;
r.extents.y1 = w->struts->bottom.y;
r.extents.x2 = r.extents.x1 + w->struts->bottom.width;
r.extents.y2 = r.extents.y1 + w->struts->bottom.height;
XSubtractRegion (region, &r, region);
r.extents.x1 = w->struts->left.x;
r.extents.y1 = w->struts->left.y;
r.extents.x2 = r.extents.x1 + w->struts->left.width;
r.extents.y2 = r.extents.y1 + w->struts->left.height;
XSubtractRegion (region, &r, region);
r.extents.x1 = w->struts->right.x;
r.extents.y1 = w->struts->right.y;
r.extents.x2 = r.extents.x1 + w->struts->right.width;
r.extents.y2 = r.extents.y1 + w->struts->right.height;
XSubtractRegion (region, &r, region);
}
}
return region;
}
/*
* groupConstrainMovement
*
*/
static Bool groupConstrainMovement(CompWindow *w, Region constrainRegion, int dx, int dy,
int *new_dx, int *new_dy)
{
GROUP_WINDOW(w);
int status;
int origDx = dx, origDy = dy;
int x, y, width, height;
if (!gw->group)
return FALSE;
if (!dx && !dy)
return FALSE;
x = gw->orgPos.x - w->input.left + dx;
y = gw->orgPos.y - w->input.top + dy;
width = WIN_REAL_WIDTH(w);
height = WIN_REAL_HEIGHT(w);
status = XRectInRegion(constrainRegion, x, y, width, height);
int xStatus = status;
while (dx && (xStatus != RectangleIn)) {
xStatus = XRectInRegion(constrainRegion, x, y - dy,
width, height);
if (xStatus != RectangleIn)
dx += (dx < 0) ? 1 : -1;
x = gw->orgPos.x - w->input.left + dx;
}
while (dy && (status != RectangleIn)) {
status = XRectInRegion(constrainRegion, x, y,
width, height);
if (status != RectangleIn)
dy += (dy < 0) ? 1 : -1;
y = gw->orgPos.y - w->input.top + dy;
}
if (new_dx)
*new_dx = dx;
if (new_dy)
*new_dy = dy;
if ((dx != origDx) || (dy != origDy))
return TRUE;
else
return FALSE;
}
/*
* groupApplyConstrainingToWindows
*
*/
static void groupApplyConstrainingToWindows(GroupSelection *group,
Region constrainRegion, Window constrainedWindow, int dx, int dy)
{
int i;
CompWindow *w;
if (!dx && !dy)
return;
for (i = 0; i < group->nWins; i++) {
w = group->windows[i];
GROUP_WINDOW(w);
/* ignore certain windows: we don't want to apply
the constraining results on the constrained window
itself, not do we want to change the target position
of unamimated windows and of windows which
already are constrained */
if (w->id == constrainedWindow)
continue;
if(!(gw->animateState & IS_ANIMATED))
continue;
if (gw->animateState & DONT_CONSTRAIN)
continue;
if (!(gw->animateState & CONSTRAINED_X)) {
gw->animateState |= IS_ANIMATED;
/* applying the constraining result of another window
might move the window offscreen, too, so check
if this is not the case */
if (groupConstrainMovement(w, constrainRegion, dx, 0, &dx, NULL))
gw->animateState |= CONSTRAINED_X;
gw->destination.x += dx;
gw->orgPos.x += dx;
}
if (!(gw->animateState & CONSTRAINED_Y)) {
gw->animateState |= IS_ANIMATED;
/* analog to X case */
if (groupConstrainMovement(w, constrainRegion, 0, dy, NULL, &dy))
gw->animateState |= CONSTRAINED_Y;
gw->destination.y += dy;
gw->orgPos.y += dy;
}
}
}
/*
* groupStartTabbingAnimation
*
*/
void groupStartTabbingAnimation(GroupSelection *group, Bool tab)
{
if (!group || (group->tabbingState != PaintOff))
return;
CompScreen *s = group->windows[0]->screen;
int i;
int dx, dy;
int constrainStatus;
group->doTabbing = TRUE;
group->changeTab = TRUE;
group->tabbingState = (tab) ? PaintFadeIn : PaintFadeOut;
if (!tab) {
/* we need to set up the X/Y constraining on untabbing */
Region constrainRegion = groupGetConstrainRegion(s);
Bool constrainedWindows = TRUE;
if (!constrainRegion)
return;
/* reset all flags */
for (i = 0; i < group->nWins; i++) {
GROUP_WINDOW(group->windows[i]);
gw->animateState &= ~(CONSTRAINED_X | CONSTRAINED_Y | DONT_CONSTRAIN);
}
/* as we apply the constraining in a flat loop,
we may need to run multiple times through this
loop until all constraining dependencies are met */
while (constrainedWindows) {
constrainedWindows = FALSE;
/* loop through all windows and try to constrain their
animation path (going from gw->orgPos to
gw->destination) to the active screen area */
for (i = 0; i < group->nWins; i++) {
CompWindow *w = group->windows[i];
GROUP_WINDOW(w);
/* ignore windows which aren't animated and/or
already are at the edge of the screen area */
if (!(gw->animateState & IS_ANIMATED))
continue;
if (gw->animateState & DONT_CONSTRAIN)
continue;
/* is the original position inside the screen area? */
constrainStatus = XRectInRegion(constrainRegion,
gw->orgPos.x - w->input.left,
gw->orgPos.y - w->input.top,
WIN_REAL_WIDTH(w), WIN_REAL_HEIGHT(w));
/* constrain the movement */
if (groupConstrainMovement(w, constrainRegion,
gw->destination.x - gw->orgPos.x,
gw->destination.y - gw->orgPos.y, &dx, &dy)) {
/* handle the case where the window is outside the screen
area on its whole animation path */
if (constrainStatus != RectangleIn && !dx && !dy) {
gw->animateState |= DONT_CONSTRAIN;
gw->animateState |= CONSTRAINED_X | CONSTRAINED_Y;
/* use the original position as last resort */
gw->destination.x = gw->mainTabOffset.x;
gw->destination.y = gw->mainTabOffset.y;
} else {
/* if we found a valid target position, apply
the change also to other windows to retain
the distance between the windows */
groupApplyConstrainingToWindows(group,
constrainRegion, w->id,
dx - gw->destination.x + gw->orgPos.x,
dy - gw->destination.y + gw->orgPos.y);
/* if we hit constraints, adjust the mask and the
target position accordingly */
if (dx != (gw->destination.x - gw->orgPos.x)) {
gw->animateState |= CONSTRAINED_X;
gw->destination.x = gw->orgPos.x + dx;
}
if (dy != (gw->destination.y - gw->orgPos.y)) {
gw->animateState |= CONSTRAINED_Y;
gw->destination.y = gw->orgPos.y + dy;
}
constrainedWindows = TRUE;
}
}
}
}
XDestroyRegion(constrainRegion);
}
}
/*
* groupTabGroup
*
*/
void groupTabGroup(CompWindow *main)
{
GROUP_WINDOW(main);
GROUP_SCREEN(main->screen);
GroupSelection *group = gw->group;
if(!group || group->tabBar)
return;
groupInitTabBar(group, main);
groupCreateInputPreventionWindow(group);
group->tabbingState = PaintOff;
groupChangeTab(gw->slot, RotateUncertain); //Slot is initialized after groupInitTabBar(group);
groupRecalcTabBarPos(gw->group, WIN_X(main) + WIN_WIDTH(main)/2,
WIN_X(main), WIN_X(main) + WIN_WIDTH(main));
int width, height;
width = group->tabBar->region->extents.x2 - group->tabBar->region->extents.x1;
height = group->tabBar->region->extents.y2 - group->tabBar->region->extents.y1;
group->tabBar->textLayer = groupCreateCairoLayer(main->screen, width, height);
group->tabBar->textLayer->state = PaintOff;
group->tabBar->textLayer->animationTime = 0;
groupRenderWindowTitle(group);
group->tabBar->textLayer->animationTime = gs->opt[GROUP_SCREEN_OPTION_FADE_TEXT_TIME].value.f * 1000;
group->tabBar->textLayer->state = PaintFadeIn;
group->tabBar->bgLayer = groupCreateCairoLayer(main->screen, width, height);
group->tabBar->bgLayer->state = PaintOn;
group->tabBar->bgLayer->animationTime = 0;
groupRenderTabBarBackground(group);
width = group->topTab->region->extents.x2 - group->topTab->region->extents.x1;
height = group->topTab->region->extents.y2 - group->topTab->region->extents.y1;
group->tabBar->selectionLayer = groupCreateCairoLayer(main->screen, width, height);
group->tabBar->selectionLayer->state = PaintOn;
group->tabBar->selectionLayer->animationTime = 0;
groupRenderTopTabHighlight(group);
if(!HAS_TOP_WIN(group))
return;
GroupTabBarSlot *slot;
for(slot = group->tabBar->slots; slot; slot = slot->next) {
CompWindow *cw = slot->window;
GROUP_WINDOW(cw);
int x = WIN_X(cw), y = WIN_Y(cw);
if(gw->animateState & IS_ANIMATED)
{
x = gw->destination.x;
y = gw->destination.y;
}
// center the window to the main window
gw->destination.x = WIN_X(main) + (WIN_WIDTH(main) / 2) - (WIN_WIDTH(cw) / 2);
gw->destination.y = WIN_Y(main) + (WIN_HEIGHT(main) / 2) - (WIN_HEIGHT(cw) / 2);
gw->mainTabOffset.x = x - gw->destination.x; //Distance from destination.
gw->mainTabOffset.y = y - gw->destination.y;
gw->orgPos.x = WIN_X(cw);
gw->orgPos.y = WIN_Y(cw);
gw->tx = gw->ty = gw->xVelocity = gw->yVelocity = 0.0f;
gw->animateState |= IS_ANIMATED;
}
groupStartTabbingAnimation(group, TRUE);
}
/*
* groupUntabGroup
*
*/
void
groupUntabGroup(GroupSelection *group)
{
if(!HAS_TOP_WIN(group))
return;
GROUP_WINDOW(TOP_TAB(group));
GROUP_SCREEN(TOP_TAB(group)->screen);
int mainOrgPosX = gw->mainTabOffset.x;
int mainOrgPosY = gw->mainTabOffset.y;
int oldX, oldY;
CompWindow* prevTopTab;
if(group->prevTopTab)
prevTopTab = PREV_TOP_TAB(group);
else
prevTopTab = TOP_TAB(group); //If prevTopTab isn't set, we have no choice but using topTab.
//It happens when there is still animation, which means the tab wasn't changed anyway.
group->oldTopTabCenterX = WIN_X(prevTopTab) + WIN_WIDTH(prevTopTab)/2;
group->oldTopTabCenterY = WIN_Y(prevTopTab) + WIN_HEIGHT(prevTopTab)/2;
group->topTab = NULL;
GroupTabBarSlot *slot;
for(slot = group->tabBar->slots; slot; slot = slot->next) {
CompWindow *cw = slot->window;
GROUP_WINDOW(cw);
gs->queued = TRUE;
moveWindowOnscreen(cw);
moveWindow(cw, group->oldTopTabCenterX - WIN_X(cw) - WIN_WIDTH(cw) / 2,
group->oldTopTabCenterY - WIN_Y(cw) - WIN_HEIGHT(cw) / 2,
TRUE, TRUE);
syncWindowPosition(cw);
gs->queued = FALSE;
/* save the old original position - we might need it if constraining fails */
oldX = gw->orgPos.x;
oldY = gw->orgPos.y;
gw->orgPos.x = group->oldTopTabCenterX - WIN_WIDTH(cw) / 2;
gw->orgPos.y = group->oldTopTabCenterY - WIN_HEIGHT(cw) / 2;
gw->destination.x = WIN_X(prevTopTab) + WIN_WIDTH(prevTopTab)/2 - WIN_WIDTH(cw)/2 + gw->mainTabOffset.x - mainOrgPosX;
gw->destination.y = WIN_Y(prevTopTab) + WIN_HEIGHT(prevTopTab)/2 - WIN_HEIGHT(cw)/2 + gw->mainTabOffset.y - mainOrgPosY;
gw->mainTabOffset.x = oldX;
gw->mainTabOffset.y = oldY;
gw->animateState |= IS_ANIMATED;
gw->tx = gw->ty = gw->xVelocity = gw->yVelocity = 0.0f;
}
group->tabbingState = PaintOff;
groupStartTabbingAnimation(group, FALSE);
damageScreen(group->screen);
}
/*
* groupChangeTab
*
*/
Bool
groupChangeTab(GroupTabBarSlot* topTab, ChangeTabAnimationDirection direction)
{
if(!topTab)
return TRUE;
CompWindow* w = topTab->window;
GROUP_WINDOW(w);
if(!gw->group || gw->group->topTab == topTab || gw->group->tabbingState != PaintOff)
return TRUE;
if (gw->group->prevTopTab && gw->group->changeState == PaintOff) {
gw->group->oldTopTabCenterX = WIN_X(PREV_TOP_TAB(gw->group)) + WIN_WIDTH(PREV_TOP_TAB(gw->group))/2;
gw->group->oldTopTabCenterY = WIN_Y(PREV_TOP_TAB(gw->group)) + WIN_HEIGHT(PREV_TOP_TAB(gw->group))/2;
}
if(gw->group->changeState != PaintOff)
gw->group->nextDirection = direction;
else if (direction == RotateLeft)
gw->group->changeAnimationDirection = 1;
else if (direction == RotateRight)
gw->group->changeAnimationDirection = -1;
else {
int distanceOld = 0, distanceNew = 0;
GroupTabBarSlot *slot;
if (gw->group->topTab)
for (slot = gw->group->tabBar->slots; slot && (slot != gw->group->topTab);
slot = slot->next, distanceOld++);
for (slot = gw->group->tabBar->slots; slot && (slot != topTab);
slot = slot->next, distanceNew++);
if (distanceNew < distanceOld)
gw->group->changeAnimationDirection = 1; //left
else
gw->group->changeAnimationDirection = -1; //right
//check if the opposite direction is shorter
if (abs(distanceNew - distanceOld) > (gw->group->tabBar->nSlots / 2))
gw->group->changeAnimationDirection *= -1;
}
if(gw->group->changeState != PaintOff)
gw->group->nextTopTab = topTab;
else
{
gw->group->topTab = topTab;
gw->group->changeTab = (gw->group->prevTopTab != topTab);
groupRenderWindowTitle(gw->group);
groupRenderTopTabHighlight(gw->group);
addWindowDamage(w);
}
return TRUE;
}
/*
* groupRebuildCairoLayer
*
*/
GroupCairoLayer* groupRebuildCairoLayer(CompScreen *s, GroupCairoLayer *layer, int width, int height)
{
int timeBuf = layer->animationTime;
PaintState stateBuf = layer->state;
groupDestroyCairoLayer(s, layer);
layer = groupCreateCairoLayer(s, width, height);
layer->animationTime = timeBuf;
layer->state = stateBuf;
return layer;
}
/*
* groupClearCairoLayer
*
*/
void groupClearCairoLayer(GroupCairoLayer *layer)
{
cairo_t *cr = layer->cairo;
cairo_save(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
cairo_paint(cr);
cairo_restore(cr);
}
/*
* groupDestroyCairoLayer
*
*/
void groupDestroyCairoLayer(CompScreen *s, GroupCairoLayer *layer)
{
if (layer->cairo)
cairo_destroy(layer->cairo);
if (layer->surface)
cairo_surface_destroy(layer->surface);
if (layer->texBuf)
free(layer->texBuf);
if (&layer->texture)
finiTexture(s, &layer->texture);
if (layer->pixmap)
XFreePixmap(s->display->display, layer->pixmap);
free(layer);
}
/*
* groupCreateCairoLayer
*
*/
GroupCairoLayer* groupCreateCairoLayer(CompScreen *s, int width, int height)
{
GroupCairoLayer* layer = (GroupCairoLayer*) malloc(sizeof(GroupCairoLayer));
layer->surface = NULL;
layer->cairo = NULL;
layer->texBuf = NULL;
layer->animationTime = 0;
layer->state = PaintOff;
layer->pixmap = None;
initTexture(s, &layer->texture);
layer->texBuf = calloc(4 * width * height, sizeof (unsigned char));
layer->surface = cairo_image_surface_create_for_data(layer->texBuf, CAIRO_FORMAT_ARGB32,
width, height, 4 * width);
if (cairo_surface_status(layer->surface) != CAIRO_STATUS_SUCCESS) {
free(layer->texBuf);
free(layer);
return NULL;
}
layer->cairo = cairo_create(layer->surface);
if (cairo_status(layer->cairo) != CAIRO_STATUS_SUCCESS) {
free(layer->texBuf);
free(layer);
return NULL;
}
groupClearCairoLayer(layer);
return layer;
}
/*
* groupRecalcSlotPos
*
*/
static void
groupRecalcSlotPos(GroupTabBarSlot *slot, int slotPos)
{
GROUP_WINDOW (slot->window);
GROUP_SCREEN (slot->window->screen);
GroupSelection *group = gw->group;
XRectangle box;
if (!HAS_TOP_WIN(group) || !group->tabBar)
return;
int border_width = gs->opt[GROUP_SCREEN_OPTION_BORDER_WIDTH].value.i;
int thumb_size = gs->opt[GROUP_SCREEN_OPTION_THUMB_SIZE].value.i;
EMPTY_REGION(slot->region);
box.x = border_width + ((thumb_size + border_width) * slotPos);
box.y = border_width;
box.width = thumb_size;
box.height = thumb_size;
XUnionRectWithRegion(&box, slot->region, slot->region);
// in case the name changed we need a new one here
if(slot->name)
free(slot->name);
slot->name = groupGetWindowTitle(slot->window);
}
/*
* groupRecalcTabBarPos
*
*/
void groupRecalcTabBarPos(GroupSelection *group, int middleX, int minX1, int maxX2)
{
if (!HAS_TOP_WIN(group) || !group->tabBar)
return;
GroupTabBarSlot *slot;
GroupTabBar *bar = group->tabBar;
CompWindow *topTab = TOP_TAB(group);
Bool isDraggedSlotGroup = FALSE;
GROUP_SCREEN(group->screen);
/* first damage the old region to make sure it is updated */
damageScreenRegion (group->screen, bar->region);
int border_width = gs->opt[GROUP_SCREEN_OPTION_BORDER_WIDTH].value.i;
int bar_width;
int currentSlot;
XRectangle box;
// calculate the space which the tabs need
int tabs_width = 0;
int tabs_height = 0;
for(slot = bar->slots; slot; slot = slot->next)
{
if (slot == gs->draggedSlot && gs->dragged) {
isDraggedSlotGroup = TRUE;
continue;
}
tabs_width += (slot->region->extents.x2 - slot->region->extents.x1);
if ((slot->region->extents.y2 - slot->region->extents.y1) > tabs_height)
tabs_height = slot->region->extents.y2 - slot->region->extents.y1;
}
// just a little work-a-round for first call
int thumb_size = gs->opt[GROUP_SCREEN_OPTION_THUMB_SIZE].value.i;
if (bar->nSlots && tabs_width <= 0) { // first call
tabs_width = thumb_size * bar->nSlots;
if (bar->nSlots && tabs_height < thumb_size) // we need to do the standard height too
tabs_height = thumb_size;
}
bar_width = border_width * (bar->nSlots + 1) + tabs_width;
if (isDraggedSlotGroup)
bar_width -= border_width; //1 tab is missing, so we have 1 less border
if (maxX2 - minX1 < bar_width)
box.x = (maxX2 + minX1)/2 - bar_width / 2;
else if (middleX - bar_width/2 < minX1)
box.x = minX1;
else if (middleX + bar_width/2 > maxX2)
box.x = maxX2 - bar_width;
else
box.x = middleX - bar_width / 2;
box.y = WIN_Y(topTab);
box.width = bar_width;
box.height = border_width * 2 + tabs_height;
EMPTY_REGION(bar->region);
XUnionRectWithRegion(&box, bar->region, bar->region);
// recalc every slot region
currentSlot = 0;
for(slot = bar->slots; slot; slot = slot->next)
{
if(slot == gs->draggedSlot && gs->dragged)
continue;
groupRecalcSlotPos (slot, currentSlot);
XOffsetRegion (slot->region,
bar->region->extents.x1,
bar->region->extents.y1);
slot->springX = (slot->region->extents.x1 + slot->region->extents.x2) / 2;
slot->speed = 0;
slot->msSinceLastMove = 0;
currentSlot++;
}
bar->leftSpringX = box.x;
bar->rightSpringX = box.x + box.width;
bar->rightSpeed = 0;
bar->leftSpeed = 0;
bar->rightMsSinceLastMove = 0;
bar->leftMsSinceLastMove = 0;
groupUpdateInputPreventionWindow(group);
groupRenderTabBarBackground(group);
}
/*
* groupInsertTabBarSlotBefore
*
*/
void groupInsertTabBarSlotBefore(GroupTabBar *bar, GroupTabBarSlot *slot, GroupTabBarSlot *nextSlot)
{
GroupTabBarSlot *prev = nextSlot->prev;
if(prev) {
slot->prev = prev;
prev->next = slot;
} else {
bar->slots = slot;
slot->prev = NULL;
}
slot->next = nextSlot;
nextSlot->prev = slot;
bar->nSlots++;
CompWindow *w = slot->window;
GROUP_WINDOW(w);
//Moving bar->region->extents.x1 / x2 as minX1 / maxX2 will work,
//because the tab-bar got wider now, so it will put it in the average between them,
//which is (bar->region->extents.x1 + bar->region->extents.x2) / 2 anyway.
groupRecalcTabBarPos(gw->group, (bar->region->extents.x1 + bar->region->extents.x2) / 2,
bar->region->extents.x1, bar->region->extents.x2);
}
/*
* groupInsertTabBarSlotAfter
*
*/
void groupInsertTabBarSlotAfter(GroupTabBar *bar, GroupTabBarSlot *slot, GroupTabBarSlot *prevSlot)
{
GroupTabBarSlot *next = prevSlot->next;
if (next) {
slot->next = next;
next->prev = slot;
} else {
bar->revSlots = slot;
slot->next = NULL;
}
slot->prev = prevSlot;
prevSlot->next = slot;
bar->nSlots++;
CompWindow *w = slot->window;
GROUP_WINDOW(w);
//Moving bar->region->extents.x1 / x2 as minX1 / maxX2 will work,
//because the tab-bar got wider now, so it will put it in the average between them,
//which is (bar->region->extents.x1 + bar->region->extents.x2) / 2 anyway.
groupRecalcTabBarPos(gw->group, (bar->region->extents.x1 + bar->region->extents.x2) / 2,
bar->region->extents.x1, bar->region->extents.x2);
}
/*
* groupInsertTabBarSlot
*
*/
void groupInsertTabBarSlot(GroupTabBar *bar, GroupTabBarSlot *slot)
{
if (bar->slots != NULL) {
bar->revSlots->next = slot;
slot->prev = bar->revSlots;
slot->next = NULL;
} else {
slot->prev = NULL;
slot->next = NULL;
bar->slots = slot;
}
bar->revSlots = slot;
bar->nSlots++;
CompWindow *w = slot->window;
GROUP_WINDOW(w);
//Moving bar->region->extents.x1 / x2 as minX1 / maxX2 will work,
//because the tab-bar got wider now, so it will put it in the average between them,
//which is (bar->region->extents.x1 + bar->region->extents.x2) / 2 anyway.
groupRecalcTabBarPos(gw->group, (bar->region->extents.x1 + bar->region->extents.x2) / 2,
bar->region->extents.x1, bar->region->extents.x2);
}
/*
* groupUnhookTabBarSlot
*
*/
void groupUnhookTabBarSlot(GroupTabBar *bar, GroupTabBarSlot *slot, Bool temporary)
{
GroupTabBarSlot *prev = slot->prev;
GroupTabBarSlot *next = slot->next;
if(prev)
prev->next = next;
else
bar->slots = next;
if(next)
next->prev = prev;
else
bar->revSlots = prev;
slot->prev = NULL;
slot->next = NULL;
bar->nSlots--;
CompWindow *w = slot->window;
GROUP_WINDOW(w);
GROUP_SCREEN(w->screen);
if (IS_TOP_TAB(w, gw->group) && !temporary)
{
if(next)
groupChangeTab(next, RotateRight);
else if(prev)
groupChangeTab(prev, RotateLeft);
else if(gw->group->nWins == 1)
gw->group->topTab = NULL;
if(gs->opt[GROUP_SCREEN_OPTION_UNTAB_ON_CLOSE].value.b)
groupUntabGroup(gw->group);
}
if(IS_PREV_TOP_TAB(w, gw->group) && !temporary)
gw->group->prevTopTab = NULL;
if (slot == bar->hoveredSlot)
bar->hoveredSlot = NULL;
if (slot == bar->textSlot)
{
bar->textSlot = NULL;
if (bar->textLayer->state == PaintFadeIn || bar->textLayer->state == PaintOn)
{
bar->textLayer->animationTime = (gs->opt[GROUP_SCREEN_OPTION_FADE_TEXT_TIME].value.f * 1000)
- bar->textLayer->animationTime;
bar->textLayer->state = PaintFadeOut;
}
}
//Moving bar->region->extents.x1 / x2 as minX1 / maxX2 will work,
//because the tab-bar got thiner now, so (bar->region->extents.x1 + bar->region->extents.x2) / 2
//Won't cause the new x1 / x2 to be outside the original region.
groupRecalcTabBarPos(gw->group, (bar->region->extents.x1 + bar->region->extents.x2) / 2,
bar->region->extents.x1, bar->region->extents.x2);
}
/*
* groupDeleteTabBarSlot
*
*/
void groupDeleteTabBarSlot(GroupTabBar *bar, GroupTabBarSlot *slot)
{
groupUnhookTabBarSlot(bar, slot, FALSE);
if (slot->region)
XDestroyRegion(slot->region);
if (slot->name)
free(slot->name);
CompWindow *w = slot->window;
CompScreen *s = w->screen;
GROUP_WINDOW(w);
GROUP_SCREEN(s);
if(slot == gs->draggedSlot)
{
gs->draggedSlot = NULL;
gs->dragged = FALSE;
if (gs->grabState == ScreenGrabTabDrag)
groupGrabScreen(s, ScreenGrabNone);
}
gw->slot = NULL;
free(slot);
}
/*
* groupCreateSlot
*
*/
void groupCreateSlot(GroupSelection *group, CompWindow *w)
{
if(!group->tabBar)
return;
GROUP_WINDOW(w);
GroupTabBarSlot *slot = (GroupTabBarSlot*) malloc(sizeof(GroupTabBarSlot));
slot->window = w;
slot->name = groupGetWindowTitle(slot->window);
slot->region = XCreateRegion();
groupInsertTabBarSlot(group->tabBar, slot);
gw->slot = slot;
}
#define SPRING_K gs->opt[GROUP_SCREEN_OPTION_TAB_DRAG_SPRING_K].value.f
#define FRICTION gs->opt[GROUP_SCREEN_OPTION_TAB_DRAG_FRICTION].value.f
#define SIZE gs->opt[GROUP_SCREEN_OPTION_THUMB_SIZE].value.i
#define BORDER gs->opt[GROUP_SCREEN_OPTION_BORDER_RADIUS].value.i
#define Y_START_MOVE gs->opt[GROUP_SCREEN_OPTION_TAB_DRAG_Y_DISTANCE].value.i
#define SPEED_LIMIT gs->opt[GROUP_SCREEN_OPTION_TAB_DRAG_SPEED_LIMIT].value.i
/*
* groupSpringForce
*
*/
static int groupSpringForce(CompScreen *s, int centerX, int springX)
{
GROUP_SCREEN(s);
// Each slot has a spring attached to it, starting at springX, and ending at the center of the slot (centerX).
// The spring will cause the slot to move, using the well-known physical formula F = k * dl...
return -SPRING_K * (centerX - springX);
}
/*
* groupDraggedSlotForce
*
*/
static int groupDraggedSlotForce(CompScreen *s, int distanceX, int distanceY)
{
// The dragged slot will make the slot move, to get DnD animations (slots will make room for the newly inserted slot).
// As the dragged slot is closer to the slot, it will put more force on the slot, causing it to make room for the dragged slot...
// But if the dragged slot gets too close to the slot, they are going to be reordered soon, so the force will get lower.
// If the dragged slot is in the other side of the slot, it will have to make force in the opposite direction.
// So we the needed funtion is an odd function that goes up at first, and down after that.
// Sinus is a function like that... :)
// The maximum is got when x = (x1 + x2) / 2, in this case: x = SIZE + BORDER.
// Because of that, for x = SIZE + BORDER, we get a force of SPRING_K * (SIZE + BORDER) / 2.
// That equals to the force we get from the the spring.
// This way, the slot won't move when its distance from the dragged slot is SIZE + BORDER (which is the default distance between slots).
GROUP_SCREEN(s);
float a = SPRING_K * (SIZE + BORDER) / 2; // The maximum value.
float b = PI / (2 * SIZE + 2 * BORDER); // This will make distanceX == 2 * (SIZE + BORDER) to get 0,
// and distanceX == (SIZE + BORDER) to get the maximum.
// If there is some distance between the slots in the y axis, the slot should get less force...
// For this, we change max to a lower value, using a simple linear function.
if(distanceY < Y_START_MOVE)
a *= 1.0f - (float)distanceY / Y_START_MOVE;
else
a = 0;
if(abs(distanceX) < 2 * (SIZE + BORDER))
return a * sin(b * distanceX);
else
return 0;
}
/*
* groupApplyFriction
*
*/
static void groupApplyFriction(CompScreen *s, int *speed)
{
GROUP_SCREEN(s);
if(abs(*speed) < FRICTION)
*speed = 0;
else if(*speed > 0)
*speed -= FRICTION;
else if(*speed < 0)
*speed += FRICTION;
}
/*
* groupApplySpeedLimit
*
*/
static void groupApplySpeedLimit(CompScreen *s, int *speed)
{
GROUP_SCREEN(s);
if(*speed > SPEED_LIMIT)
*speed = SPEED_LIMIT;
else if(*speed < -SPEED_LIMIT)
*speed = - SPEED_LIMIT;
}
/*
* groupApplyForces
*
*/
void groupApplyForces(CompScreen *s, GroupTabBar *bar, GroupTabBarSlot* draggedSlot)
{
GROUP_SCREEN(s);
GroupTabBarSlot* slot, *slot2;
int centerX, centerY;
int draggedCenterX, draggedCenterY;
if (draggedSlot) {
int vx, vy;
groupGetDrawOffsetForSlot(draggedSlot, &vx, &vy);
draggedCenterX = ((draggedSlot->region->extents.x1 + draggedSlot->region->extents.x2) / 2) + vx;
draggedCenterY = ((draggedSlot->region->extents.y1 + draggedSlot->region->extents.y2) / 2) + vy;
} else {
draggedCenterX = 0;
draggedCenterY = 0;
}
bar->leftSpeed += groupSpringForce(s, bar->region->extents.x1, bar->leftSpringX);
bar->rightSpeed += groupSpringForce(s, bar->region->extents.x2, bar->rightSpringX);
if (draggedSlot)
{
int leftForce = groupDraggedSlotForce(s, bar->region->extents.x1 - SIZE / 2 - draggedCenterX,
abs((bar->region->extents.y1 + bar->region->extents.y2) / 2 - draggedCenterY));
int rightForce = groupDraggedSlotForce(s, bar->region->extents.x2 + SIZE / 2 - draggedCenterX,
abs((bar->region->extents.y1 + bar->region->extents.y2) / 2 - draggedCenterY));
if(leftForce < 0)
bar->leftSpeed += leftForce;
if(rightForce > 0)
bar->rightSpeed += rightForce;
}
for(slot = bar->slots; slot; slot = slot->next)
{
centerX = (slot->region->extents.x1 + slot->region->extents.x2) / 2;
centerY = (slot->region->extents.y1 + slot->region->extents.y2) / 2;
slot->speed += groupSpringForce(s, centerX, slot->springX);
if(draggedSlot && draggedSlot != slot)
{
int draggedSlotForce = groupDraggedSlotForce(s, centerX - draggedCenterX,
abs(centerY - draggedCenterY));
slot->speed += draggedSlotForce ;
slot2 = NULL;
if(draggedSlotForce < 0)
{
slot2 = slot->prev;
bar->leftSpeed += draggedSlotForce;
}
else if(draggedSlotForce > 0)
{
slot2 = slot->next;
bar->rightSpeed += draggedSlotForce;
}
for( ; slot2; slot2 = (draggedSlotForce < 0)? slot2->prev: slot2->next)
{
if(slot2 != draggedSlot)
slot2->speed += draggedSlotForce;
}
}
}
for(slot = bar->slots; slot; slot = slot->next)
{
groupApplyFriction(s, &slot->speed);
groupApplySpeedLimit(s, &slot->speed);
}
groupApplyFriction(s, &bar->leftSpeed);
groupApplySpeedLimit(s, &bar->leftSpeed);
groupApplyFriction(s, &bar->rightSpeed);
groupApplySpeedLimit(s, &bar->rightSpeed);
}
/*
* groupApplySpeeds
*
*/
void groupApplySpeeds(CompScreen* s, GroupTabBar* bar, int msSinceLastRepaint)
{
GROUP_SCREEN(s);
GroupTabBarSlot* slot;
int move;
XRectangle box;
Bool updateTabBar = FALSE;
box.x = bar->region->extents.x1;
box.y = bar->region->extents.y1;
box.width = bar->region->extents.x2 - bar->region->extents.x1;
box.height = bar->region->extents.y2 - bar->region->extents.y1;
bar->leftMsSinceLastMove += msSinceLastRepaint;
bar->rightMsSinceLastMove += msSinceLastRepaint;
// Left
move = bar->leftSpeed * bar->leftMsSinceLastMove / 1000;
if(move)
{
box.x += move;
box.width -= move;
bar->leftMsSinceLastMove = 0;
updateTabBar = TRUE;
}
else if(bar->leftSpeed == 0 && bar->region->extents.x1 != bar->leftSpringX &&
SPRING_K * abs(bar->region->extents.x1 - bar->leftSpringX) < FRICTION)
{
// Friction is preventing from the left border to get to its original position.
box.x += bar->leftSpringX - bar->region->extents.x1;
box.width -= bar->leftSpringX - bar->region->extents.x1;
bar->leftMsSinceLastMove = 0;
updateTabBar = TRUE;
}
else if(bar->leftSpeed == 0)
bar->leftMsSinceLastMove = 0;
//Right
move = bar->rightSpeed * bar->rightMsSinceLastMove / 1000;
if(move)
{
box.width += move;
bar->rightMsSinceLastMove = 0;
updateTabBar = TRUE;
}
else if(bar->rightSpeed == 0 && bar->region->extents.x2 != bar->rightSpringX &&
SPRING_K * abs(bar->region->extents.x2 - bar->rightSpringX) < FRICTION)
{
// Friction is preventing from the right border to get to its original position.
box.width += bar->leftSpringX - bar->region->extents.x1;
bar->leftMsSinceLastMove = 0;
updateTabBar = TRUE;
}
else if(bar->rightSpeed == 0)
bar->rightMsSinceLastMove = 0;
if(updateTabBar)
{
EMPTY_REGION(bar->region);
XUnionRectWithRegion(&box, bar->region, bar->region);
}
for(slot = bar->slots; slot; slot = slot->next)
{
slot->msSinceLastMove += msSinceLastRepaint;
move = slot->speed * slot->msSinceLastMove / 1000;
if(move)
{
XOffsetRegion (slot->region,
move, 0);
slot->msSinceLastMove = 0;
}
else if(slot->speed == 0 && (slot->region->extents.x1 + slot->region->extents.x2) / 2 != slot->springX &&
SPRING_K * abs((slot->region->extents.x1 + slot->region->extents.x2) / 2 - slot->springX) < FRICTION)
{
// Friction is preventing from the slot to get to its original position.
XOffsetRegion (slot->region,
slot->springX - (slot->region->extents.x1 + slot->region->extents.x2) / 2, 0);
slot->msSinceLastMove = 0;
}
else if(slot->speed == 0)
slot->msSinceLastMove = 0;
}
}
/*
* groupInitTabBar
*
*/
void groupInitTabBar(GroupSelection *group, CompWindow* topTab)
{
// error
if (group->tabBar)
return;
GroupTabBar *bar = (GroupTabBar*) malloc(sizeof(GroupTabBar));
bar->slots = NULL;
bar->nSlots = 0;
bar->state = PaintOff;
bar->animationTime = 0;
bar->timeoutHandle = 0;
bar->textLayer = NULL;
bar->bgLayer = NULL;
bar->selectionLayer = NULL;
bar->hoveredSlot = NULL;
bar->textSlot = NULL;
group->tabBar = bar;
bar->region = XCreateRegion();
int i;
for(i = 0; i < group->nWins; i++)
groupCreateSlot(group, group->windows[i]);
groupRecalcTabBarPos(group, WIN_X(topTab) + WIN_WIDTH(topTab) / 2,
WIN_X(topTab), WIN_X(topTab) + WIN_WIDTH(topTab));
}
/*
* groupDeleteTabBar
*
*/
void groupDeleteTabBar(GroupSelection *group)
{
GroupTabBar *bar = group->tabBar;
groupDestroyCairoLayer(group->screen, bar->textLayer);
groupDestroyCairoLayer(group->screen, bar->bgLayer);
groupDestroyCairoLayer(group->screen, bar->selectionLayer);
groupDestroyInputPreventionWindow(group);
if (bar->timeoutHandle)
compRemoveTimeout(bar->timeoutHandle);
while (bar->slots)
groupDeleteTabBarSlot(bar, bar->slots);
if (bar->region)
XDestroyRegion(bar->region);
free(bar);
group->tabBar = NULL;
}
/*
* groupInitTab
*
*/
Bool
groupInitTab(CompDisplay * d, CompAction * action, CompActionState state,
CompOption * option, int nOption)
{
CompWindow *w = findWindowAtDisplay(d, d->activeWindow);
Bool allowUntab = TRUE;
if (!w)
return TRUE;
GROUP_WINDOW(w);
if (gw->inSelection)
{
groupGroupWindows(d, action, state, option, nOption);
allowUntab = FALSE; // If the window was selected, we don't want to untab the group.
// because the user probably wanted to tab the selected windows.
}
if (!gw->group)
return TRUE;
if (gw->group->tabbingState != PaintOff)
groupSyncWindows(gw->group);
if (!gw->group->tabBar) {
groupTabGroup(w);
} else if (allowUntab) {
groupUntabGroup(gw->group);
}
damageScreen(w->screen);
return TRUE;
}
/*
* groupChangeTabLeft
*
*/
Bool
groupChangeTabLeft(CompDisplay * d, CompAction * action, CompActionState state,
CompOption * option, int nOption)
{
CompWindow *w = findWindowAtDisplay(d, d->activeWindow);
CompWindow *topTab = w;
if(!w)
return TRUE;
GROUP_WINDOW(w);
GROUP_SCREEN(w->screen);
if(!gw->slot || !gw->group)
return TRUE;
if(gw->group->nextTopTab)
topTab = NEXT_TOP_TAB(gw->group);
else if(gw->group->topTab)
topTab = TOP_TAB(gw->group); //If there are no tabbing animations, topTab is never NULL.
gw = GET_GROUP_WINDOW(topTab, gs);
if(gw->slot->prev)
return groupChangeTab(gw->slot->prev, RotateLeft);
else
return groupChangeTab(gw->group->tabBar->revSlots, RotateLeft);
}
/*
* groupChangeTabRight
*
*/
Bool
groupChangeTabRight(CompDisplay * d, CompAction * action, CompActionState state,
CompOption * option, int nOption)
{
CompWindow *w = findWindowAtDisplay(d, d->activeWindow);
CompWindow *topTab = w;
if(!w)
return TRUE;
GROUP_WINDOW(w);
GROUP_SCREEN(w->screen);
if(!gw->slot || !gw->group)
return TRUE;
if(gw->group->nextTopTab)
topTab = NEXT_TOP_TAB(gw->group);
else if(gw->group->topTab)
topTab = TOP_TAB(gw->group); //If there are no tabbing animations, topTab is never NULL.
gw = GET_GROUP_WINDOW(topTab, gs);
if(gw->slot->next)
return groupChangeTab(gw->slot->next, RotateRight);
else
return groupChangeTab(gw->group->tabBar->slots, RotateRight);
}
/*
* groupSwitchTopTabInput
*
*/
void
groupSwitchTopTabInput(GroupSelection *group, Bool enable)
{
if(!group->tabBar || !HAS_TOP_WIN(group))
return;
if (!group->inputPrevention)
groupCreateInputPreventionWindow(group);
if (!enable)
XMapWindow(group->screen->display->display, group->inputPrevention);
else
XUnmapWindow(group->screen->display->display, group->inputPrevention);
}
/*
* groupUpdateInputPreventionWindow
*
*/
void
groupUpdateInputPreventionWindow(GroupSelection *group)
{
XWindowChanges xwc;
if(!group->tabBar || !HAS_TOP_WIN(group))
return;
if (!group->inputPrevention)
groupCreateInputPreventionWindow(group);
CompWindow *w = TOP_TAB(group);
xwc.x = group->tabBar->leftSpringX;
xwc.y = group->tabBar->region->extents.y1;
xwc.width = group->tabBar->rightSpringX - group->tabBar->leftSpringX;
xwc.height = group->tabBar->region->extents.y2 - group->tabBar->region->extents.y1;
xwc.stack_mode = Above;
xwc.sibling = w->id;
XConfigureWindow(group->screen->display->display, group->inputPrevention,
CWSibling | CWStackMode | CWX | CWY | CWWidth | CWHeight, &xwc);
}
/*
* groupCreateInputPreventionWindow
*
*/
void groupCreateInputPreventionWindow(GroupSelection* group)
{
if (!group->inputPrevention) //For preventing a memory leak.
{
XSetWindowAttributes attrib;
attrib.override_redirect = TRUE;
attrib.event_mask = ButtonPressMask;
group->inputPrevention = XCreateWindow(group->screen->display->display,
group->screen->root, -100, -100, 1, 1, 0, CopyFromParent,
InputOnly, CopyFromParent, CWOverrideRedirect | CWEventMask, &attrib);
}
}
/*
* groupDestroyInputPreventionWindow
*
*/
void groupDestroyInputPreventionWindow(GroupSelection* group)
{
if(group->inputPrevention)
{
XDestroyWindow(group->screen->display->display, group->inputPrevention);
group->inputPrevention = None;
}
//
// if (group->tabBar)
// group->tabBar->lastHoveredSlot = NULL;
}
syntax highlighted by Code2HTML, v. 0.9.1