Skip to content

Commit 83bd18c

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. The timeout source is added when the mouse first enters a category button, and is stopped when the menu closes or when the mouse exits the categories box. It is not stopped when uninhibiting.
1 parent f3786e0 commit 83bd18c

File tree

1 file changed

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

1 file changed

+102
-107
lines changed

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

Lines changed: 102 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,100 @@ 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 = { x: mx, y: my };
150+
} else {
151+
this._averageSlope = 0;
152+
}
153+
}
154+
155+
_trackMouse() {
156+
if (!this._timeoutId)
157+
return false;
158+
159+
let [mx, my, ] = global.get_pointer();
160+
if (!this._bounds.contains(mx, my)) {
161+
this.stop();
162+
return false;
163+
}
164+
165+
if (this._inhibited) {
166+
let dx = mx - this._lastPos.x;
167+
let dy = Math.abs(my - this._lastPos.y);
168+
169+
if (dx > 0 && dy > 0) {
170+
// moving up/right or down/right
171+
if (this._averageSlope === 0) {
172+
this._averageSlope = dy / dx;
173+
} else {
174+
this._averageSlope += dy / dx;
175+
this._averageSlope /= 2;
176+
}
177+
} else if (dy > 0 && dx === 0) {
178+
// moving directly up or down
179+
this._averageSlope += dy;
180+
} else {
181+
// moving left or not moving at all
182+
this._averageSlope = 0;
183+
this._setInhibited(false);
184+
}
185+
186+
// approx 4 pixels straight up or down, or moving up/down 3.5 pixels per 1 right
187+
// basically it's what "feels right" with my weird algorithm.
188+
if (this._averageSlope > 3.5)
189+
this._setInhibited(false);
190+
}
191+
this._lastPos = { x: mx, y: my };
192+
return true;
193+
}
194+
}
195+
102196
class ApplicationContextMenuItem extends PopupMenu.PopupBaseMenuItem {
103197
constructor(appButton, label, action, iconName) {
104198
super({focusOnHover: false});
@@ -1154,6 +1248,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
11541248
this.noRecentDocuments = true;
11551249
this._activeContextMenuParent = null;
11561250
this._activeContextMenuItem = null;
1251+
this.selectionInhibitor = new SelectionInhibitor();
11571252
this._display();
11581253
appsys.connect('installed-changed', Lang.bind(this, this.onAppSysChanged));
11591254
AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._refreshFavs));
@@ -1361,7 +1456,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
13611456

13621457
this._clearAllSelections(true);
13631458
this._scrollToButton(this.favBoxIter.getFirstVisible()._delegate, this.favoritesScrollBox);
1364-
this.destroyVectorBox();
1459+
this.selectionInhibitor.stop();
13651460
}
13661461
}
13671462

@@ -2061,101 +2156,6 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
20612156
}
20622157
}
20632158

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-
21592159
_refreshPlaces () {
21602160
for (let i = 0; i < this._placesButtons.length; i ++) {
21612161
this._placesButtons[i].actor.destroy();
@@ -2184,7 +2184,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
21842184
this.closeContextMenus(null, false);
21852185
this._select_category("places");
21862186

2187-
this.makeVectorBox(this.placesButton.actor);
2187+
this.selectionInhibitor.start();
21882188
}
21892189
}));
21902190
this.placesButton.actor.connect('leave-event', Lang.bind(this, function () {
@@ -2258,7 +2258,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
22582258
this.closeContextMenus(null, false);
22592259
this._select_category("recent");
22602260

2261-
this.makeVectorBox(this.recentButton.actor);
2261+
this.selectionInhibitor.start();
22622262
}
22632263
}));
22642264
this.recentButton.actor.connect('leave-event', Lang.bind(this, function () {
@@ -2495,7 +2495,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
24952495
this._allAppsCategoryButton.actor.style_class = "menu-category-button-selected";
24962496
this._select_category(null);
24972497

2498-
this.makeVectorBox(this._allAppsCategoryButton.actor);
2498+
this.selectionInhibitor.start();
24992499
}
25002500
}));
25012501
this._allAppsCategoryButton.actor.connect('leave-event', Lang.bind(this, function () {
@@ -2560,7 +2560,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
25602560
categoryButton.actor.style_class = "menu-category-button-selected";
25612561
this._select_category(dir.get_menu_id());
25622562

2563-
this.makeVectorBox(categoryButton.actor);
2563+
this.selectionInhibitor.start();
25642564
}
25652565
});
25662566
};
@@ -2862,10 +2862,6 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
28622862
_display() {
28632863
this._activeContainer = null;
28642864
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;
28692865
let section = new PopupMenu.PopupMenuSection();
28702866
this.menu.addMenuItem(section);
28712867

@@ -2899,11 +2895,10 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
28992895
rightPane.add_actor(this.searchBox);
29002896
rightPane.add_actor(this.categoriesApplicationsBox.actor);
29012897

2902-
this.categoriesOverlayBox = new Clutter.Actor();
29032898
this.categoriesBox = new St.BoxLayout({ style_class: 'menu-categories-box',
29042899
vertical: true,
29052900
accessible_role: Atk.Role.LIST });
2906-
this.categoriesOverlayBox.add_actor(this.categoriesBox);
2901+
this.selectionInhibitor.setContainer(this.categoriesBox);
29072902

29082903
this.applicationsScrollBox = new St.ScrollView({ x_fill: true, y_fill: false, y_align: St.Align.START, style_class: 'vfade menu-applications-scrollbox' });
29092904
this.favoritesScrollBox = new St.ScrollView({
@@ -2937,7 +2932,7 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
29372932
this.applicationsBox.add_style_class_name('menu-applications-box'); //this is to support old themes
29382933
this.applicationsScrollBox.add_actor(this.applicationsBox);
29392934
this.applicationsScrollBox.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
2940-
this.categoriesApplicationsBox.actor.add_actor(this.categoriesOverlayBox);
2935+
this.categoriesApplicationsBox.actor.add_actor(this.categoriesBox);
29412936
this.categoriesApplicationsBox.actor.add_actor(this.applicationsScrollBox);
29422937

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

0 commit comments

Comments
 (0)