Skip to content

Commit 00f4c78

Browse files
committed
menu applet: rework refreshing
This reworks refreshing so that you queue a specific refresh type (or multiple) at any point and don't worry about whether the rest of the categories physically below it need to be refreshed as well. We now preserve menu order by inserting each button type directly to the relative index it should be at. For example: a place button should be inserted after the last application button. This also queues all refreshes over 1 second from the first, and runs all queued refreshes at once in the correct order. As a result, some refreshes that were instant before are delayed now. Finally, most button actors are now hidden by default. This simplifies the visibility management with random refreshes. We now only ever have to hide actors that we have previously shown. Previously, if a refresh happened while the menu is closed we would have extra actors in the all apps category (places and recent). This is due to a optimized initial state set code that doesn't do all the work _displayButtons() does. One downside of this change is that if the menu refreshes during use, then buttons may completely disappear until a search or category select.
1 parent d2d5f8f commit 00f4c78

File tree

1 file changed

+77
-49
lines changed
  • files/usr/share/cinnamon/applets/menu@cinnamon.org

1 file changed

+77
-49
lines changed

files/usr/share/cinnamon/applets/menu@cinnamon.org/applet.js

Lines changed: 77 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ function getFavIconSize() {
5050
return Math.min(icon_size, MAX_FAV_ICON_SIZE);
5151
}
5252

53+
const RefreshFlags = Object.freeze({
54+
APP: 0b00001,
55+
FAV: 0b00010,
56+
PLACE: 0b00100,
57+
RECENT: 0b01000,
58+
SYSTEM: 0b10000
59+
});
60+
const REFRESH_ALL_MASK = 0b11111;
61+
5362
/* VisibleChildIterator takes a container (boxlayout, etc.)
5463
* and creates an array of its visible children and their index
5564
* positions. We can then work through that list without
@@ -979,7 +988,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
979988

980989
this.settings = new Settings.AppletSettings(this, "menu@cinnamon.org", instance_id);
981990

982-
this.settings.bind("show-places", "showPlaces", this._refreshBelowApps);
991+
this.settings.bind("show-places", "showPlaces", () => this.queueRefresh(RefreshFlags.PLACE));
983992

984993
this._appletEnterEventId = 0;
985994
this._appletLeaveEventId = 0;
@@ -1038,26 +1047,25 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
10381047
this._activeContextMenuParent = null;
10391048
this._activeContextMenuItem = null;
10401049
this._display();
1041-
appsys.connect('installed-changed', Lang.bind(this, this.onAppSysChanged));
1042-
AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._refreshFavs));
1043-
Main.placesManager.connect('places-updated', Lang.bind(this, this._refreshBelowApps));
1044-
this.RecentManager.connect('changed', Lang.bind(this, this._refreshRecent));
1045-
this.privacy_settings.connect("changed::" + REMEMBER_RECENT_KEY, Lang.bind(this, this._refreshRecent));
1050+
appsys.connect('installed-changed', () => this.queueRefresh(RefreshFlags.APP | RefreshFlags.FAV));
1051+
AppFavorites.getAppFavorites().connect('changed', () => this.queueRefresh(RefreshFlags.FAV));
1052+
Main.placesManager.connect('places-updated', () => this.queueRefresh(RefreshFlags.PLACE));
1053+
this.RecentManager.connect('changed', () => this.queueRefresh(RefreshFlags.RECENT));
1054+
this.privacy_settings.connect("changed::" + REMEMBER_RECENT_KEY, () => this.queueRefresh(RefreshFlags.RECENT));
10461055
this._fileFolderAccessActive = false;
10471056
this._pathCompleter = new Gio.FilenameCompleter();
10481057
this._pathCompleter.set_dirs_only(false);
10491058
this.lastAcResults = [];
10501059
this.settings.bind("search-filesystem", "searchFilesystem");
1051-
this.refreshing = false; // used as a flag to know if we're currently refreshing (so we don't do it more than once concurrently)
1052-
10531060
this.contextMenu = null;
1054-
10551061
this.lastSelectedCategory = null;
10561062

10571063
// We shouldn't need to call refreshAll() here... since we get a "icon-theme-changed" signal when CSD starts.
10581064
// The reason we do is in case the Cinnamon icon theme is the same as the one specificed in GTK itself (in .config)
10591065
// In that particular case we get no signal at all.
1060-
this._refreshAll();
1066+
this.refreshId = 0;
1067+
this.refreshMask = REFRESH_ALL_MASK;
1068+
this._doRefresh();
10611069

10621070
this.set_show_label_in_vertical_panels(false);
10631071
}
@@ -1079,32 +1087,44 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
10791087
}));
10801088
}
10811089

1082-
onAppSysChanged() {
1083-
if (this.refreshing == false) {
1084-
this.refreshing = true;
1085-
Mainloop.timeout_add_seconds(1, () => this._refreshAll());
1086-
}
1090+
queueRefresh(refreshFlags) {
1091+
if (!refreshFlags)
1092+
return;
1093+
this.refreshMask |= refreshFlags;
1094+
if (!this.refreshId)
1095+
this.refreshId = Mainloop.timeout_add_seconds(1, () => this._doRefresh(), Mainloop.PRIORITY_LOW);
10871096
}
10881097

1089-
_refreshAll() {
1090-
try {
1098+
_doRefresh() {
1099+
this.refreshId = 0;
1100+
if (this.refreshMask === 0)
1101+
return;
1102+
1103+
let m = this.refreshMask;
1104+
if ((m & RefreshFlags.APP) === RefreshFlags.APP)
10911105
this._refreshApps();
1106+
if ((m & RefreshFlags.FAV) === RefreshFlags.FAV)
10921107
this._refreshFavs();
1108+
if ((m & RefreshFlags.SYSTEM) === RefreshFlags.SYSTEM)
10931109
this._refreshSystemButtons();
1110+
if ((m & RefreshFlags.PLACE) === RefreshFlags.PLACE)
10941111
this._refreshPlaces();
1112+
if ((m & RefreshFlags.RECENT) === RefreshFlags.RECENT)
10951113
this._refreshRecent();
10961114

1097-
this._resizeApplicationsBox();
1098-
}
1099-
catch (exception) {
1100-
global.log(exception);
1101-
}
1102-
this.refreshing = false;
1103-
}
1115+
this.refreshMask = 0;
11041116

1105-
_refreshBelowApps() {
1106-
this._refreshPlaces();
1107-
this._refreshRecent();
1117+
// recent category is always last
1118+
if (this.recentButton)
1119+
this.categoriesBox.set_child_at_index(this.recentButton.actor, -1);
1120+
1121+
// places is before recents, or last in list if recents is disabled/not generated
1122+
if (this.placesButton) {
1123+
if (this.recentButton)
1124+
this.categoriesBox.set_child_below_sibling(this.placesButton.actor, this.recentButton.actor);
1125+
else
1126+
this.categoriesBox.set_child_at_index(this.placesButton.actor, -1);
1127+
}
11081128

11091129
this._resizeApplicationsBox();
11101130
}
@@ -2089,14 +2109,14 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
20892109
this.categoriesBox.add_actor(this.placesButton.actor);
20902110
}
20912111

2092-
// places is before recents, or last in list if recents is disabled
2093-
let sibling = this.recentButton ? this.recentButton.actor : null;
2094-
this.categoriesBox.set_child_above_sibling(this.placesButton.actor, sibling);
2095-
2112+
// places go after the last applicationbutton
2113+
let sibling = this._applicationsButtons[this._applicationsButtons.length - 1].actor;
20962114
Util.each(Main.placesManager.getAllPlaces(), place => {
20972115
let button = new PlaceButton(this, place);
20982116
this._placesButtons.push(button);
2099-
this.applicationsBox.add_actor(button.actor);
2117+
this.applicationsBox.insert_child_below(button.actor, sibling);
2118+
button.actor.visible = false;
2119+
sibling = button.actor;
21002120
});
21012121

21022122
this._resizeApplicationsBox();
@@ -2127,16 +2147,15 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
21272147
this.categoriesBox.add_actor(this.recentButton.actor);
21282148
}
21292149

2130-
// recent is always last
2131-
this.categoriesBox.set_child_at_index(this.recentButton.actor, -1);
2132-
21332150
if (this.RecentManager._infosByTimestamp.length > 0) {
21342151
this.noRecentDocuments = false;
21352152
Util.each(this.RecentManager._infosByTimestamp, (info) => {
21362153
let button = new RecentButton(this, info);
21372154
this._recentButtons.push(button);
21382155
this.applicationsBox.add_actor(button.actor);
2156+
button.actor.visible = false;
21392157
});
2158+
21402159
let button = new SimpleMenuItem(this, { name: _("Clear list"),
21412160
description: ("Clear all recent documents"),
21422161
type: 'recent-clear',
@@ -2154,6 +2173,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
21542173

21552174
this._recentButtons.push(button);
21562175
this.applicationsBox.add_actor(button.actor);
2176+
button.actor.visible = false;
21572177
} else {
21582178
this.noRecentDocuments = true;
21592179
let button = new SimpleMenuItem(this, { name: _("No recent documents"),
@@ -2164,6 +2184,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
21642184
button.addLabel(button.name, 'menu-application-button-label');
21652185
this._recentButtons.push(button);
21662186
this.applicationsBox.add_actor(button.actor);
2187+
button.actor.visible = false;
21672188
}
21682189

21692190
this._resizeApplicationsBox();
@@ -2173,19 +2194,22 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
21732194
/* iterate in reverse, so multiple splices will not upset
21742195
* the remaining elements */
21752196
for (let i = this._categoryButtons.length - 1; i > -1; i--) {
2176-
if (this._categoryButtons[i].categoryId != 'place' &&
2177-
this._categoryButtons[i].categoryId != 'recent') {
2178-
this._categoryButtons[i].destroy();
2179-
this._categoryButtons.splice(i, 1);
2180-
}
2197+
let b = this._categoryButtons[i];
2198+
if (b === this._allAppsCategoryButton ||
2199+
['place', 'recent'].includes(b.categoryId))
2200+
continue;
2201+
this._categoryButtons[i].destroy();
2202+
this._categoryButtons.splice(i, 1);
21812203
}
21822204

21832205
this._applicationsButtons.forEach(button => button.destroy());
21842206
this._applicationsButtons = [];
21852207

2186-
this._allAppsCategoryButton = new CategoryButton(this);
2187-
this.categoriesBox.add_actor(this._allAppsCategoryButton.actor);
2188-
this._categoryButtons.push(this._allAppsCategoryButton);
2208+
if (!this._allAppsCategoryButton) {
2209+
this._allAppsCategoryButton = new CategoryButton(this);
2210+
this.categoriesBox.add_actor(this._allAppsCategoryButton.actor);
2211+
this._categoryButtons.push(this._allAppsCategoryButton);
2212+
}
21892213

21902214
// grab top level directories and all apps in them
21912215
let [apps, dirs] = AppUtils.getApps();
@@ -2197,11 +2221,12 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
21972221
this.categoriesBox.add_actor(categoryButton.actor);
21982222
});
21992223

2200-
// generate all app buttons and associate all categories
2201-
Util.each(apps, (a) => {
2202-
let app = a[0];
2224+
/* we add them in reverse at index 0 so they are always above places and
2225+
* recent buttons, and below */
2226+
for (let i = apps.length - 1; i > -1; i--) {
2227+
let app = apps[i][0];
22032228
let button = new ApplicationButton(this, app);
2204-
button.category = a[1];
2229+
button.category = apps[i][1];
22052230
let appKey = app.get_id() || `${app.get_name()}:${app.get_description()}`;
22062231

22072232
// appsWereRefreshed if this is not initial load. on initial load every
@@ -2212,9 +2237,12 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
22122237
this._knownApps.add(appKey);
22132238

22142239
this._applicationsButtons.push(button);
2215-
this.applicationsBox.add_actor(button.actor);
2216-
});
2240+
this.applicationsBox.insert_child_at_index(button.actor, 0);
2241+
button.actor.visible = false;
2242+
}
22172243

2244+
// we expect this array to be in the same order as the child list
2245+
this._applicationsButtons.reverse();
22182246
this._appsWereRefreshed = true;
22192247
}
22202248

0 commit comments

Comments
 (0)