New widget: BoxPointer

Implement a rounded box with an arrow pointer.

https://bugzilla.gnome.org/show_bug.cgi?id=613804
This commit is contained in:
Colin Walters 2010-05-04 14:32:28 -04:00
parent 78e3126f97
commit 47dae0832b

175
js/ui/boxpointer.js Normal file
View File

@ -0,0 +1,175 @@
const Lang = imports.lang;
const Cairo = imports.cairo;
const Clutter = imports.gi.Clutter;
const St = imports.gi.St;
const Shell = imports.gi.Shell;
/**
* BoxPointer:
* @side: A St.Side type; currently only St.Side.TOP is implemented
* @sourceActor: The side of the BoxPointer where the pointer is
* constrained to be as big as the corresponding dimension of
* @sourceActor. E.g. for St.Side.TOP, the BoxPointer is horizontally
* constrained to be bigger than @sourceActor.
* @binProperties: Properties to set on contained bin
*
* An actor which displays a triangle "arrow" pointing to a given
* side. The .bin property is a container in which content can be
* placed. The arrow position may be controlled via setArrowOrigin().
*
*/
function BoxPointer(side, sourceActor, binProperties) {
this._init(side, sourceActor, binProperties);
}
BoxPointer.prototype = {
_init: function(arrowSide, sourceActor, binProperties) {
if (arrowSide != St.Side.TOP)
throw new Error("not implemented");
this._arrowSide = arrowSide;
this._sourceActor = sourceActor;
this._arrowOrigin = 0;
this.actor = new St.Bin({ x_fill: true,
y_fill: true });
this._container = new Shell.GenericContainer();
this.actor.set_child(this._container);
this._container.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
this._container.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
this._container.connect('allocate', Lang.bind(this, this._allocate));
this.bin = new St.Bin(binProperties);
this._container.add_actor(this.bin);
this._border = new St.DrawingArea();
this._border.connect('repaint', Lang.bind(this, this._drawBorder));
this._container.add_actor(this._border);
this.bin.raise(this._border);
},
_adjustAllocationForArrow: function(isWidth, alloc) {
let themeNode = this.actor.get_theme_node();
let found, borderWidth, base, rise;
[found, borderWidth] = themeNode.get_length('-arrow-border-width', false);
alloc.min_size += borderWidth * 2;
alloc.natural_size += borderWidth * 2;
if ((!isWidth && (this._arrowSide == St.Side.TOP || this._arrowSide == St.Side.BOTTOM))
|| (isWidth && (this._arrowSide == St.Side.LEFT || this._arrowSide == St.Side.RIGHT))) {
let [found, rise] = themeNode.get_length('-arrow-rise', false);
alloc.min_size += rise;
alloc.natural_size += rise;
}
},
_getPreferredWidth: function(actor, forHeight, alloc) {
let [minInternalSize, natInternalSize] = this.bin.get_preferred_width(forHeight);
// The allocation property unlike get_allocation_box() doesn't trigger
// synchronous relayout, which would be bad here. We assume that the
// previous allocation of the button is fine.
let sourceAlloc = this._sourceActor.allocation;
let sourceWidth = sourceAlloc.x2 - sourceAlloc.x1;
alloc.min_size = Math.max(minInternalSize, sourceWidth);
alloc.natural_size = Math.max(natInternalSize, sourceWidth);
this._adjustAllocationForArrow(true, alloc);
},
_getPreferredHeight: function(actor, forWidth, alloc) {
let [minSize, naturalSize] = this.bin.get_preferred_height(forWidth);
alloc.min_size = minSize;
alloc.natural_size = naturalSize;
this._adjustAllocationForArrow(false, alloc);
},
_allocate: function(actor, box, flags) {
let themeNode = this.actor.get_theme_node();
let found, borderWidth, borderRadius, rise, base;
[found, borderWidth] = themeNode.get_length('-arrow-border-width', false);
[found, rise] = themeNode.get_length('-arrow-rise', false);
let childBox = new Clutter.ActorBox();
let availWidth = box.x2 - box.x1;
let availHeight = box.y2 - box.y1;
childBox.x1 = 0
childBox.y1 = 0;
childBox.x2 = availWidth;
childBox.y2 = availHeight;
this._border.allocate(childBox, flags);
switch (this._arrowSide) {
case St.Side.TOP:
childBox.x1 = borderWidth;
childBox.y1 = rise + borderWidth;
childBox.x2 = availWidth - borderWidth;
childBox.y2 = availHeight - borderWidth;
break;
default:
break;
}
this.bin.allocate(childBox, flags);
},
_drawBorder: function(area) {
let themeNode = this.actor.get_theme_node();
let [sourceX, sourceY] = this._sourceActor.get_transformed_position();
let found, borderWidth, borderRadius, rise, base;
[found, borderWidth] = themeNode.get_length('-arrow-border-width', false);
[found, base] = themeNode.get_length('-arrow-base', false);
[found, rise] = themeNode.get_length('-arrow-rise', false);
[found, borderRadius] = themeNode.get_length('-arrow-border-radius', false);
let halfBorder = borderWidth / 2;
let borderColor = new Clutter.Color();
themeNode.get_color('-arrow-border-color', false, borderColor);
let backgroundColor = new Clutter.Color();
themeNode.get_color('-arrow-background-color', false, backgroundColor);
let [width, height] = area.get_surface_size();
let [boxWidth, boxHeight] = [width, height];
if (this._arrowSide == St.Side.TOP || this._arrowSide == St.Side.BOTTOM) {
boxHeight -= rise;
} else {
boxWidth -= rise;
}
let cr = area.get_context();
Clutter.cairo_set_source_color(cr, borderColor);
if (this._arrowSide == St.Side.TOP) {
cr.translate(0, rise);
}
cr.moveTo(borderRadius, halfBorder);
if (this._arrowSide == St.Side.TOP) {
cr.translate(0, -rise);
let halfBase = Math.floor(base/2);
cr.lineTo(this._arrowOrigin - halfBase, rise + halfBorder);
cr.lineTo(this._arrowOrigin, halfBorder);
cr.lineTo(this._arrowOrigin + halfBase, rise + halfBorder);
cr.translate(0, rise);
}
cr.lineTo(boxWidth - borderRadius, halfBorder);
cr.arc(boxWidth - borderRadius - halfBorder, borderRadius + halfBorder, borderRadius,
3*Math.PI/2, Math.PI*2);
cr.lineTo(boxWidth - halfBorder, boxHeight - borderRadius);
cr.arc(boxWidth - borderRadius - halfBorder, boxHeight - borderRadius - halfBorder, borderRadius,
0, Math.PI/2);
cr.lineTo(borderRadius, boxHeight - halfBorder);
cr.arc(borderRadius + halfBorder, boxHeight - borderRadius - halfBorder, borderRadius,
Math.PI/2, Math.PI);
cr.lineTo(halfBorder, borderRadius);
cr.arc(borderRadius + halfBorder, borderRadius + halfBorder, borderRadius,
Math.PI, 3*Math.PI/2);
Clutter.cairo_set_source_color(cr, backgroundColor);
cr.fillPreserve();
Clutter.cairo_set_source_color(cr, borderColor);
cr.setLineWidth(borderWidth);
cr.stroke();
},
// @origin: Coordinate specifying middle of the arrow, along
// the Y axis for St.Side.LEFT, St.Side.RIGHT from the top and X axis from
// the left for St.Side.TOP and St.Side.BOTTOM.
setArrowOrigin: function(origin) {
if (this._arrowOrigin != origin) {
this._arrowOrigin = origin;
this._border.queue_repaint();
}
}
}