diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 0e5cbdc8b..4447bff32 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -220,7 +220,7 @@ AppDisplay.prototype = { // Creates an AppDisplayItem based on itemInfo, which is expected be a GAppInfo object. _createDisplayItem: function(itemInfo) { - return new AppDisplayItem(itemInfo, this._width); + return new AppDisplayItem(itemInfo, this._columnWidth); } }; diff --git a/js/ui/docDisplay.js b/js/ui/docDisplay.js index b921bd212..c23d91c44 100644 --- a/js/ui/docDisplay.js +++ b/js/ui/docDisplay.js @@ -205,7 +205,7 @@ DocDisplay.prototype = { // Creates a DocDisplayItem based on itemInfo, which is expected be a GtkRecentInfo object. _createDisplayItem: function(itemInfo) { - return new DocDisplayItem(itemInfo, this._width); + return new DocDisplayItem(itemInfo, this._columnWidth); } }; diff --git a/js/ui/genericDisplay.js b/js/ui/genericDisplay.js index 8ea31dfc2..d6c2392f0 100644 --- a/js/ui/genericDisplay.js +++ b/js/ui/genericDisplay.js @@ -19,9 +19,10 @@ const ITEM_DISPLAY_SELECTED_BACKGROUND_COLOR = new Clutter.Color(); ITEM_DISPLAY_SELECTED_BACKGROUND_COLOR.from_pixel(0x00ff0055); const ITEM_DISPLAY_HEIGHT = 50; +const MIN_ITEM_DISPLAY_WIDTH = 230; const ITEM_DISPLAY_ICON_SIZE = 48; const ITEM_DISPLAY_PADDING = 1; -const ITEM_DISPLAY_MARGIN = 4; +const COLUMN_GAP = 6; /* This is a virtual class that represents a single display item containing * a name, a description, and an icon. It allows selecting an item and represents @@ -144,9 +145,15 @@ function GenericDisplay(width, height) { GenericDisplay.prototype = { _init : function(width, height) { this._search = ''; - this._width = width; - this._height = height; - this._grid = new Tidy.Grid({width: width, height: height}); + this._width = null; + this._height = null; + this._columnWidth = null; + this._maxColumns = null; + this._maxItems = null; + this._setDimensionsAndMaxItems(width, height); + this._grid = new Tidy.Grid({width: this._width, height: this._height}); + this._grid.column_major = true; + this._grid.column_gap = COLUMN_GAP; // map where Object represents the item info this._allItems = {}; // map @@ -156,8 +163,6 @@ GenericDisplay.prototype = { this._activatedItem = null; this._selectedIndex = -1; this._keepDisplayCurrent = false; - // TODO: this should be Math.floor, but right now we get too few items if we apply it - this._maxItems = this._height / (ITEM_DISPLAY_HEIGHT + ITEM_DISPLAY_MARGIN); this.actor = this._grid; }, @@ -249,6 +254,14 @@ GenericDisplay.prototype = { } }, + // Readjusts display layout and the items displayed based on the new dimensions. + updateDimensions: function(width, height) { + this._setDimensionsAndMaxItems(width, height); + this._grid.width = this._width; + this._grid.height = this._height; + this._redisplay(); + }, + // Updates the displayed items and makes the display actor visible. show: function() { this._keepDisplayCurrent = true; @@ -259,6 +272,7 @@ GenericDisplay.prototype = { // Hides the display actor. hide: function() { this._grid.hide(); + this._removeAllDisplayItems(); this._keepDisplayCurrent = false; }, @@ -289,9 +303,17 @@ GenericDisplay.prototype = { // Removes an item identifed by the itemId from the displayed items. _removeDisplayItem: function(itemId) { let displayItem = this._displayedItems[itemId]; + let displayItemIndex = this._getIndexOfDisplayedActor(displayItem.actor); + + if (this.hasSelected() && this._displayedItemsCount == 1) { + this.unsetSelected(); + } else if (this.hasSelected() && displayItemIndex < this._selectedIndex) { + this.selectUp(); + } + displayItem.actor.destroy(); delete this._displayedItems[itemId]; - this._displayedItemsCount--; + this._displayedItemsCount--; }, // Removes all displayed items. @@ -305,13 +327,18 @@ GenericDisplay.prototype = { if (!this._keepDisplayCurrent) return; + // When generating a new list to display, we first remove all the old + // displayed items which will unset the selection. So we need + // to keep a flag which indicates if this display had the selection. + let hadSelected = this.hasSelected(); + this._refreshCache(); if (!this._search) this._setDefaultList(); else this._doSearchFilter(); - if (this.hasSelected()) { + if (hadSelected) { this._selectedIndex = -1; this.selectFirstItem(); } @@ -351,6 +378,20 @@ GenericDisplay.prototype = { //// Private methods //// + // Sets this._width, this._height, this._maxColumns, this._columnWidth, and + // this._maxItems based on the space available for the display and the number + // of items it can fit. + _setDimensionsAndMaxItems: function(width, height) { + this._width = width; + let maxItemsInColumn = Math.floor(height / ITEM_DISPLAY_HEIGHT); + // we always want to try to display at lease one column, even if its + // width is less then MIN_ITEM_DISPLAY_WIDTH + this._maxColumns = Math.max(Math.floor(width / (MIN_ITEM_DISPLAY_WIDTH + COLUMN_GAP)), 1); + this._columnWidth = (width - COLUMN_GAP * (this._maxColumns - 1)) / this._maxColumns; + this._maxItems = maxItemsInColumn * this._maxColumns; + this._height = maxItemsInColumn * ITEM_DISPLAY_HEIGHT; + }, + // Applies the search string to the list of items to find matches, // and displays up to this._maxItems that matched. _doSearchFilter: function() { diff --git a/js/ui/overlay.js b/js/ui/overlay.js index 80a5441d1..569abbae6 100644 --- a/js/ui/overlay.js +++ b/js/ui/overlay.js @@ -11,6 +11,7 @@ const Tweener = imports.tweener.tweener; const AppDisplay = imports.ui.appDisplay; const DocDisplay = imports.ui.docDisplay; +const GenericDisplay = imports.ui.genericDisplay; const Main = imports.ui.main; const Panel = imports.ui.panel; const Workspaces = imports.ui.workspaces; @@ -18,11 +19,12 @@ const Workspaces = imports.ui.workspaces; const OVERLAY_BACKGROUND_COLOR = new Clutter.Color(); OVERLAY_BACKGROUND_COLOR.from_pixel(0x000000ff); +const LABEL_HEIGHT = 16; const SIDESHOW_PAD = 6; const SIDESHOW_PAD_BOTTOM = 40; const SIDESHOW_MIN_WIDTH = 250; -const SIDESHOW_SECTION_PAD = 10; -const SIDESHOW_SECTION_LABEL_PAD_BOTTOM = 6; +const SIDESHOW_SECTION_MARGIN = 10; +const SIDESHOW_SECTION_LABEL_MARGIN_BOTTOM = 6; const SIDESHOW_SEARCH_BG_COLOR = new Clutter.Color(); SIDESHOW_SEARCH_BG_COLOR.from_pixel(0xffffffff); const SIDESHOW_TEXT_COLOR = new Clutter.Color(); @@ -34,6 +36,10 @@ const ANIMATION_TIME = 0.5; // How much of the screen the workspace grid takes up const WORKSPACE_GRID_SCALE = 0.75; +// How much of the screen the workspace grid takes up when it is moved aside to provide space +// for an expanded 'More' view +const WORKSPACE_GRID_ASIDE_SCALE = 0.1; + // Padding around workspace grid / Spacing between Sideshow and Workspaces const WORKSPACE_GRID_PADDING = 10; @@ -45,6 +51,10 @@ Sideshow.prototype = { _init : function(parent, width) { let me = this; + this._moreMode = false; + + this._width = width; + let global = Shell.Global.get(); this.actor = new Clutter.Group(); parent.add_actor(this.actor); @@ -131,39 +141,56 @@ Sideshow.prototype = { return false; }); - let appsText = new Clutter.Label({ color: SIDESHOW_TEXT_COLOR, + this._appsText = new Clutter.Label({ color: SIDESHOW_TEXT_COLOR, font_name: "Sans Bold 14px", text: "Applications", x: SIDESHOW_PAD, - y: this._searchEntry.y + this._searchEntry.height + SIDESHOW_SECTION_PAD, - height: 16}); - this.actor.add_actor(appsText); + y: this._searchEntry.y + this._searchEntry.height + SIDESHOW_SECTION_MARGIN, + height: LABEL_HEIGHT}); + this.actor.add_actor(this._appsText); - let sectionLabelHeight = appsText.height + SIDESHOW_SECTION_LABEL_PAD_BOTTOM - let menuY = appsText.y + sectionLabelHeight; + let sectionLabelHeight = LABEL_HEIGHT + SIDESHOW_SECTION_LABEL_MARGIN_BOTTOM; + let menuY = this._appsText.y + sectionLabelHeight; - let itemDisplayHeight = global.screen_height - menuY - SIDESHOW_SECTION_PAD - sectionLabelHeight - SIDESHOW_PAD_BOTTOM; - this._appDisplay = new AppDisplay.AppDisplay(width, itemDisplayHeight / 2); + // extra LABEL_HEIGHT is for the More link + this._itemDisplayHeight = global.screen_height - menuY - SIDESHOW_SECTION_MARGIN - sectionLabelHeight - SIDESHOW_PAD_BOTTOM - LABEL_HEIGHT; + this._appDisplay = new AppDisplay.AppDisplay(width, this._itemDisplayHeight / 2); this._appDisplay.actor.x = SIDESHOW_PAD; this._appDisplay.actor.y = menuY; this.actor.add_actor(this._appDisplay.actor); - let docsText = new Clutter.Label({ color: SIDESHOW_TEXT_COLOR, + this._moreAppsText = new Clutter.Label({ color: SIDESHOW_TEXT_COLOR, + font_name: "Sans Bold 14px", + text: "More...", + y: this._appDisplay.actor.y + this._appDisplay.actor.height, + height: LABEL_HEIGHT, + reactive: true}); + + // This sets right-alignment manually. + this._moreAppsText.x = width - this._moreAppsText.width + SIDESHOW_PAD; + this.actor.add_actor(this._moreAppsText); + + this._docsText = new Clutter.Label({ color: SIDESHOW_TEXT_COLOR, font_name: "Sans Bold 14px", text: "Recent Documents", x: SIDESHOW_PAD, - y: menuY + (itemDisplayHeight / 2) + SIDESHOW_SECTION_PAD, - height: 16}); - this.actor.add_actor(docsText); + y: this._moreAppsText.y + this._moreAppsText.height + SIDESHOW_SECTION_MARGIN, + height: LABEL_HEIGHT}); + this.actor.add_actor(this._docsText); - this._docDisplay = new DocDisplay.DocDisplay(width, itemDisplayHeight / 2); + this._docDisplay = new DocDisplay.DocDisplay(width, this._itemDisplayHeight - this._appDisplay.actor.height); this._docDisplay.actor.x = SIDESHOW_PAD; - this._docDisplay.actor.y = docsText.y + docsText.height + 6; + this._docDisplay.actor.y = this._docsText.y + sectionLabelHeight; this.actor.add_actor(this._docDisplay.actor); + // When we are sliding out documents for the applcations 'More' mode, we need to know what fraction of the + // animation time we'll spend sliding out the "Recent Documents" section header, so that we can fully clip + // the document items in the remaining fraction of time. We do the animation in a linear fashion to make the + // two stage tweening process look right. + this._docsTextAnimationTimeRatio = me._docsText.height / me._docDisplay.actor.height; + /* Proxy the activated signals */ this._appDisplay.connect('activated', function(appDisplay) { - me._searchEntry.text = ''; // we allow clicking on an item to launch it, and this unsets the selection // so that we can move it to the item that was clicked on me._appDisplay.unsetSelected(); @@ -172,7 +199,6 @@ Sideshow.prototype = { me.emit('activated'); }); this._docDisplay.connect('activated', function(docDisplay) { - me._searchEntry.text = ''; // we allow clicking on an item to launch it, and this unsets the selection // so that we can move it to the item that was clicked on me._appDisplay.unsetSelected(); @@ -191,19 +217,150 @@ Sideshow.prototype = { if (!me._docDisplay.hasSelected() && !me._appDisplay.hasSelected()) me._appDisplay.selectFirstItem(); }); + + this._moreAppsText.connect('button-press-event', + function(o, event) { + if (me._moreMode) { + me._unsetMoreMode(); + } else { + me._setMoreMode(); + } + return true; + }); }, show: function() { + this._appDisplay.show(); + this._docDisplay.show(); this._appDisplay.selectFirstItem(); if (!this._appDisplay.hasSelected()) this._docDisplay.selectFirstItem(); else this._docDisplay.unsetSelected(); - this._appDisplay.show(); - this._docDisplay.show(); }, hide: function() { + this._appDisplay.hide(); + this._docDisplay.hide(); + this._searchEntry.text = ''; + this._unsetMoreMode(); + }, + + // Sets the 'More' mode for browsing applications. Slides down the documents section. Gives more space to + // the applications section once sliding of the documents section is completed. + _setMoreMode: function() { + if (this._moreMode) + return; + + this._moreMode = true; + + if (!this._docDisplay.actor.has_clip) + this._docDisplay.actor.set_clip(0, 0, this._docDisplay.actor.width, this._docDisplay.actor.height); + + // Move the selection to the applications section if it was in the docs section. + this._docDisplay.unsetSelected(); + if (!this._appDisplay.hasSelected()) + this._appDisplay.selectFirstItem(); + + this._moreAppsText.hide(); + + Tweener.addTween(this._docDisplay.actor, + { y: this._docDisplay.actor.y + this._docDisplay.actor.height, + clipHeight: 0, + time: ANIMATION_TIME * (1 - this._docsTextAnimationTimeRatio), + transition: "linear" + }); + Tweener.addTween(this._docsText, + { y: this._docsText.y + this._docDisplay.actor.height, + time: ANIMATION_TIME * (1 - this._docsTextAnimationTimeRatio), + transition: "linear", + onComplete: this._removeDocsSection, + onCompleteScope: this + }); + + this.emit('more-activated'); + }, + + // Unsets the 'More' mode for browsing applications. Updates applications section to have + // smaller dimensions. Slides in the documents section. + _unsetMoreMode: function() { + if (!this._moreMode) + return; + + this._moreMode = false; + + this._moreAppsText.hide(); + this._updateAppsSection(); + + this._docDisplay.show(); + Tweener.addTween(this._docsText, + { y: this._docsText.y - this._docsText.height, + clipHeight: this._docsText.height, + time: ANIMATION_TIME * this._docsTextAnimationTimeRatio, + transition: "linear", + onComplete: this._restoreDocsSection, + onCompleteScope: this + }); + this.emit('less-activated'); + }, + + // Completes sliding out the documents section and hides it so that it doesn't + // get updated on new searchs. Once that's completed, updates the dimensions of + // the applications section. + _removeDocsSection: function() { + this._docDisplay.hide(); + + Tweener.addTween(this._docsText, + { y: this._docsText.y + this._docsText.height, + clipHeight: 0, + time: ANIMATION_TIME * this._docsTextAnimationTimeRatio, + transition: "linear", + onComplete: this._updateAppsSection, + onCompleteScope: this + }); + }, + + // Completes restoring the documents section. + _restoreDocsSection: function() { + Tweener.addTween(this._docsText, + { y: this._docsText.y - this._docDisplay.actor.height, + time: ANIMATION_TIME * (1 - this._docsTextAnimationTimeRatio), + transition: "linear" + }); + Tweener.addTween(this._docDisplay.actor, + { y: this._docDisplay.actor.y - this._docDisplay.actor.height, + clipHeight: this._docDisplay.actor.height, + time: ANIMATION_TIME * (1 - this._docsTextAnimationTimeRatio), + transition: "linear", + onComplete: this._onDocsSectionRestored, + onCompleteScope: this + }); + }, + + // Selects the first item in the documents section if applications section has no items. + _onDocsSectionRestored: function() { + if (!this._appDisplay.hasItems()) + this._docDisplay.selectFirstItem(); + }, + + // Updates the applications section display and the 'More...' or 'Less...' control associated with it + // depending on the current mode. This function must only be called once after the 'More' mode has been + // changed, which is ensured by _setMoreMode() and _unsetMoreMode() functions. + _updateAppsSection: function() { + let additionalWidth = this._width + GenericDisplay.COLUMN_GAP; + if (this._moreMode) { + this._appDisplay.updateDimensions(this._width + additionalWidth, + this._itemDisplayHeight + LABEL_HEIGHT * 2 + SIDESHOW_SECTION_LABEL_MARGIN_BOTTOM); + this._moreAppsText.x = this._moreAppsText.x + additionalWidth; + this._moreAppsText.y = this._appDisplay.actor.y + this._appDisplay.actor.height; + this._moreAppsText.text = "Less..."; + } else { + this._appDisplay.updateDimensions(this._width, this._itemDisplayHeight / 2); + this._moreAppsText.x = this._moreAppsText.x - additionalWidth; + this._moreAppsText.y = this._appDisplay.actor.y + this._appDisplay.actor.height; + this._moreAppsText.text = "More..."; + } + this._moreAppsText.show(); } }; Signals.addSignalMethods(Sideshow.prototype); @@ -237,12 +394,21 @@ Overlay.prototype = { let sideshowWidth = screenWidth - (screenWidth * WORKSPACE_GRID_SCALE) - 2 * WORKSPACE_GRID_PADDING; this._sideshow = new Sideshow(this._group, sideshowWidth); + this._workspaces = null; this._sideshow.connect('activated', function(sideshow) { // TODO - have some sort of animation/effect while // transitioning to the new app. We definitely need // startup-notification integration at least. me._deactivate(); }); + this._sideshow.connect('more-activated', function(sideshow) { + if (me._workspaces != null) + me._workspaces.moveAside(); + }); + this._sideshow.connect('less-activated', function(sideshow) { + if (me._workspaces != null) + me._workspaces.moveToCenter(); + }); }, show : function() { @@ -289,13 +455,26 @@ Overlay.prototype = { this.visible = false; global.window_group.show(); - this._group.hide(); this._workspaces.destroy(); this._workspaces = null; + + this._sideshow.hide(); + this._group.hide(); }, _deactivate : function() { Main.hide_overlay(); } }; + +Tweener.registerSpecialProperty("clipHeight", _clipHeightGet, _clipHeightSet); + +function _clipHeightGet(actor) { + let [xOffset, yOffset, clipHeight, clipWidth] = actor.get_clip(); + return clipHeight; +} + +function _clipHeightSet(actor, clipHeight) { + actor.set_clip(0, 0, actor.width, clipHeight); +} diff --git a/js/ui/workspaces.js b/js/ui/workspaces.js index 0723b7768..11efa0ea5 100644 --- a/js/ui/workspaces.js +++ b/js/ui/workspaces.js @@ -399,6 +399,8 @@ Workspace.prototype = { this._visible = false; this._frame = null; + + this.leavingOverlay = false; }, // Checks if the workspace is empty (ie, contains only a desktop window) @@ -529,7 +531,7 @@ Workspace.prototype = { transition: "easeOutQuad" }; - // workspace_relative assumes that the workspace is zooming in our out + // workspace_relative assumes that the workspace is zooming in or out if (workspaceZooming) tweenProperties['workspace_relative'] = this; @@ -576,6 +578,14 @@ Workspace.prototype = { zoomToOverlay : function() { // Move the workspace into size/position this.actor.set_position(this.fullSizeX, this.fullSizeY); + + this.updateInOverlay(); + + this._visible = true; + }, + + // Animates the display of a workspace and its windows to have the current dimensions and position. + updateInOverlay : function() { Tweener.addTween(this.actor, { x: this.gridX, y: this.gridY, @@ -587,12 +597,12 @@ Workspace.prototype = { // Likewise for each of the windows in the workspace. this._positionWindows(true); - - this._visible = true; }, // Animates the return from overlay mode zoomFromOverlay : function() { + this.leavingOverlay = true; + Tweener.addTween(this.actor, { x: this.fullSizeX, y: this.fullSizeY, @@ -615,7 +625,9 @@ Workspace.prototype = { }); } + this.leavingOverlay = false; this._visible = false; + }, // Animates grid shrinking/expanding when a row or column @@ -777,14 +789,17 @@ Workspaces.prototype = { this.actor = new Clutter.Group(); - let screenWidth = global.screen_width; let screenHeight = global.screen_height; - this._width = screenWidth * Overlay.WORKSPACE_GRID_SCALE - - 2 * Overlay.WORKSPACE_GRID_PADDING; - this._height = screenHeight * Overlay.WORKSPACE_GRID_SCALE; - this._x = screenWidth - this._width - Overlay.WORKSPACE_GRID_PADDING; - this._y = Panel.PANEL_HEIGHT + (screenHeight - this._height - Panel.PANEL_HEIGHT) / 2; + this._width = null; + this._height = null; + this._x = null; + this._y = null; + this._bottomHeight = null; + + this._setDimensions(true); + + this._bottomHeight = screenHeight - this._height - this._y; this._workspaces = []; @@ -823,13 +838,12 @@ Workspaces.prototype = { }); // Create (+) and (-) buttons - let bottomHeight = screenHeight - this._height - this._y; - this._buttonSize = Math.floor(bottomHeight * 3/5); - let plusX = this._x + this._width - this._buttonSize; - let plusY = screenHeight - Math.floor(bottomHeight * 4/5); + this._buttonSize = Math.floor(this._bottomHeight * 3/5); + this._plusX = this._x + this._width - this._buttonSize; + this._plusY = screenHeight - Math.floor(this._bottomHeight * 4/5); - let plus = new Clutter.Texture({ x: plusX, - y: plusY, + let plus = new Clutter.Texture({ x: this._plusX, + y: this._plusY, width: this._buttonSize, height: this._buttonSize, reactive: true @@ -856,6 +870,18 @@ Workspaces.prototype = { Lang.bind(this, this._activeWorkspaceChanged)); }, + // Moves the workspaces display to the side. + moveAside : function() { + this._setDimensions(false); + this._updateInOverlay(); + }, + + // Moves the workspaces display to the center. + moveToCenter : function() { + this._setDimensions(true); + this._updateInOverlay(); + }, + hide : function() { let global = Shell.Global.get(); let activeWorkspaceIndex = global.screen.get_active_workspace_index(); @@ -892,6 +918,46 @@ Workspaces.prototype = { global.window_manager.disconnect(this._switchWorkspaceNotifyId); }, + // Sets dimensions and position of the workspaces display depending on the mode + // in which it will be presented (in the center of the overlay mode or on the side). + // + // centerMode - a boolean flag indicating if the workspaces will be displayed in the center of the overlay + // + _setDimensions : function(centerMode) { + let global = Shell.Global.get(); + let screenWidth = global.screen_width; + let screenHeight = global.screen_height; + + let scalingFactor = centerMode ? Overlay.WORKSPACE_GRID_SCALE : Overlay.WORKSPACE_GRID_ASIDE_SCALE; + + this._width = screenWidth * scalingFactor - 2 * Overlay.WORKSPACE_GRID_PADDING; + this._height = screenHeight * scalingFactor; + this._x = screenWidth - this._width - Overlay.WORKSPACE_GRID_PADDING; + if (centerMode) + this._y = Panel.PANEL_HEIGHT + (screenHeight - this._height - Panel.PANEL_HEIGHT) / 2; + else + this._y = screenHeight - this._height - this._bottomHeight; + }, + + // Updates the workspaces display based on the current dimensions and position. + _updateInOverlay : function() { + let global = Shell.Global.get(); + + this._positionWorkspaces(global); + Tweener.addTween(this._backdrop, + { x: this._x, + y: this._y, + width: this._width, + height: this._height, + time: Overlay.ANIMATION_TIME, + transition: "easeOutQuad" + }); + + // Position/scale the desktop windows and their children + for (let w = 0; w < this._workspaces.length; w++) + this._workspaces[w].updateInOverlay(); + }, + // Assign grid positions to workspaces. We can't just do a simple // row-major or column-major numbering, because we don't want the // existing workspaces to get rearranged when we add a row or @@ -1091,12 +1157,12 @@ Tweener.registerSpecialPropertyModifier("workspace_relative", _workspace_relativ function _workspace_relative_modifier(workspace) { let endX, endY; - if (workspace.actor.x == workspace.fullSizeX) { + if (workspace.leavingOverlay) { + endX = workspace.fullSizeX; + endY = workspace.fullSizeY; + } else { endX = workspace.gridX; endY = workspace.gridY; - } else { - endX = workspace.fullSizeX; - endY = workspace.fullSizeY; } return [ { name: "x", @@ -1110,4 +1176,4 @@ function _workspace_relative_modifier(workspace) { function _workspace_relative_get(begin, end, time, params) { return (begin + params.begin) + time * (end + params.end - (begin + params.begin)) - params.cur(); -} \ No newline at end of file +}