panel: Add rounded corners

Current mockups show the panel curving downwards at the edges to
frame the work area and look awesome. Implement those as separate
actors to not affect the struts set by the panel, and synchronize
their state with the corresponding panel buttons so they blend in
with the panel. It might be worth considering whether the corners
should be hidden with maximized windows on the current workspace,
though this might affect the illusion of them being part of the
panel. As the corners don't affect the input region, the small
overlap with windows might not be too bad after all.

https://bugzilla.gnome.org/show_bug.cgi?id=642697
This commit is contained in:
Florian Müllner 2011-02-09 03:12:10 +01:00
parent 0a94c01f83
commit 977f45cdb9
2 changed files with 177 additions and 0 deletions

View File

@ -210,6 +210,21 @@ StTooltip StLabel {
padding-right: 4px; padding-right: 4px;
} }
.panel-corner {
-panel-corner-radius: 10px;
-panel-corner-background-color: black;
-panel-corner-inner-border-width: 2px;
-panel-corner-inner-border-color: transparent;
-panel-corner-outer-border-width: 1px;
-panel-corner-outer-border-color: #536272;
}
.panel-corner:active,
.panel-corner:checked,
.panel-corner:focus {
-panel-corner-inner-border-color: rgba(255,255,255,0.8);
}
#appMenu { #appMenu {
spacing: 4px; spacing: 4px;
} }

View File

@ -1,5 +1,6 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
const Cairo = imports.cairo;
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const Lang = imports.lang; const Lang = imports.lang;
@ -50,6 +51,51 @@ const CLOCK_FORMAT_KEY = 'clock-format';
const CLOCK_SHOW_DATE_KEY = 'show-date'; const CLOCK_SHOW_DATE_KEY = 'show-date';
const CLOCK_SHOW_SECONDS_KEY = 'show-seconds'; const CLOCK_SHOW_SECONDS_KEY = 'show-seconds';
// To make sure the panel corners blend nicely with the panel,
// we draw background and borders the same way, e.g. drawing
// them as filled shapes from the outside inwards instead of
// using cairo stroke(). So in order to give the border the
// appearance of being drawn on top of the background, we need
// to blend border and background color together.
// For that purpose we use the following helper methods, taken
// from st-theme-node-drawing.c
function _norm(x) {
return Math.round(x / 255);
}
function _over(srcColor, dstColor) {
let src = _premultiply(srcColor);
let dst = _premultiply(dstColor);
let result = new Clutter.Color();
result.alpha = src.alpha + _norm((255 - src.alpha) * dst.alpha);
result.red = src.red + _norm((255 - src.alpha) * dst.red);
result.green = src.green + _norm((255 - src.alpha) * dst.green);
result.blue = src.blue + _norm((255 - src.alpha) * dst.blue);
return _unpremultiply(result);
}
function _premultiply(color) {
return new Clutter.Color({ red: _norm(color.red * color.alpha),
green: _norm(color.green * color.alpha),
blue: _norm(color.blue * color.alpha),
alpha: color.alpha });
};
function _unpremultiply(color) {
if (color.alpha == 0)
return new Clutter.Color();
let red = Math.min((color.red * 255 + 127) / color.alpha, 255);
let green = Math.min((color.green * 255 + 127) / color.alpha, 255);
let blue = Math.min((color.blue * 255 + 127) / color.alpha, 255);
return new Clutter.Color({ red: red, green: green,
blue: blue, alpha: color.alpha });
};
function AnimatedIcon(name, size) { function AnimatedIcon(name, size) {
this._init(name, size); this._init(name, size);
} }
@ -504,6 +550,95 @@ AppMenuButton.prototype = {
Signals.addSignalMethods(AppMenuButton.prototype); Signals.addSignalMethods(AppMenuButton.prototype);
function PanelCorner(side) {
this._init(side);
}
PanelCorner.prototype = {
_init: function(side) {
this._side = side;
this.actor = new St.DrawingArea({ style_class: 'panel-corner' });
this.actor.connect('repaint', Lang.bind(this, this._repaint));
this.actor.connect('style-changed', Lang.bind(this, this._reposition));
},
_repaint: function() {
let node = this.actor.get_theme_node();
let cornerRadius = node.get_length("-panel-corner-radius");
let innerBorderWidth = node.get_length('-panel-corner-inner-border-width');
let outerBorderWidth = node.get_length('-panel-corner-outer-border-width');
let backgroundColor = node.get_color('-panel-corner-background-color');
let innerBorderColor = node.get_color('-panel-corner-inner-border-color');
let outerBorderColor = node.get_color('-panel-corner-outer-border-color');
let cr = this.actor.get_context();
cr.setOperator(Cairo.Operator.SOURCE);
cr.moveTo(0, 0);
if (this._side == St.Side.LEFT)
cr.arc(cornerRadius,
innerBorderWidth + outerBorderWidth + cornerRadius,
cornerRadius, Math.PI, 3 * Math.PI / 2);
else
cr.arc(0,
innerBorderWidth + outerBorderWidth + cornerRadius,
cornerRadius, 3 * Math.PI / 2, 2 * Math.PI);
cr.lineTo(cornerRadius, 0);
cr.closePath();
let savedPath = cr.copyPath();
let over = _over(outerBorderColor, backgroundColor);
Clutter.cairo_set_source_color(cr, over);
cr.fill();
let xOffsetDirection = this._side == St.Side.LEFT ? -1 : 1;
let offset = outerBorderWidth;
over = _over(innerBorderColor, backgroundColor);
Clutter.cairo_set_source_color(cr, over);
cr.save();
cr.translate(xOffsetDirection * offset, - offset);
cr.appendPath(savedPath);
cr.fill();
cr.restore();
if (this._side == St.Side.LEFT)
cr.rectangle(cornerRadius - offset, 0, offset, innerBorderWidth);
else
cr.rectangle(0, 0, offset, innerBorderWidth);
cr.fill();
offset = innerBorderWidth + outerBorderWidth;
Clutter.cairo_set_source_color(cr, backgroundColor);
cr.save();
cr.translate(xOffsetDirection * offset, - offset);
cr.appendPath(savedPath);
cr.fill();
cr.restore();
},
_reposition: function() {
let node = this.actor.get_theme_node();
let cornerRadius = node.get_length("-panel-corner-radius");
let innerBorderWidth = node.get_length('-panel-corner-inner-border-width');
let outerBorderWidth = node.get_length('-panel-corner-outer-border-width');
this.actor.set_size(cornerRadius,
innerBorderWidth + outerBorderWidth + cornerRadius);
if (this._side == St.Side.LEFT)
this.actor.set_position(0, Main.panel.actor.height - innerBorderWidth - outerBorderWidth);
else
this.actor.set_position(Main.panel.actor.width - cornerRadius, Main.panel.actor.height - innerBorderWidth - outerBorderWidth);
}
};
function Panel() { function Panel() {
this._init(); this._init();
} }
@ -535,6 +670,9 @@ Panel.prototype = {
this._rightBox.add_style_pseudo_class('rtl'); this._rightBox.add_style_pseudo_class('rtl');
} }
this._leftCorner = new PanelCorner(St.Side.LEFT);
this._rightCorner = new PanelCorner(St.Side.RIGHT);
/* This box container ensures that the centerBox is positioned in the *absolute* /* This box container ensures that the centerBox is positioned in the *absolute*
* center, but can be pushed aside if necessary. */ * center, but can be pushed aside if necessary. */
this._boxContainer = new Shell.GenericContainer(); this._boxContainer = new Shell.GenericContainer();
@ -651,6 +789,15 @@ Panel.prototype = {
}); });
this._leftBox.add(this.button); this._leftBox.add(this.button);
// Synchronize the buttons pseudo classes with its corner
this.button.connect('style-changed', Lang.bind(this,
function(actor) {
let rtl = actor.get_direction() == St.TextDirection.RTL;
let corner = rtl ? this._rightCorner : this._leftCorner;
let pseudoClass = actor.get_style_pseudo_class();
corner.actor.set_style_pseudo_class(pseudoClass);
}));
// We use this flag to mark the case where the user has entered the // We use this flag to mark the case where the user has entered the
// hot corner and has not left both the hot corner and a surrounding // hot corner and has not left both the hot corner and a surrounding
// guard area (the "environs"). This avoids triggering the hot corner // guard area (the "environs"). This avoids triggering the hot corner
@ -727,6 +874,15 @@ Panel.prototype = {
this._menus.addMenu(this._statusmenu.menu); this._menus.addMenu(this._statusmenu.menu);
this._rightBox.add(this._statusmenu.actor); this._rightBox.add(this._statusmenu.actor);
// Synchronize the buttons pseudo classes with its corner
this._statusmenu.actor.connect('style-changed', Lang.bind(this,
function(actor) {
let rtl = actor.get_direction() == St.TextDirection.RTL;
let corner = rtl ? this._leftCorner : this._rightCorner;
let pseudoClass = actor.get_style_pseudo_class();
corner.actor.set_style_pseudo_class(pseudoClass);
}));
Main.statusIconDispatcher.connect('status-icon-added', Lang.bind(this, this._onTrayIconAdded)); Main.statusIconDispatcher.connect('status-icon-added', Lang.bind(this, this._onTrayIconAdded));
Main.statusIconDispatcher.connect('status-icon-removed', Lang.bind(this, this._onTrayIconRemoved)); Main.statusIconDispatcher.connect('status-icon-removed', Lang.bind(this, this._onTrayIconRemoved));
@ -753,6 +909,12 @@ Panel.prototype = {
})); }));
Main.chrome.addActor(this.actor, { visibleInOverview: true }); Main.chrome.addActor(this.actor, { visibleInOverview: true });
Main.chrome.addActor(this._leftCorner.actor, { visibleInOverview: true,
affectsStruts: false,
affectsInputRegion: false });
Main.chrome.addActor(this._rightCorner.actor, { visibleInOverview: true,
affectsStruts: false,
affectsInputRegion: false });
}, },
_xdndShowOverview: function (actor) { _xdndShowOverview: function (actor) {