-
Notifications
You must be signed in to change notification settings - Fork 27
/
fluentRevealNavbar.uc.js
258 lines (231 loc) · 9.38 KB
/
fluentRevealNavbar.uc.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
// ==UserScript==
// @name Fluent Reveal Navbar Buttons
// @version 1.2.2
// @author aminomancer
// @homepage https://github.com/aminomancer/uc.css.js
// @description Adds a visual effect to navbar buttons similar to the spotlight gradient effect on Windows 10's start menu tiles. When hovering over or near a button, a subtle radial gradient is applied to every button in the vicinity the mouse. This is compatible with Fluent Reveal Tabs.
// @downloadURL https://cdn.jsdelivr.net/gh/aminomancer/uc.css.js@master/JS/fluentRevealNavbar.uc.js
// @updateURL https://cdn.jsdelivr.net/gh/aminomancer/uc.css.js@master/JS/fluentRevealNavbar.uc.js
// @license This Source Code Form is subject to the terms of the Creative Commons Attribution-NonCommercial-ShareAlike International License, v. 4.0. If a copy of the CC BY-NC-SA 4.0 was not distributed with this file, You can obtain one at http://creativecommons.org/licenses/by-nc-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
// ==/UserScript==
(function () {
class FluentRevealEffect {
// user configuration
static options = {
// if true, show the effect on bookmarks on the toolbar
includeBookmarks: true,
// the color of the gradient. default is sort of a faint baby blue. you may prefer just white, e.g. hsla(0, 0%, 100%, 0.05)
lightColor: "hsla(224, 100%, 80%, 0.15)",
// how wide the radial gradient is.
gradientSize: 50,
// whether to show an additional light burst when clicking an element. (not recommended)
clickEffect: false,
};
// instantiate the handler for a given window
constructor() {
this._options = FluentRevealEffect.options;
this.applyEffect(window);
document.documentElement.setAttribute("fluent-reveal-hover", true);
if (this._options.clickEffect) {
document.documentElement.setAttribute("fluent-reveal-click", true);
}
}
// get all the toolbar buttons in the navbar, in iterable form
get toolbarButtons() {
let buttons = Array.from(
gNavToolbox.querySelectorAll(".toolbarbutton-1")
);
if (this._options.includeBookmarks) {
buttons = buttons.concat(
Array.from(this.placesToolbarItems.querySelectorAll(".bookmark-item"))
);
}
return buttons;
}
get placesToolbarItems() {
return (
this._placesToolbarItems ||
(this._placesToolbarItems =
document.getElementById("PlacesToolbarItems"))
);
}
/**
* main event handler. handles all the mouse behavior.
* @param {object} e (event)
*/
handleEvent(e) {
requestAnimationFrame(time => {
switch (e.type) {
case "scroll":
case "mousemove":
if (this._options.clickEffect && this._options.is_pressed) {
this.generateEffectsForAll(e, true);
} else {
this.generateEffectsForAll(e);
}
break;
case "mousedown":
this._options.is_pressed = true;
this.generateEffectsForAll(e, true);
break;
case "mouseup":
this._options.is_pressed = false;
this.generateEffectsForAll(e);
break;
}
});
}
/**
* main entry point for applying all the script behavior to an element.
* @param {object} el (a DOM node to apply the effect to)
* @param {object} options (an object containing options similar to the
* static options at the top of the script)
*/
applyEffect(el, options = this._options) {
let { clickEffect } =
options.clickEffect === undefined ? this._options : options;
let { gradientSize } =
options.gradientSize === undefined ? this._options : options;
let { lightColor } =
options.lightColor === undefined ? this._options : options;
Object.assign(this._options, {
clickEffect,
lightColor,
gradientSize,
is_pressed: false,
});
el.addEventListener("mousemove", this);
el.addEventListener("mouseleave", this);
el.addEventListener("scroll", this, true);
// only set up the click effect if the option is enabled and the element
// doesn't already have a click effect.
if (clickEffect) {
el.addEventListener("mousedown", this);
el.addEventListener("mouseup", this);
}
}
/**
* called individually on each toolbar button. finds the element inside the
* toolbar button that's supposed to have a background color (they're not
* all the same, some are just the icons, some are badge stacks, and some
* widgets have multiple buttons too) and generates a gradient for it, since
* gradient coordinates need to be relative to the top left corner of the
* element the gradient is displayed on.
* @param {object} el (a toolbar button node)
* @param {object} e (the event that triggered the painting)
* @param {boolean} click (whether the left mouse button is down)
*/
generateToolbarButtonEffect(el, e, click = false) {
let { gradientSize, lightColor } = this._options;
let isBookmark =
el.id === "PlacesChevron" || el.classList.contains("bookmark-item");
let area = isBookmark
? el
: el.querySelector(".toolbarbutton-badge-stack") ||
el.querySelector(".toolbarbutton-icon");
let areaStyle = getComputedStyle(area);
if (
areaStyle.display == "none" ||
areaStyle.visibility == "hidden" ||
areaStyle.visibility == "collapse"
) {
if (isBookmark) return this.clearEffect(area);
area = el.querySelector(".toolbarbutton-text");
}
if (el.disabled || areaStyle.pointerEvents == "none") {
return this.clearEffect(area);
}
let x =
(e.pageX || MousePosTracker._x) -
this.getOffset(area).left -
window.scrollX;
let y =
(e.pageY || MousePosTracker._y) -
this.getOffset(area).top -
window.scrollY;
let cssLightEffect = `radial-gradient(circle ${gradientSize}px at ${x}px ${y}px, ${lightColor}, rgba(255,255,255,0)), radial-gradient(circle ${70}px at ${x}px ${y}px, rgba(255,255,255,0), ${lightColor}, rgba(255,255,255,0), rgba(255,255,255,0))`;
this.drawEffect(
area,
x,
y,
lightColor,
gradientSize,
click ? cssLightEffect : null
);
}
/**
* iterate over all the toolbar buttons in the navbar, generating a separate gradient for each.
* @param {object} e (the event that invoked this)
* @param {boolean} click (whether the left mouse button is down)
*/
generateEffectsForAll(e, click = false) {
this.toolbarButtons.forEach(button =>
this.generateToolbarButtonEffect(button, e, click)
);
}
/**
* used to calculate the x and y coordinates used in drawing the gradient
* @param {object} el (a DOM node)
* @returns {object} (an object containing top and left coordinates)
*/
getOffset(el) {
return {
top: el.getBoundingClientRect().top,
left: el.getBoundingClientRect().left,
};
}
/**
* finally draw the specified effect on a given element, that is, give the
* element an inline background-image property
* @param {object} el (a DOM node)
* @param {integer} x (x coordinate for gradient center)
* @param {integer} y (y coordinate for gradient center)
* @param {string} lightColor (any color value accepted by CSS, e.g. "#FFF",
* "rgba(125, 125, 125, 0.5)", or
* "hsla(50, 0%, 100%, 0.2)")
* @param {integer} gradientSize (how many pixels wide the gradient should be)
* @param {string} cssLightEffect (technically, any background-image value accepted
* by CSS, but should be a radial-gradient()
* function, surrounded by quotes)
*/
drawEffect(el, x, y, lightColor, gradientSize, cssLightEffect = null) {
let lightBg;
if (cssLightEffect === null) {
lightBg = `radial-gradient(circle ${gradientSize}px at ${x}px ${y}px, ${lightColor}, rgba(255,255,255,0))`;
} else {
lightBg = cssLightEffect;
}
el.style.backgroundImage = lightBg;
}
/**
* invoked when the script tries to paint a disabled or otherwise
* unclickable button. (e.g. in the toolbar customization menu)
* @param {object} el (a DOM node)
*/
clearEffect(el) {
this._options.is_pressed = false;
el.style.removeProperty("background-image");
}
}
function init() {
// instantiate the class on a global property to share the methods
// with other scripts if desired.
window.fluentRevealNavbar = new FluentRevealEffect();
}
// wait for the chrome window to finish starting up, since we need to
// reference gNavToolbox as soon as any mouse events are detected
if (gBrowserInit.delayedStartupFinished) {
init();
} else {
let delayedListener = (subject, topic) => {
if (topic == "browser-delayed-startup-finished" && subject == window) {
Services.obs.removeObserver(delayedListener, topic);
init();
}
};
Services.obs.addObserver(
delayedListener,
"browser-delayed-startup-finished"
);
}
})();