Instantly share code, notes, and snippets.
Created
December 12, 2012 00:01
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save mathematicalcoffee/4263565 to your computer and use it in GitHub Desktop.
Some javascript classes for gnome-shell extensions that create handy Popup Menu Items. PopupDoubleSliderMenuItem: a PopupSliderMenuItem with two sliders, for a lower and upper limit.
PopupSliderMenuItemWithLabel: a PopupSliderMenuItem with a convenience label showing the current value. You can specify the lower/upper limits of the slider.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* A SliderMenuItem with two slidable things, for | |
* selecting a range. Basically a modified PopupSliderMenuItem. | |
* It has no scroll or key-press event as it's hard to tell which | |
* blob the user meant to scroll. | |
*/ | |
function PopupDoubleSliderMenuItem() { | |
this._init.apply(this, arguments); | |
} | |
PopupDoubleSliderMenuItem.prototype = { | |
__proto__: PopupMenu.PopupBaseMenuItem.prototype, | |
_init: function (val1, val2) { | |
PopupMenu.PopupBaseMenuItem.prototype._init.call(this, | |
{ activate: false }); | |
if (isNaN(val1) || isNaN(val2)) | |
// Avoid spreading NaNs around | |
throw new TypeError('The slider value must be a number'); | |
this._values = [Math.max(Math.min(val1, 1), 0), | |
Math.max(Math.min(val2, 1), 0)]; | |
this._slider = new St.DrawingArea({ | |
style_class: 'popup-slider-menu-item', | |
reactive: true | |
}); | |
this.addActor(this._slider, { span: -1, expand: true }); | |
this._slider.connect('repaint', Lang.bind(this, this._sliderRepaint)); | |
this.actor.connect('button-press-event', Lang.bind(this, | |
this._startDragging)); | |
this._releaseId = this._motionId = 0; | |
this._dragging = false; | |
}, | |
_setValue: function (i, value) { | |
if (isNaN(value)) | |
throw new TypeError('The slider value must be a number'); | |
this._value[i] = Math.max(Math.min(value, 1), 0); | |
this._slider.queue_repaint(); | |
}, | |
setValue1: function (value) { | |
this._setValue(0, value); | |
}, | |
setValue2: function (value) { | |
this._setValue(1, value); | |
}, | |
_sliderRepaint: function (area) { | |
let cr = area.get_context(); | |
let themeNode = area.get_theme_node(); | |
let [width, height] = area.get_surface_size(); | |
let handleRadius = themeNode.get_length('-slider-handle-radius'); | |
let sliderWidth = width - 2 * handleRadius; | |
let sliderHeight = themeNode.get_length('-slider-height'); | |
let sliderBorderWidth = themeNode.get_length('-slider-border-width'); | |
let sliderBorderColor = themeNode.get_color('-slider-border-color'); | |
let sliderColor = themeNode.get_color('-slider-background-color'); | |
let sliderActiveBorderColor = themeNode.get_color('-slider-active-border-color'); | |
let sliderActiveColor = themeNode.get_color('-slider-active-background-color'); | |
/* slider active colour from val0 to val1 */ | |
cr.setSourceRGBA( | |
sliderActiveColor.red / 255, | |
sliderActiveColor.green / 255, | |
sliderActiveColor.blue / 255, | |
sliderActiveColor.alpha / 255); | |
cr.rectangle(handleRadius + sliderWidth * this._values[0], | |
(height - sliderHeight) / 2, | |
sliderWidth * this._values[1], sliderHeight); | |
cr.fillPreserve(); | |
cr.setSourceRGBA( | |
sliderActiveBorderColor.red / 255, | |
sliderActiveBorderColor.green / 255, | |
sliderActiveBorderColor.blue / 255, | |
sliderActiveBorderColor.alpha / 255); | |
cr.setLineWidth(sliderBorderWidth); | |
cr.stroke(); | |
/* slider from 0 to val0 */ | |
cr.setSourceRGBA( | |
sliderColor.red / 255, | |
sliderColor.green / 255, | |
sliderColor.blue / 255, | |
sliderColor.alpha / 255); | |
cr.rectangle(handleRadius, (height - sliderHeight) / 2, | |
sliderWidth * this._values[0], sliderHeight); | |
cr.fillPreserve(); | |
cr.setSourceRGBA( | |
sliderBorderColor.red / 255, | |
sliderBorderColor.green / 255, | |
sliderBorderColor.blue / 255, | |
sliderBorderColor.alpha / 255); | |
cr.setLineWidth(sliderBorderWidth); | |
cr.stroke(); | |
/* slider from val1 to 1 */ | |
cr.setSourceRGBA( | |
sliderColor.red / 255, | |
sliderColor.green / 255, | |
sliderColor.blue / 255, | |
sliderColor.alpha / 255); | |
cr.rectangle(handleRadius + sliderWidth * this._values[1], | |
(height - sliderHeight) / 2, | |
sliderWidth, sliderHeight); | |
cr.fillPreserve(); | |
cr.setSourceRGBA( | |
sliderBorderColor.red / 255, | |
sliderBorderColor.green / 255, | |
sliderBorderColor.blue / 255, | |
sliderBorderColor.alpha / 255); | |
cr.setLineWidth(sliderBorderWidth); | |
cr.stroke(); | |
/* dots */ | |
let i = this._values.length; | |
while (i--) { | |
let val = this._values[i]; | |
let handleY = height / 2; | |
let handleX = handleRadius + (width - 2 * handleRadius) * val; | |
let color = themeNode.get_foreground_color(); | |
cr.setSourceRGBA( | |
color.red / 255, | |
color.green / 255, | |
color.blue / 255, | |
color.alpha / 255); | |
cr.arc(handleX, handleY, handleRadius, 0, 2 * Math.PI); | |
cr.fill(); | |
} | |
}, | |
/* returns the index of the dot to move */ | |
_whichDotToMove: function (absX, absY) { | |
let relX, relY, sliderX, sliderY; | |
[sliderX, sliderY] = this._slider.get_transformed_position(); | |
relX = absX - sliderX; | |
let width = this._slider.width, | |
handleRadius = this._slider.get_theme_node().get_length( | |
'-slider-handle-radius'), | |
newvalue; | |
if (relX < handleRadius) | |
newvalue = 0; | |
else if (relX > width - handleRadius) | |
newvalue = 1; | |
else | |
newvalue = (relX - handleRadius) / (width - 2 * handleRadius); | |
return (Math.abs(newvalue - this._values[0]) < | |
Math.abs(newvalue - this._values[1]) ? 0 : 1); | |
}, | |
_endDragging: function () { | |
PopupMenu.PopupSliderMenuItem.prototype._endDragging.apply(this, arguments); | |
}, | |
_startDragging: function (actor, event) { | |
if (this._dragging) // don't allow two drags at the same time | |
return; | |
this._dragging = true; | |
let absX, absY; | |
[absX, absY] = event.get_coords(); | |
let dot = this._whichDotToMove(absX, absY); | |
// FIXME: we should only grab the specific device that originated | |
// the event, but for some weird reason events are still delivered | |
// outside the slider if using clutter_grab_pointer_for_device | |
Clutter.grab_pointer(this._slider); | |
this._releaseId = this._slider.connect('button-release-event', | |
Lang.bind(this, this._endDragging)); | |
this._motionId = this._slider.connect('motion-event', | |
Lang.bind(this, this._motionEvent, dot)); | |
this._moveHandle(absX, absY, dot); | |
}, | |
_motionEvent: function (actor, event, dot) { | |
let absX, absY; | |
[absX, absY] = event.get_coords(); | |
this._moveHandle(absX, absY, dot); | |
return true; | |
}, | |
/* Don't let the bottom slider cross over the top slider | |
* and vice versa */ | |
_moveHandle: function (absX, absY, which) { | |
let relX, relY, sliderX, sliderY; | |
[sliderX, sliderY] = this._slider.get_transformed_position(); | |
relX = absX - sliderX; | |
relY = absY - sliderY; | |
let width = this._slider.width, | |
handleRadius = this._slider.get_theme_node().get_length( | |
'-slider-handle-radius'), | |
newvalue = (relX - handleRadius) / (width - 2 * handleRadius); | |
newvalue = Math.max(which ? this._values[0] : 0, | |
Math.min(newvalue, which ? 1 : this._values[1])); | |
this._values[which] = newvalue; | |
this._slider.queue_repaint(); | |
this.emit('value-changed', this._values[which], which); | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* A slider with a label + number that updates with the slider. | |
* The range of the slider may be set from `min` to `max`. | |
* Use getValue() and setValue(val) to get/set the value of the slider (between `min` and `max`). | |
* Use getValue(true) and setValue(val, true) to get/set the value of the slider on the underlying 0 to 1 scale. | |
* | |
* text: the text for the item | |
* defaultVal: the intial value for the item (on the min -> max scale) | |
* min, max: the min and max values for the slider | |
* round: whether to round the value to the nearest integer | |
* ndec: number of decimal places to round to | |
* params: other params for PopupBaseMenuItem | |
* | |
*/ | |
function PopupSliderMenuItemWithLabel() { | |
this._init.apply(this, arguments); | |
} | |
PopupSliderMenuItemWithLabel.prototype = { | |
__proto__: PopupMenu.PopupBaseMenuItem.prototype, | |
_init: function (text, defaultVal, min, max, round, ndec, params) { | |
PopupMenu.PopupBaseMenuItem.prototype._init.call(this, params); | |
/* set up properties */ | |
this.min = min || 0; | |
this.max = max || 1; | |
this.round = round || false; | |
this._value = defaultVal; | |
if (round) { | |
this._value = Math.round(this._value); | |
} | |
this.ndec = this.ndec || (round ? 0 : 2); | |
/* set up item */ | |
this.box = new St.BoxLayout({vertical: true}); | |
this.addActor(this.box, {expand: true, span: -1}); | |
this.topBox = new St.BoxLayout({vertical: false, | |
style_class: 'slider-menu-item-top-box'}); | |
this.box.add(this.topBox, {x_fill: true}); | |
this.bottomBox = new St.BoxLayout({vertical: false, | |
style_class: 'slider-menu-item-bottom-box'}); | |
this.box.add(this.bottomBox, {x_fill: true}); | |
/* text */ | |
this.label = new St.Label({text: text, reactive: false}); | |
/* number */ | |
this.numberLabel = new St.Label({text: this._value.toFixed(this.ndec), | |
reactive: false}); | |
/* slider */ | |
this.slider = new PopupMenu.PopupSliderMenuItem((defaultVal - min) / | |
(max - min)); // between 0 and 1 | |
/* connect up signals */ | |
this.slider.connect('value-changed', | |
Lang.bind(this, this._updateValue)); | |
/* pass through the drag-end, clicked signal */ | |
this.slider.connect('drag-end', Lang.bind(this, function () { | |
this.emit('drag-end', this._value); | |
})); | |
// Note: if I set the padding in the css it gets overridden | |
this.slider.actor.set_style('padding-left: 0em; padding-right: 0em;'); | |
/* assemble the item */ | |
this.topBox.add(this.label, {expand: true}); | |
this.topBox.add(this.numberLabel, {align: St.Align.END}); | |
this.bottomBox.add(this.slider.actor, {expand: true, span: -1}); | |
}, | |
/* returns the value of the slider, either the raw (0-1) value or the | |
* value on the min->max scale. */ | |
getValue: function (raw) { | |
if (raw) { | |
return this.slider.value; | |
} else { | |
return this._value; | |
} | |
}, | |
/* sets the value of the slider, either the raw (0-1) value or the | |
* value on the min->max scale */ | |
setValue: function (value, raw) { | |
value = (raw ? value : (value - this.min) / (this.max - this.min)); | |
this._updateValue(this.slider, value); | |
this.slider.setValue(value); | |
}, | |
_updateValue: function (slider, value) { | |
let val = value * (this.max - this.min) + this.min; | |
if (this.round) { | |
val = Math.round(val); | |
} | |
this._value = val; | |
this.numberLabel.set_text(val.toFixed(this.ndec)); | |
}, | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* For PopupSliderMenuItemWithLabel */ | |
.slider-menu-item-top-box { | |
padding-left: 0em; | |
padding-right: 1.75em; | |
} | |
.slider-menu-item-bottom-box { | |
padding-left: 0em; | |
padding-right: 0em; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment