#include "group.h" /** * * Beryl group plugin * * tab.c * * Copyright : (C) 2006 by Patrick Niklaus, Roi Cohen, Danny Baumann * Authors: Patrick Niklaus * Roi Cohen * Danny Baumann * * * 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; }