Skip to content

Commit d30a3c3

Browse files
committed
menu applet: mouse tracking based selection inhibitor
This replaces the StPolygon-based overlay solution with a new class that disables reactivity of a container's children while the mouse is moving more rightwards than up/down over it.
1 parent f3786e0 commit d30a3c3

File tree

1 file changed

+95
-107
lines changed
  • files/usr/share/cinnamon/applets/menu@cinnamon.org

1 file changed

+95
-107
lines changed

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

Lines changed: 95 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,93 @@ class VisibleChildIterator {
9999
}
100100
}
101101

102+
// blocks child reactivity on a container while the mouse is moving
103+
// more rightwards than up/down. any left movement uninhibits.
104+
class SelectionInhibitor {
105+
constructor() {
106+
this._timeoutId = 0;
107+
this._averageSlope = 0;
108+
this._inhibited = false;
109+
this._container = null;
110+
this._bounds = null;
111+
this._lastPos = null;
112+
}
113+
114+
start() {
115+
if (!this._container)
116+
return;
117+
118+
if (!this._timeoutId) {
119+
this._bounds = Cinnamon.util_get_transformed_allocation(this._container);
120+
this._timeoutId = Mainloop.timeout_add(50, this._trackMouse.bind(this));
121+
}
122+
123+
this._setInhibited(true);
124+
}
125+
126+
stop() {
127+
if (this._timeoutId)
128+
Mainloop.source_remove(this._timeoutId);
129+
130+
this._timeoutId = 0;
131+
this._bounds = null;
132+
this._setInhibited(false);
133+
}
134+
135+
setContainer(container) {
136+
this.stop();
137+
this._container = container;
138+
}
139+
140+
_setInhibited(state) {
141+
if (state == this._inhibited || !this._container)
142+
return;
143+
144+
this._inhibited = state;
145+
this._container.get_children().forEach(c => c.set_reactive(!state));
146+
147+
if (state) {
148+
let [mx, my, ] = global.get_pointer();
149+
this._lastPos = [mx, my];
150+
this._averageSlope = 0;
151+
}
152+
}
153+
154+
_trackMouse() {
155+
if (!this._timeoutId)
156+
return false;
157+
158+
let [x, y, ] = global.get_pointer();
159+
if (!this._bounds.contains(x, y)) {
160+
this.stop();
161+
return false;
162+
}
163+
164+
if (this._inhibited) {
165+
let dx = x - this._lastPos[0];
166+
let dy = Math.abs(y - this._lastPos[1]);
167+
168+
if (dx > 0 && dy > 0) {
169+
this._averageSlope += dy / dx;
170+
this._averageSlope /= 2;
171+
} else if (dy > 0 && dx >= 0) {
172+
this._averageSlope += dy;
173+
} else {
174+
// x < 0 (moving left) or not moving at all
175+
this._averageSlope = 0;
176+
this._setInhibited(false)
177+
}
178+
179+
// approx 4 pixels straight up or down, or moving up/down 3.5 pixels per one right on average
180+
// basically it's what "feels right" with my weird algorithm.
181+
if (this._averageSlope > 3.5)
182+
this._setInhibited(false)
183+
}
184+
this._lastPos = [x, y];
185+
return true;
186+
}
187+
}
188+
102189
class ApplicationContextMenuItem extends PopupMenu.PopupBaseMenuItem {
103190
constructor(appButton, label, action, iconName) {
104191
super({focusOnHover: false});
@@ -1154,6 +1241,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
11541241
this.noRecentDocuments = true;
11551242
this._activeContextMenuParent = null;
11561243
this._activeContextMenuItem = null;
1244+
this.selectionInhibitor = new SelectionInhibitor();
11571245
this._display();
11581246
appsys.connect('installed-changed', Lang.bind(this, this.onAppSysChanged));
11591247
AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._refreshFavs));
@@ -1361,7 +1449,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
13611449

13621450
this._clearAllSelections(true);
13631451
this._scrollToButton(this.favBoxIter.getFirstVisible()._delegate, this.favoritesScrollBox);
1364-
this.destroyVectorBox();
1452+
this.selectionInhibitor.stop();
13651453
}
13661454
}
13671455

@@ -2061,101 +2149,6 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
20612149
}
20622150
}
20632151

2064-
/*
2065-
* The vectorBox overlays the the categoriesBox to aid in navigation from categories to apps
2066-
* by preventing misselections. It is set to the same size as the categoriesOverlayBox and
2067-
* categoriesBox.
2068-
*
2069-
* The actor is a quadrilateral that we turn into a triangle by setting the A and B vertices to
2070-
* the same position. The size and origin of the vectorBox are calculated in _getVectorInfo().
2071-
* Using those properties, the bounding box is sized as (w, h) and the triangle is defined as
2072-
* follows:
2073-
* _____
2074-
* | /|D
2075-
* | / | AB: (mx, my)
2076-
* | A/ | C: (w, h)
2077-
* | B\ | D: (w, 0)
2078-
* | \ |
2079-
* |____\|C
2080-
*/
2081-
2082-
_getVectorInfo() {
2083-
let [mx, my, mask] = global.get_pointer();
2084-
let [bx, by] = this.categoriesOverlayBox.get_transformed_position();
2085-
let [bw, bh] = this.categoriesOverlayBox.get_transformed_size();
2086-
2087-
let xformed_mx = mx - bx;
2088-
let xformed_my = my - by;
2089-
2090-
if (xformed_mx < 0 || xformed_mx > bw || xformed_my < 0 || xformed_my > bh) {
2091-
return null;
2092-
}
2093-
2094-
return { mx: xformed_mx,
2095-
my: xformed_my,
2096-
w: bw,
2097-
h: bh };
2098-
}
2099-
2100-
makeVectorBox(actor) {
2101-
this.destroyVectorBox(actor);
2102-
let vi = this._getVectorInfo();
2103-
if (!vi) {
2104-
return;
2105-
}
2106-
2107-
this.vectorBox = new St.Polygon({ debug: false, width: vi.w -1, height: vi.h,
2108-
ulc_x: vi.mx, ulc_y: vi.my,
2109-
llc_x: vi.mx, llc_y: vi.my,
2110-
urc_x: vi.w, urc_y: 0,
2111-
lrc_x: vi.w, lrc_y: vi.h });
2112-
2113-
this.categoriesOverlayBox.add_actor(this.vectorBox);
2114-
2115-
this.vectorBox.show();
2116-
this.vectorBox.set_reactive(true);
2117-
2118-
this.vectorBox.connect("leave-event", Lang.bind(this, this.destroyVectorBox));
2119-
this.vectorBox.connect("motion-event", Lang.bind(this, this.maybeUpdateVectorBox));
2120-
this.actor_motion_id = actor.connect("motion-event", Lang.bind(this, this.maybeUpdateVectorBox));
2121-
this.current_motion_actor = actor;
2122-
}
2123-
2124-
maybeUpdateVectorBox() {
2125-
if (this.vector_update_loop) {
2126-
Mainloop.source_remove(this.vector_update_loop);
2127-
this.vector_update_loop = 0;
2128-
}
2129-
this.vector_update_loop = Mainloop.timeout_add(50, Lang.bind(this, this.updateVectorBox));
2130-
}
2131-
2132-
updateVectorBox(actor) {
2133-
if (this.vectorBox) {
2134-
let vi = this._getVectorInfo();
2135-
if (vi) {
2136-
this.vectorBox.ulc_x = vi.mx;
2137-
this.vectorBox.llc_x = vi.mx;
2138-
this.vectorBox.queue_repaint();
2139-
} else {
2140-
this.destroyVectorBox(actor);
2141-
}
2142-
}
2143-
this.vector_update_loop = 0;
2144-
return false;
2145-
}
2146-
2147-
destroyVectorBox(actor) {
2148-
if (this.vectorBox != null) {
2149-
this.vectorBox.destroy();
2150-
this.vectorBox = null;
2151-
}
2152-
if (this.actor_motion_id > 0 && this.current_motion_actor != null) {
2153-
this.current_motion_actor.disconnect(this.actor_motion_id);
2154-
this.actor_motion_id = 0;
2155-
this.current_motion_actor = null;
2156-
}
2157-
}
2158-
21592152
_refreshPlaces () {
21602153
for (let i = 0; i < this._placesButtons.length; i ++) {
21612154
this._placesButtons[i].actor.destroy();
@@ -2184,7 +2177,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
21842177
this.closeContextMenus(null, false);
21852178
this._select_category("places");
21862179

2187-
this.makeVectorBox(this.placesButton.actor);
2180+
this.selectionInhibitor.start();
21882181
}
21892182
}));
21902183
this.placesButton.actor.connect('leave-event', Lang.bind(this, function () {
@@ -2258,7 +2251,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
22582251
this.closeContextMenus(null, false);
22592252
this._select_category("recent");
22602253

2261-
this.makeVectorBox(this.recentButton.actor);
2254+
this.selectionInhibitor.start();
22622255
}
22632256
}));
22642257
this.recentButton.actor.connect('leave-event', Lang.bind(this, function () {
@@ -2495,7 +2488,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
24952488
this._allAppsCategoryButton.actor.style_class = "menu-category-button-selected";
24962489
this._select_category(null);
24972490

2498-
this.makeVectorBox(this._allAppsCategoryButton.actor);
2491+
this.selectionInhibitor.start();
24992492
}
25002493
}));
25012494
this._allAppsCategoryButton.actor.connect('leave-event', Lang.bind(this, function () {
@@ -2560,7 +2553,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
25602553
categoryButton.actor.style_class = "menu-category-button-selected";
25612554
this._select_category(dir.get_menu_id());
25622555

2563-
this.makeVectorBox(categoryButton.actor);
2556+
this.selectionInhibitor.start();
25642557
}
25652558
});
25662559
};
@@ -2862,10 +2855,6 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
28622855
_display() {
28632856
this._activeContainer = null;
28642857
this._activeActor = null;
2865-
this.vectorBox = null;
2866-
this.actor_motion_id = 0;
2867-
this.vector_update_loop = null;
2868-
this.current_motion_actor = null;
28692858
let section = new PopupMenu.PopupMenuSection();
28702859
this.menu.addMenuItem(section);
28712860

@@ -2899,11 +2888,10 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
28992888
rightPane.add_actor(this.searchBox);
29002889
rightPane.add_actor(this.categoriesApplicationsBox.actor);
29012890

2902-
this.categoriesOverlayBox = new Clutter.Actor();
29032891
this.categoriesBox = new St.BoxLayout({ style_class: 'menu-categories-box',
29042892
vertical: true,
29052893
accessible_role: Atk.Role.LIST });
2906-
this.categoriesOverlayBox.add_actor(this.categoriesBox);
2894+
this.selectionInhibitor.setContainer(this.categoriesBox);
29072895

29082896
this.applicationsScrollBox = new St.ScrollView({ x_fill: true, y_fill: false, y_align: St.Align.START, style_class: 'vfade menu-applications-scrollbox' });
29092897
this.favoritesScrollBox = new St.ScrollView({
@@ -2937,7 +2925,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
29372925
this.applicationsBox.add_style_class_name('menu-applications-box'); //this is to support old themes
29382926
this.applicationsScrollBox.add_actor(this.applicationsBox);
29392927
this.applicationsScrollBox.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
2940-
this.categoriesApplicationsBox.actor.add_actor(this.categoriesOverlayBox);
2928+
this.categoriesApplicationsBox.actor.add_actor(this.categoriesBox);
29412929
this.categoriesApplicationsBox.actor.add_actor(this.applicationsScrollBox);
29422930

29432931
this.favoritesBox = new FavoritesBox().actor;

0 commit comments

Comments
 (0)