PopupMenu: add horizontal sliders
Introduce Cairo-drawn sliders to be used in PopupMenus (for example for volume). They are stylable to some extent (colors, border width, slider height) and have the standard behaviour of a slider, except they are completely modal (once you start dragging, all events are intercepted by the slider, which thus is kept active and highlighted at all times). They show numeric values between 0 and 1 (scaling must be performed outside) and emit value-changed on button release, but no activate, keeping the menu open. https://bugzilla.gnome.org/show_bug.cgi?id=625029
This commit is contained in:
parent
aebdf79430
commit
f336564e19
@ -148,6 +148,15 @@ StTooltip {
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.popup-slider-menu-item {
|
||||
height: 1em;
|
||||
-slider-height: 0.3em;
|
||||
-slider-background-color: #333333;
|
||||
-slider-border-color: #5f5f5f;
|
||||
-slider-border-width: 1px;
|
||||
-slider-handle-radius: 0.5em;
|
||||
}
|
||||
|
||||
/* Switches (to be used in menus) */
|
||||
.toggle-switch {
|
||||
width: 4.5em;
|
||||
|
@ -4,8 +4,9 @@ const Cairo = imports.cairo;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Lang = imports.lang;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const BoxPointer = imports.ui.boxpointer;
|
||||
const Main = imports.ui.main;
|
||||
@ -164,6 +165,163 @@ PopupSeparatorMenuItem.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
function PopupSliderMenuItem() {
|
||||
this._init.apply(this, arguments);
|
||||
}
|
||||
|
||||
PopupSliderMenuItem.prototype = {
|
||||
__proto__: PopupBaseMenuItem.prototype,
|
||||
|
||||
_init: function(value) {
|
||||
PopupBaseMenuItem.prototype._init.call(this, { activate: false });
|
||||
|
||||
if (isNaN(value))
|
||||
// Avoid spreading NaNs around
|
||||
throw TypeError('The slider value must be a number');
|
||||
this._displayValue = this._value = Math.max(Math.min(value, 1), 0);
|
||||
|
||||
this._slider = new St.DrawingArea({ style_class: 'popup-slider-menu-item', reactive: true });
|
||||
this.actor.set_child(this._slider);
|
||||
this._slider.connect('repaint', Lang.bind(this, this._sliderRepaint));
|
||||
this._slider.connect('button-press-event', Lang.bind(this, this._startDragging));
|
||||
|
||||
this._releaseId = this._motionId = 0;
|
||||
this._dragging = false;
|
||||
},
|
||||
|
||||
setValue: function(value) {
|
||||
if (isNaN(value))
|
||||
throw TypeError('The slider value must be a number');
|
||||
|
||||
this._displayValue = this._value = Math.max(Math.min(value, 1), 0);
|
||||
this._slider.queue_repaint();
|
||||
},
|
||||
|
||||
_sliderRepaint: function(area) {
|
||||
let cr = area.get_context();
|
||||
let themeNode = area.get_theme_node();
|
||||
let [width, height] = area.get_surface_size();
|
||||
|
||||
let found, handleRadius;
|
||||
[found, handleRadius] = themeNode.get_length('-slider-handle-radius', false);
|
||||
|
||||
let sliderWidth = width - 2 * handleRadius;
|
||||
let sliderHeight;
|
||||
[found, sliderHeight] = themeNode.get_length('-slider-height', false);
|
||||
|
||||
let sliderBorderWidth;
|
||||
[found, sliderBorderWidth] = themeNode.get_length('-slider-border-width', false);
|
||||
|
||||
let sliderBorderColor = new Clutter.Color();
|
||||
themeNode.get_color('-slider-border-color', false, sliderBorderColor);
|
||||
let sliderColor = new Clutter.Color();
|
||||
themeNode.get_color('-slider-background-color', false, sliderColor);
|
||||
|
||||
cr.setSourceRGBA (
|
||||
sliderColor.red / 255,
|
||||
sliderColor.green / 255,
|
||||
sliderColor.blue / 255,
|
||||
sliderColor.alpha / 255);
|
||||
cr.rectangle(handleRadius, (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();
|
||||
|
||||
let handleY = height / 2;
|
||||
let handleX = handleRadius + (width - 2 * handleRadius) * this._displayValue;
|
||||
|
||||
let color = new Clutter.Color();
|
||||
themeNode.get_foreground_color(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();
|
||||
},
|
||||
|
||||
_startDragging: function(actor, event) {
|
||||
if (this._dragging) // don't allow two drags at the same time
|
||||
return;
|
||||
|
||||
this._dragging = true;
|
||||
|
||||
// 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));
|
||||
let absX, absY;
|
||||
[absX, absY] = event.get_coords();
|
||||
this._moveHandle(absX, absY);
|
||||
},
|
||||
|
||||
_endDragging: function() {
|
||||
if (this._dragging) {
|
||||
this._slider.disconnect(this._releaseId);
|
||||
this._slider.disconnect(this._motionId);
|
||||
|
||||
Clutter.ungrab_pointer();
|
||||
this._dragging = false;
|
||||
|
||||
this._value = this._displayValue;
|
||||
this.emit('value-changed', this._value);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
_motionEvent: function(actor, event) {
|
||||
let absX, absY;
|
||||
[absX, absY] = event.get_coords();
|
||||
this._moveHandle(absX, absY)
|
||||
return true;
|
||||
},
|
||||
|
||||
_moveHandle: function(absX, absY) {
|
||||
let relX, relY, sliderX, sliderY;
|
||||
[sliderX, sliderY] = this._slider.get_transformed_position();
|
||||
relX = absX - sliderX;
|
||||
relY = absY - sliderY;
|
||||
|
||||
let width = this._slider.width;
|
||||
let found, handleRadius;
|
||||
[found, handleRadius] = this._slider.get_theme_node().get_length('-slider-handle-radius', false);
|
||||
|
||||
let newvalue;
|
||||
if (relX < handleRadius)
|
||||
newvalue = 0;
|
||||
else if (relX > width - handleRadius)
|
||||
newvalue = 1;
|
||||
else
|
||||
newvalue = (relX - handleRadius) / (width - 2 * handleRadius);
|
||||
this._displayValue = newvalue;
|
||||
this._slider.queue_repaint();
|
||||
},
|
||||
|
||||
get value() {
|
||||
return this._value;
|
||||
},
|
||||
|
||||
handleKeyPress: function(event) {
|
||||
let key = event.get_key_symbol();
|
||||
if (key == Clutter.Right || key == Clutter.Left) {
|
||||
let delta = key == Clutter.Right ? 0.1 : -0.1;
|
||||
this._value = this._displayValue = Math.max(0, Math.min(this._value + delta, 1));
|
||||
this._slider.queue_repaint();
|
||||
this.emit('value-changed', this._value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function PopupSwitchMenuItem() {
|
||||
this._init.apply(this, arguments);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user