appDisplay: Paginate AllView
Organize applications in AllView by pages using the new PaginatedIconGrid added previously. Pagination is generally a better pattern for collections than scrolling, as it better suits spacial memory. Hook into AppDisplay's allocation function to communicate the available size to the different views before child allocations - this is only required by the paginated view (as pages must be computed before calling get_preferred_height/get_preferred_width), but doing it for all views will guarantee that their dynamic spacing calculation is based on the same values. https://bugzilla.gnome.org/show_bug.cgi?id=706081
This commit is contained in:
parent
804c02701a
commit
754ca7c8f2
@ -934,9 +934,8 @@ StScrollBar StButton#vhandle:active {
|
|||||||
padding: 3px 31px;
|
padding: 3px 31px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.search-display > StBoxLayout,
|
.search-display > StBoxLayout,
|
||||||
.all-apps > StBoxLayout,
|
.all-apps,
|
||||||
.frequent-apps > StBoxLayout {
|
.frequent-apps > StBoxLayout {
|
||||||
/* horizontal padding to make sure scrollbars or dash don't overlap content */
|
/* horizontal padding to make sure scrollbars or dash don't overlap content */
|
||||||
padding: 0px 88px;
|
padding: 0px 88px;
|
||||||
|
@ -35,6 +35,7 @@ const INACTIVE_GRID_OPACITY = 77;
|
|||||||
const INACTIVE_GRID_OPACITY_ANIMATION_TIME = 0.15;
|
const INACTIVE_GRID_OPACITY_ANIMATION_TIME = 0.15;
|
||||||
const FOLDER_SUBICON_FRACTION = .4;
|
const FOLDER_SUBICON_FRACTION = .4;
|
||||||
|
|
||||||
|
const PAGE_SWITCH_TIME = 0.3;
|
||||||
|
|
||||||
// Recursively load a GMenuTreeDirectory; we could put this in ShellAppSystem too
|
// Recursively load a GMenuTreeDirectory; we could put this in ShellAppSystem too
|
||||||
function _loadCategory(dir, view) {
|
function _loadCategory(dir, view) {
|
||||||
@ -59,9 +60,16 @@ const BaseAppView = new Lang.Class({
|
|||||||
Name: 'BaseAppView',
|
Name: 'BaseAppView',
|
||||||
Abstract: true,
|
Abstract: true,
|
||||||
|
|
||||||
_init: function() {
|
_init: function(params, gridParams) {
|
||||||
this._grid = new IconGrid.IconGrid({ xAlign: St.Align.MIDDLE,
|
gridParams = Params.parse(gridParams, { xAlign: St.Align.MIDDLE,
|
||||||
columnLimit: MAX_COLUMNS });
|
columnLimit: MAX_COLUMNS,
|
||||||
|
fillParent: false });
|
||||||
|
params = Params.parse(params, { usePagination: false });
|
||||||
|
|
||||||
|
if(params.usePagination)
|
||||||
|
this._grid = new IconGrid.PaginatedIconGrid(gridParams);
|
||||||
|
else
|
||||||
|
this._grid = new IconGrid.IconGrid(gridParams);
|
||||||
|
|
||||||
// Standard hack for ClutterBinLayout
|
// Standard hack for ClutterBinLayout
|
||||||
this._grid.actor.x_expand = true;
|
this._grid.actor.x_expand = true;
|
||||||
@ -142,27 +150,18 @@ const BaseAppView = new Lang.Class({
|
|||||||
});
|
});
|
||||||
Signals.addSignalMethods(BaseAppView.prototype);
|
Signals.addSignalMethods(BaseAppView.prototype);
|
||||||
|
|
||||||
const AllViewLayout = new Lang.Class({
|
|
||||||
Name: 'AllViewLayout',
|
|
||||||
Extends: Clutter.BinLayout,
|
|
||||||
|
|
||||||
vfunc_get_preferred_height: function(container, forWidth) {
|
// Ignore child size requests to use the available size from the parent
|
||||||
let minBottom = 0;
|
const PagesBin = new Lang.Class({
|
||||||
let naturalBottom = 0;
|
Name: 'PagesBin',
|
||||||
|
Extends: St.Bin,
|
||||||
|
|
||||||
for (let child = container.get_first_child();
|
vfunc_get_preferred_height: function (forWidth) {
|
||||||
child;
|
return [0, 0];
|
||||||
child = child.get_next_sibling()) {
|
},
|
||||||
let childY = child.y;
|
|
||||||
let [childMin, childNatural] = child.get_preferred_height(forWidth);
|
|
||||||
|
|
||||||
if (childMin + childY > minBottom)
|
vfunc_get_preferred_width: function(forHeight) {
|
||||||
minBottom = childMin + childY;
|
return [0, 0];
|
||||||
|
|
||||||
if (childNatural + childY > naturalBottom)
|
|
||||||
naturalBottom = childNatural + childY;
|
|
||||||
}
|
|
||||||
return [minBottom, naturalBottom];
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -171,30 +170,32 @@ const AllView = new Lang.Class({
|
|||||||
Extends: BaseAppView,
|
Extends: BaseAppView,
|
||||||
|
|
||||||
_init: function() {
|
_init: function() {
|
||||||
this.parent();
|
this.parent({ usePagination: true }, null);
|
||||||
|
this._pagesBin = new PagesBin({ style_class: 'all-apps',
|
||||||
this._grid.actor.y_align = Clutter.ActorAlign.START;
|
x_expand: true,
|
||||||
this._grid.actor.y_expand = true;
|
y_expand: true,
|
||||||
|
x_fill: true,
|
||||||
|
y_fill: false,
|
||||||
|
reactive: true,
|
||||||
|
y_align: St.Align.START });
|
||||||
|
this.actor = new St.Widget({ layout_manager: new Clutter.BinLayout(),
|
||||||
|
x_expand:true, y_expand:true });
|
||||||
|
this.actor.add_actor(this._pagesBin);
|
||||||
|
|
||||||
|
this._stack = new St.Widget({ layout_manager: new Clutter.BinLayout() });
|
||||||
let box = new St.BoxLayout({ vertical: true });
|
let box = new St.BoxLayout({ vertical: true });
|
||||||
this._stack = new St.Widget({ layout_manager: new AllViewLayout() });
|
this._verticalAdjustment = new St.Adjustment();
|
||||||
|
box.set_adjustments(new St.Adjustment() /* unused */, this._verticalAdjustment);
|
||||||
|
|
||||||
|
this._currentPage = 0;
|
||||||
this._stack.add_actor(this._grid.actor);
|
this._stack.add_actor(this._grid.actor);
|
||||||
this._eventBlocker = new St.Widget({ x_expand: true, y_expand: true });
|
this._eventBlocker = new St.Widget({ x_expand: true, y_expand: true });
|
||||||
this._stack.add_actor(this._eventBlocker);
|
this._stack.add_actor(this._eventBlocker);
|
||||||
box.add(this._stack, { y_align: St.Align.START, expand: true });
|
|
||||||
|
|
||||||
this.actor = new St.ScrollView({ x_fill: true,
|
box.add_actor(this._stack);
|
||||||
y_fill: false,
|
this._pagesBin.add_actor(box);
|
||||||
y_align: St.Align.START,
|
|
||||||
x_expand: true,
|
this._pagesBin.connect('scroll-event', Lang.bind(this, this._onScroll));
|
||||||
y_expand: true,
|
|
||||||
overlay_scrollbars: true,
|
|
||||||
style_class: 'all-apps vfade' });
|
|
||||||
this.actor.add_actor(box);
|
|
||||||
this.actor.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
|
||||||
let action = new Clutter.PanAction({ interpolate: true });
|
|
||||||
action.connect('pan', Lang.bind(this, this._onPan));
|
|
||||||
this.actor.add_action(action);
|
|
||||||
|
|
||||||
this._clickAction = new Clutter.ClickAction();
|
this._clickAction = new Clutter.ClickAction();
|
||||||
this._clickAction.connect('clicked', Lang.bind(this, function() {
|
this._clickAction.connect('clicked', Lang.bind(this, function() {
|
||||||
@ -207,15 +208,30 @@ const AllView = new Lang.Class({
|
|||||||
this._currentPopup.popdown();
|
this._currentPopup.popdown();
|
||||||
}));
|
}));
|
||||||
this._eventBlocker.add_action(this._clickAction);
|
this._eventBlocker.add_action(this._clickAction);
|
||||||
|
|
||||||
|
this._availWidth = 0;
|
||||||
|
this._availHeight = 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
_onPan: function(action) {
|
goToPage: function(pageNumber) {
|
||||||
this._clickAction.release();
|
this._currentPage = pageNumber;
|
||||||
|
Tweener.addTween(this._verticalAdjustment,
|
||||||
|
{ value: this._grid.getPageY(this._currentPage),
|
||||||
|
time: PAGE_SWITCH_TIME,
|
||||||
|
transition: 'easeOutQuad' });
|
||||||
|
},
|
||||||
|
|
||||||
let [dist, dx, dy] = action.get_motion_delta(0);
|
_onScroll: function(actor, event) {
|
||||||
let adjustment = this.actor.vscroll.adjustment;
|
let direction = event.get_scroll_direction();
|
||||||
adjustment.value -= (dy / this.actor.height) * adjustment.page_size;
|
if (direction == Clutter.ScrollDirection.UP) {
|
||||||
return false;
|
if (this._currentPage > 0)
|
||||||
|
this.goToPage(this._currentPage - 1);
|
||||||
|
} else {
|
||||||
|
if (direction == Clutter.ScrollDirection.DOWN) {
|
||||||
|
if (this._currentPage < (this._grid.nPages() - 1))
|
||||||
|
this.goToPage(this._currentPage + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_getItemId: function(item) {
|
_getItemId: function(item) {
|
||||||
@ -265,17 +281,17 @@ const AllView = new Lang.Class({
|
|||||||
this._eventBlocker.reactive = isOpen;
|
this._eventBlocker.reactive = isOpen;
|
||||||
this._currentPopup = isOpen ? popup : null;
|
this._currentPopup = isOpen ? popup : null;
|
||||||
this._updateIconOpacities(isOpen);
|
this._updateIconOpacities(isOpen);
|
||||||
if (isOpen) {
|
|
||||||
this._ensureIconVisible(popup.actor);
|
|
||||||
this._grid.actor.y = popup.parentOffset;
|
|
||||||
} else {
|
|
||||||
this._grid.actor.y = 0;
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
_ensureIconVisible: function(icon) {
|
_ensureIconVisible: function(icon) {
|
||||||
Util.ensureActorVisibleInScrollView(this.actor, icon);
|
let itemPage = this._grid.getItemPage(icon);
|
||||||
|
this.goToPage(itemPage);
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateAdjustment: function(availHeight) {
|
||||||
|
this._verticalAdjustment.page_size = availHeight;
|
||||||
|
this._verticalAdjustment.upper = this._stack.height;
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateIconOpacities: function(folderOpen) {
|
_updateIconOpacities: function(folderOpen) {
|
||||||
@ -290,16 +306,41 @@ const AllView = new Lang.Class({
|
|||||||
transition: 'easeOutQuad' };
|
transition: 'easeOutQuad' };
|
||||||
Tweener.addTween(this._items[id].actor, params);
|
Tweener.addTween(this._items[id].actor, params);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Called before allocation to calculate dynamic spacing
|
||||||
|
adaptToSize: function(width, height) {
|
||||||
|
let box = new Clutter.ActorBox();
|
||||||
|
box.x1 = 0;
|
||||||
|
box.x2 = width;
|
||||||
|
box.y1 = 0;
|
||||||
|
box.y2 = height;
|
||||||
|
box = this.actor.get_theme_node().get_content_box(box);
|
||||||
|
box = this._pagesBin.get_theme_node().get_content_box(box);
|
||||||
|
box = this._grid.actor.get_theme_node().get_content_box(box);
|
||||||
|
let availWidth = box.x2 - box.x1;
|
||||||
|
let availHeight = box.y2 - box.y1;
|
||||||
|
let oldNPages = this._grid.nPages();
|
||||||
|
|
||||||
|
this._updateAdjustment(availHeight);
|
||||||
|
this._grid.updateSpacingForSize(availWidth, availHeight);
|
||||||
|
this._grid.computePages(availWidth, availHeight);
|
||||||
|
// Make sure the view doesn't have a bad adjustment value after screen size changes
|
||||||
|
// and therefore the pages computation.
|
||||||
|
if (this._availWidth != availWidth || this._availHeight != availHeight || oldNPages != this._grid.nPages())
|
||||||
|
this._verticalAdjustment.value = 0;
|
||||||
|
|
||||||
|
this._availWidth = availWidth;
|
||||||
|
this._availHeight = availHeight;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const FrequentView = new Lang.Class({
|
const FrequentView = new Lang.Class({
|
||||||
Name: 'FrequentView',
|
Name: 'FrequentView',
|
||||||
|
Extends: BaseAppView,
|
||||||
|
|
||||||
_init: function() {
|
_init: function() {
|
||||||
this._grid = new IconGrid.IconGrid({ xAlign: St.Align.MIDDLE,
|
this.parent(null, { fillParent: true });
|
||||||
fillParent: true,
|
|
||||||
columnLimit: MAX_COLUMNS });
|
|
||||||
this.actor = new St.Widget({ style_class: 'frequent-apps',
|
this.actor = new St.Widget({ style_class: 'frequent-apps',
|
||||||
x_expand: true, y_expand: true });
|
x_expand: true, y_expand: true });
|
||||||
this.actor.add_actor(this._grid.actor);
|
this.actor.add_actor(this._grid.actor);
|
||||||
@ -307,10 +348,6 @@ const FrequentView = new Lang.Class({
|
|||||||
this._usage = Shell.AppUsage.get_default();
|
this._usage = Shell.AppUsage.get_default();
|
||||||
},
|
},
|
||||||
|
|
||||||
removeAll: function() {
|
|
||||||
this._grid.removeAll();
|
|
||||||
},
|
|
||||||
|
|
||||||
loadApps: function() {
|
loadApps: function() {
|
||||||
let mostUsed = this._usage.get_most_used ("");
|
let mostUsed = this._usage.get_most_used ("");
|
||||||
for (let i = 0; i < mostUsed.length; i++) {
|
for (let i = 0; i < mostUsed.length; i++) {
|
||||||
@ -319,6 +356,19 @@ const FrequentView = new Lang.Class({
|
|||||||
let appIcon = new AppIcon(mostUsed[i]);
|
let appIcon = new AppIcon(mostUsed[i]);
|
||||||
this._grid.addItem(appIcon.actor, -1);
|
this._grid.addItem(appIcon.actor, -1);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Called before allocation to calculate dynamic spacing
|
||||||
|
adaptToSize: function(width, height) {
|
||||||
|
let box = new Clutter.ActorBox();
|
||||||
|
box.x1 = box.y1 = 0;
|
||||||
|
box.x2 = width;
|
||||||
|
box.y2 = height;
|
||||||
|
box = this.actor.get_theme_node().get_content_box(box);
|
||||||
|
box = this._grid.actor.get_theme_node().get_content_box(box);
|
||||||
|
let availWidth = box.x2 - box.x1;
|
||||||
|
let availHeight = box.y2 - box.y1;
|
||||||
|
this._grid.updateSpacingForSize(availWidth, availHeight);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -352,6 +402,21 @@ const ControlsBoxLayout = Lang.Class({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ViewStackLayout = new Lang.Class({
|
||||||
|
Name: 'ViewStackLayout',
|
||||||
|
Extends: Clutter.BinLayout,
|
||||||
|
|
||||||
|
vfunc_allocate: function (actor, box, flags) {
|
||||||
|
let availWidth = box.x2 - box.x1;
|
||||||
|
let availHeight = box.y2 - box.y1;
|
||||||
|
// Prepare children of all views for the upcoming allocation, calculate all
|
||||||
|
// the needed values to adapt available size
|
||||||
|
this.emit('allocated-size-changed', availWidth, availHeight);
|
||||||
|
this.parent(actor, box, flags);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Signals.addSignalMethods(ViewStackLayout.prototype);
|
||||||
|
|
||||||
const AppDisplay = new Lang.Class({
|
const AppDisplay = new Lang.Class({
|
||||||
Name: 'AppDisplay',
|
Name: 'AppDisplay',
|
||||||
|
|
||||||
@ -387,20 +452,19 @@ const AppDisplay = new Lang.Class({
|
|||||||
x_expand: true });
|
x_expand: true });
|
||||||
this._views[Views.ALL] = { 'view': view, 'control': button };
|
this._views[Views.ALL] = { 'view': view, 'control': button };
|
||||||
|
|
||||||
this.actor = new St.BoxLayout({ style_class: 'app-display',
|
this.actor = new St.BoxLayout ({ style_class: 'app-display',
|
||||||
vertical: true,
|
x_expand: true, y_expand: true,
|
||||||
x_expand: true, y_expand: true });
|
vertical: true });
|
||||||
|
this._viewStackLayout = new ViewStackLayout();
|
||||||
this._viewStack = new St.Widget({ layout_manager: new Clutter.BinLayout(),
|
this._viewStack = new St.Widget({ x_expand: true, y_expand: true,
|
||||||
x_expand: true, y_expand: true });
|
layout_manager: this._viewStackLayout });
|
||||||
this.actor.add(this._viewStack, { expand: true });
|
this._viewStackLayout.connect('allocated-size-changed', Lang.bind(this, this._onAllocatedSizeChanged));
|
||||||
|
this.actor.add_actor(this._viewStack, { expand: true });
|
||||||
let layout = new ControlsBoxLayout({ homogeneous: true });
|
let layout = new ControlsBoxLayout({ homogeneous: true });
|
||||||
this._controls = new St.Widget({ style_class: 'app-view-controls',
|
this._controls = new St.Widget({ style_class: 'app-view-controls',
|
||||||
layout_manager: layout });
|
layout_manager: layout });
|
||||||
layout.hookup_style(this._controls);
|
layout.hookup_style(this._controls);
|
||||||
this.actor.add(new St.Bin({ child: this._controls }));
|
this.actor.add_actor(new St.Bin({ child: this._controls }));
|
||||||
|
|
||||||
|
|
||||||
for (let i = 0; i < this._views.length; i++) {
|
for (let i = 0; i < this._views.length; i++) {
|
||||||
this._viewStack.add_actor(this._views[i].view.actor);
|
this._viewStack.add_actor(this._views[i].view.actor);
|
||||||
@ -506,7 +570,19 @@ const AppDisplay = new Lang.Class({
|
|||||||
this._showView(Views.ALL);
|
this._showView(Views.ALL);
|
||||||
this._views[Views.ALL].view.selectApp(id);
|
this._views[Views.ALL].view.selectApp(id);
|
||||||
},
|
},
|
||||||
});
|
|
||||||
|
_onAllocatedSizeChanged: function(actor, width, height) {
|
||||||
|
let box = new Clutter.ActorBox();
|
||||||
|
box.x1 = box.y1 =0;
|
||||||
|
box.x2 = width;
|
||||||
|
box.y2 = height;
|
||||||
|
box = this._viewStack.get_theme_node().get_content_box(box);
|
||||||
|
let availWidth = box.x2 - box.x1;
|
||||||
|
let availHeight = box.y2 - box.y1;
|
||||||
|
for (let i = 0; i < this._views.length; i++)
|
||||||
|
this._views[i].view.adaptToSize(availWidth, availHeight);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const AppSearchProvider = new Lang.Class({
|
const AppSearchProvider = new Lang.Class({
|
||||||
Name: 'AppSearchProvider',
|
Name: 'AppSearchProvider',
|
||||||
@ -569,7 +645,7 @@ const FolderView = new Lang.Class({
|
|||||||
Extends: BaseAppView,
|
Extends: BaseAppView,
|
||||||
|
|
||||||
_init: function() {
|
_init: function() {
|
||||||
this.parent();
|
this.parent(null, null);
|
||||||
this.actor = this._grid.actor;
|
this.actor = this._grid.actor;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -261,7 +261,6 @@ const IconGrid = new Lang.Class({
|
|||||||
let children = this._getVisibleChildren();
|
let children = this._getVisibleChildren();
|
||||||
let availWidth = box.x2 - box.x1;
|
let availWidth = box.x2 - box.x1;
|
||||||
let availHeight = box.y2 - box.y1;
|
let availHeight = box.y2 - box.y1;
|
||||||
this.updateSpacingForSize(availWidth);
|
|
||||||
let spacing = this._getSpacing();
|
let spacing = this._getSpacing();
|
||||||
let [nColumns, usedWidth] = this._computeLayout(availWidth);
|
let [nColumns, usedWidth] = this._computeLayout(availWidth);
|
||||||
|
|
||||||
@ -390,6 +389,10 @@ const IconGrid = new Lang.Class({
|
|||||||
return this._fixedSpacing ? this._fixedSpacing : this._spacing;
|
return this._fixedSpacing ? this._fixedSpacing : this._spacing;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function must to be called before iconGrid allocation,
|
||||||
|
* to know how much spacing can the grid has
|
||||||
|
*/
|
||||||
updateSpacingForSize: function(availWidth) {
|
updateSpacingForSize: function(availWidth) {
|
||||||
let spacing = this._spacing;
|
let spacing = this._spacing;
|
||||||
|
|
||||||
@ -504,5 +507,15 @@ const PaginatedIconGrid = new Lang.Class({
|
|||||||
let firstPageItem = pageNumber * this._childrenPerPage
|
let firstPageItem = pageNumber * this._childrenPerPage
|
||||||
let childBox = this._getVisibleChildren()[firstPageItem].get_allocation_box();
|
let childBox = this._getVisibleChildren()[firstPageItem].get_allocation_box();
|
||||||
return childBox.y1;
|
return childBox.y1;
|
||||||
|
},
|
||||||
|
|
||||||
|
getItemPage: function(item) {
|
||||||
|
let children = this._getVisibleChildren();
|
||||||
|
let index = children.indexOf(item);
|
||||||
|
if (index == -1) {
|
||||||
|
throw new Error('Item not found.');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Math.floor(index / this._childrenPerPage);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user