diff --git a/js/ui/button.js b/js/ui/button.js new file mode 100644 index 000000000..9c22c85ba --- /dev/null +++ b/js/ui/button.js @@ -0,0 +1,114 @@ +/* -*- mode: js2; js2-basic-offset: 4; -*- */ + +const Clutter = imports.gi.Clutter; +const Tweener = imports.tweener.tweener; + +const DEFAULT_BUTTON_COLOR = new Clutter.Color(); +DEFAULT_BUTTON_COLOR.from_pixel(0xeeddccff); + +const DEFAULT_PRESSED_BUTTON_COLOR = new Clutter.Color(); +DEFAULT_PRESSED_BUTTON_COLOR.from_pixel(0xddccbbff); + +// Time for animation making the button darker +const ANIMATION_TIME = 0.3; + +const FULL_OPACITY = 255; + +const PARTIAL_OPACITY = 0.5 * 255; + +function Button(text, button_color, pressed_button_color, min_width, min_height) { + this._init(text, button_color, pressed_button_color, min_width, min_height); +} + +Button.prototype = { + _init : function(text, button_color, pressed_button_color, stays_pressed, min_width, min_height) { + + this._button_color = button_color + if (button_color == null) + this._button_color = DEFAULT_BUTTON_COLOR; + + this._pressed_button_color = pressed_button_color + if (pressed_button_color == null) + this._pressed_button_color = DEFAULT_PRESSED_BUTTON_COLOR; + + if (stays_pressed == null) + stays_pressed = false + if (min_width == null) + min_width = 0; + if (min_height == null) + min_height = 0; + + // if stays_pressed is true, this.active will be true past the first release of a button, untill a subsequent one (the button + // is unpressed) or untill release() is called explicitly + this._active = false; + this._is_between_press_and_release = false; + this._mouse_is_over_button = false; + + this.button = new Clutter.Group({reactive: true}); + this._background = new Clutter.Rectangle({ color: this._button_color}); + this._label = new Clutter.Label({ font_name: "Sans Bold 16px", + text: text}); + this._label.set_position(5, 5); + this._background.set_width(Math.max(this._label.get_width()+10, min_width)) + this._background.set_height(Math.max(this._label.get_height()+10, min_height)) + this.button.add_actor(this._background); + this.button.add_actor(this._label); + let me = this; + this.button.connect('button-press-event', + function(o, event) { + me._is_between_press_and_release = true; + Tweener.addTween(me._background, + { time: ANIMATION_TIME, + opacity: FULL_OPACITY, + transition: "linear" + }); + return false; + }); + this.button.connect('button-release-event', + function(o, event) { + me._is_between_press_and_release = false; + if (!stays_pressed || me._active) { + me.release(); + } else { + me._active = true; + } + return false; + }); + this.button.connect('enter-event', + function(o, event) { + me._mouse_is_over_button = true; + if (!me._active) { + Tweener.removeTweens(me._background); + me._background.set_opacity(PARTIAL_OPACITY); + me._background.set_color(me._pressed_button_color); + } + return false; + }); + this.button.connect('leave-event', + function(o, event) { + me._is_between_press_and_release = false; + me._mouse_is_over_button = false; + if (!me._active) { + Tweener.removeTweens(me._background); + me._background.set_opacity(FULL_OPACITY); + me._background.set_color(me._button_color); + } + return false; + }); + }, + + release : function() { + if (!this._is_between_press_and_release) { + this._active = false; + Tweener.removeTweens(this._background); + if (this._mouse_is_over_button) { + this._background.set_opacity(PARTIAL_OPACITY); + this._background.set_color(this._pressed_button_color); + } else { + this._background.set_opacity(FULL_OPACITY); + this._background.set_color(this._button_color); + } + } + } +} + diff --git a/js/ui/main.js b/js/ui/main.js index c19d4a00e..add0a6795 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -48,5 +48,6 @@ function hide_overlay() { let global = Shell.global_get(); overlay.hide(); + panel.overlayHidden(); global.set_stage_input_area(0, 0, global.screen_width, Panel.PANEL_HEIGHT); } diff --git a/js/ui/panel.js b/js/ui/panel.js index a9f3c6973..3507065f5 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -5,6 +5,7 @@ const Mainloop = imports.mainloop; const Shell = imports.gi.Shell; const Clutter = imports.gi.Clutter; const Tidy = imports.gi.Tidy; +const Button = imports.ui.button; const Main = imports.ui.main; @@ -12,6 +13,8 @@ const PANEL_HEIGHT = 32; const TRAY_HEIGHT = 24; const PANEL_BACKGROUND_COLOR = new Clutter.Color(); PANEL_BACKGROUND_COLOR.from_pixel(0xeeddccff); +const PRESSED_BUTTON_BACKGROUND_COLOR = new Clutter.Color(); +PRESSED_BUTTON_BACKGROUND_COLOR.from_pixel(0xddccbbff); function Panel() { this._init(); @@ -31,11 +34,9 @@ Panel.prototype = { background.set_position(-1, -1); this._group.add_actor(background); - let message = new Clutter.Label({ font_name: "Sans Bold 16px", - text: "Activities", - reactive: true }); - message.set_position(5, 5); - this._group.add_actor(message); + this.button = new Button.Button("Activities", PANEL_BACKGROUND_COLOR, PRESSED_BUTTON_BACKGROUND_COLOR, true, null, PANEL_HEIGHT); + + this._group.add_actor(this.button.button); this._grid = new Tidy.Grid({ height: TRAY_HEIGHT, valign: 0.5, @@ -47,12 +48,16 @@ Panel.prototype = { text: "" }); this._grid.add_actor(this._clock); + // Setting the anchor point at top right (north east) makes that portion of the + // grid positioned at the position specified below. this._grid.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST); this._grid.set_position(global.screen_width - 2, (PANEL_HEIGHT - TRAY_HEIGHT) / 2); this._traymanager = new Shell.TrayManager(); let panel = this; - this._traymanager.connect('tray-icon-added', + // the anchor point needs to be updated each time the height/width of the content might have changed, because + // it doesn't get updated on its own + this._traymanager.connect('tray-icon-added', function(o, icon) { panel._grid.add_actor(icon); /* bump the clock back to the end */ @@ -67,7 +72,11 @@ Panel.prototype = { }); this._traymanager.manage_stage(global.stage); - message.connect('button-press-event', + // TODO: decide what to do with the rest of the panel in the overlay mode (make it fade-out, become non-reactive, etc.) + // We get into the overlay mode on button-press-event as opposed to button-release-event because eventually we'll probably + // have the overlay act like a menu that allows the user to release the mouse on the activity the user wants + // to switch to. + this.button.button.connect('button-press-event', function(o, event) { if (Main.overlay.visible) Main.hide_overlay(); @@ -85,6 +94,7 @@ Panel.prototype = { _startClock: function() { let me = this; + // TODO: this makes the clock updated every 60 seconds, but not necessarily on the minute, so it is inaccurate Mainloop.timeout_add_seconds(60, function() { me._updateClock(); @@ -95,5 +105,9 @@ Panel.prototype = { _updateClock: function() { this._clock.set_text(new Date().toLocaleFormat("%H:%M")); return true; + }, + + overlayHidden: function() { + this.button.release(); } }; diff --git a/src/shell-global.c b/src/shell-global.c index 6f1410575..dde40d981 100644 --- a/src/shell-global.c +++ b/src/shell-global.c @@ -175,9 +175,9 @@ shell_global_get (void) /** * shell_global_set_stage_input_area: * x: X coordinate of rectangle - * y: X coordinate of rectangle + * y: Y coordinate of rectangle * width: width of rectangle - * height: Height of rectangle + * height: height of rectangle * * Sets the area of the stage that is responsive to mouse clicks as * a rectangle.