[gnome-shell] PopupMenu: add horizontal sliders



commit f336564e19dbd28ec382f804eeed25e7ad22305e
Author: Giovanni Campagna <scampa giovanni gmail com>
Date:   Thu Jul 22 14:34:02 2010 +0200

    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

 data/theme/gnome-shell.css |    9 +++
 js/ui/popupMenu.js         |  160 +++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 168 insertions(+), 1 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index 0e96be8..3c63794 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -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;
diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js
index de8de1e..7e60fe8 100644
--- a/js/ui/popupMenu.js
+++ b/js/ui/popupMenu.js
@@ -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);
 }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]