Blame tests/interactive/scroll-view-sizing.js

Packit d345d1
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
Packit d345d1
Packit d345d1
const UI = imports.testcommon.ui;
Packit d345d1
Packit d345d1
const { Clutter, GObject, Gtk, Shell, St } = imports.gi;
Packit d345d1
Packit d345d1
// This is an interactive test of the sizing behavior of StScrollView. It
Packit d345d1
// may be interesting in the future to split out the two classes at the
Packit d345d1
// top into utility classes for testing the sizing behavior of other
Packit d345d1
// containers and actors.
Packit d345d1
Packit d345d1
/****************************************************************************/
Packit d345d1
Packit d345d1
// FlowedBoxes: This is a simple actor that demonstrates an interesting
Packit d345d1
// height-for-width behavior. A set of boxes of different sizes are line-wrapped
Packit d345d1
// horizontally with the minimum horizontal size being determined by the
Packit d345d1
// largest box. It would be easy to extend this to allow doing vertical
Packit d345d1
// wrapping instead, if you wanted to see just how badly our width-for-height
Packit d345d1
// implementation is or work on fixing it.
Packit d345d1
Packit d345d1
const BOX_HEIGHT = 20;
Packit d345d1
const BOX_WIDTHS = [
Packit d345d1
    10, 40, 100, 20, 60, 30, 70, 10, 20, 200, 50, 70, 90, 20, 40,
Packit d345d1
    10, 40, 100, 20, 60, 30, 70, 10, 20, 200, 50, 70, 90, 20, 40,
Packit d345d1
    10, 40, 100, 20, 60, 30, 70, 10, 20, 200, 50, 70, 90, 20, 40,
Packit d345d1
    10, 40, 100, 20, 60, 30, 70, 10, 20, 200, 50, 70, 90, 20, 40,
Packit d345d1
];
Packit d345d1
Packit d345d1
const SPACING = 10;
Packit d345d1
Packit d345d1
var FlowedBoxes = GObject.registerClass(
Packit d345d1
class FlowedBoxes extends St.Widget {
Packit d345d1
    _init() {
Packit d345d1
        super._init();
Packit d345d1
Packit d345d1
	for (let i = 0; i < BOX_WIDTHS.length; i++) {
Packit d345d1
	    let child = new St.Bin({ width: BOX_WIDTHS[i], height: BOX_HEIGHT,
Packit d345d1
	                             style: 'border: 1px solid #444444; background: #00aa44' })
Packit d345d1
	    this.add_actor(child);
Packit d345d1
	}
Packit d345d1
    }
Packit d345d1
Packit d345d1
    vfunc_get_preferred_width(forHeight) {
Packit d345d1
        let children = this.get_children();
Packit d345d1
Packit d345d1
	let maxMinWidth = 0;
Packit d345d1
	let totalNaturalWidth = 0;
Packit d345d1
Packit d345d1
	for (let i = 0; i < children.length; i++) {
Packit d345d1
	    let child = children[i];
Packit d345d1
	    let [minWidth, naturalWidth] = child.get_preferred_width(-1);
Packit d345d1
	    maxMinWidth = Math.max(maxMinWidth, minWidth);
Packit d345d1
	    if (i != 0)
Packit d345d1
		totalNaturalWidth += SPACING;
Packit d345d1
	    totalNaturalWidth += naturalWidth;
Packit d345d1
	}
Packit d345d1
Packit d345d1
        return [maxMinWidth, totalNaturalWidth];
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _layoutChildren(forWidth, callback) {
Packit d345d1
        let children = this.get_children();
Packit d345d1
Packit d345d1
	let x = 0;
Packit d345d1
	let y = 0;
Packit d345d1
	for (let i = 0; i < children.length; i++) {
Packit d345d1
	    let child = children[i];
Packit d345d1
	    let [minWidth, naturalWidth] = child.get_preferred_width(-1);
Packit d345d1
	    let [minHeight, naturalHeight] = child.get_preferred_height(naturalWidth);
Packit d345d1
Packit d345d1
	    let x1 = x;
Packit d345d1
	    if (x != 0)
Packit d345d1
		x1 += SPACING;
Packit d345d1
	    let x2 = x1 + naturalWidth;
Packit d345d1
Packit d345d1
	    if (x2 > forWidth) {
Packit d345d1
		if (x > 0) {
Packit d345d1
	            x1 = 0;
Packit d345d1
		    y += BOX_HEIGHT + SPACING;
Packit d345d1
                }
Packit d345d1
Packit d345d1
                x2 = naturalWidth;
Packit d345d1
	    }
Packit d345d1
Packit d345d1
	    callback(child, x1, y, x2, y + naturalHeight);
Packit d345d1
	    x = x2;
Packit d345d1
	}
Packit d345d1
Packit d345d1
    }
Packit d345d1
Packit d345d1
    vfunc_get_preferred_height(forWidth) {
Packit d345d1
	let height = 0;
Packit d345d1
	this._layoutChildren(forWidth,
Packit d345d1
           function(child, x1, y1, x2, y2) {
Packit d345d1
	       height = Math.max(height, y2);
Packit d345d1
	   });
Packit d345d1
Packit d345d1
        return [height, height];
Packit d345d1
    }
Packit d345d1
Packit d345d1
    vfunc_allocate(box, flags) {
Packit d345d1
        this.set_allocation(box, flags);
Packit d345d1
Packit d345d1
	this._layoutChildren(box.x2 - box.x1,
Packit d345d1
           function(child, x1, y1, x2, y2) {
Packit d345d1
	       child.allocate(new Clutter.ActorBox({ x1: x1, y1: y1, x2: x2, y2: y2 }),
Packit d345d1
			      flags);
Packit d345d1
	   });
Packit d345d1
    }
Packit d345d1
});
Packit d345d1
Packit d345d1
/****************************************************************************/
Packit d345d1
Packit d345d1
// SizingIllustrator: this is a container that allows interactively exploring
Packit d345d1
// the sizing behavior of the child. Lines are drawn to indicate the minimum
Packit d345d1
// and natural size of the child, and a drag handle allows the user to resize
Packit d345d1
// the child interactively and see how that affects it.
Packit d345d1
//
Packit d345d1
// This is currently only written for the case where the child is height-for-width
Packit d345d1
Packit d345d1
var SizingIllustrator = GObject.registerClass(
Packit d345d1
class SizingIllustrator extends St.Widget {
Packit d345d1
    _init() {
Packit d345d1
        super._init();
Packit d345d1
Packit d345d1
	this.minWidthLine = new St.Bin({ style: 'background: red' });
Packit d345d1
        this.add_actor(this.minWidthLine);
Packit d345d1
	this.minHeightLine = new St.Bin({ style: 'background: red' });
Packit d345d1
        this.add_actor(this.minHeightLine);
Packit d345d1
Packit d345d1
	this.naturalWidthLine = new St.Bin({ style: 'background: #4444ff' });
Packit d345d1
        this.add_actor(this.naturalWidthLine);
Packit d345d1
	this.naturalHeightLine = new St.Bin({ style: 'background: #4444ff' });
Packit d345d1
        this.add_actor(this.naturalHeightLine);
Packit d345d1
Packit d345d1
	this.currentWidthLine = new St.Bin({ style: 'background: #aaaaaa' });
Packit d345d1
        this.add_actor(this.currentWidthLine);
Packit d345d1
	this.currentHeightLine = new St.Bin({ style: 'background: #aaaaaa' });
Packit d345d1
        this.add_actor(this.currentHeightLine);
Packit d345d1
Packit d345d1
	this.handle = new St.Bin({ style: 'background: yellow; border: 1px solid black;',
Packit d345d1
				   reactive: true });
Packit d345d1
	this.handle.connect('button-press-event', this._handlePressed.bind(this));
Packit d345d1
	this.handle.connect('button-release-event', this._handleReleased.bind(this));
Packit d345d1
	this.handle.connect('motion-event', this._handleMotion.bind(this));
Packit d345d1
        this.add_actor(this.handle);
Packit d345d1
Packit d345d1
	this._inDrag = false;
Packit d345d1
Packit d345d1
	this.width = 300;
Packit d345d1
	this.height = 300;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    add(child) {
Packit d345d1
        this.child = child;
Packit d345d1
        this.add_child(child);
Packit d345d1
        this.child.lower_bottom();
Packit d345d1
    }
Packit d345d1
Packit d345d1
    vfunc_get_preferred_width(forHeight) {
Packit d345d1
        let children = this.get_children();
Packit d345d1
	for (let i = 0; i < children.length; i++) {
Packit d345d1
	    let child = children[i];
Packit d345d1
	    let [minWidth, naturalWidth] = child.get_preferred_width(-1);
Packit d345d1
	    if (child == this.child) {
Packit d345d1
		this.minWidth = minWidth;
Packit d345d1
		this.naturalWidth = naturalWidth;
Packit d345d1
	    }
Packit d345d1
	}
Packit d345d1
Packit d345d1
        return [0, 400];
Packit d345d1
    }
Packit d345d1
Packit d345d1
    vfunc_get_preferred_height(forWidth) {
Packit d345d1
        let children = this.get_children();
Packit d345d1
	for (let i = 0; i < children.length; i++) {
Packit d345d1
	    let child = children[i];
Packit d345d1
	    if (child == this.child) {
Packit d345d1
		[this.minHeight, this.naturalHeight] = child.get_preferred_height(this.width);
Packit d345d1
	    } else {
Packit d345d1
		let [minWidth, naturalWidth] = child.get_preferred_height(naturalWidth);
Packit d345d1
	    }
Packit d345d1
	}
Packit d345d1
Packit d345d1
        return [0, 400];
Packit d345d1
    }
Packit d345d1
Packit d345d1
    vfunc_allocate(box, flags) {
Packit d345d1
        this.set_allocation(box, flags);
Packit d345d1
Packit d345d1
        box = this.get_theme_node().get_content_box(box);
Packit d345d1
Packit d345d1
	let allocWidth = box.x2 - box.x1;
Packit d345d1
	let allocHeight = box.y2 - box.y1;
Packit d345d1
Packit d345d1
	function alloc(child, x1, y1, x2, y2) {
Packit d345d1
	    child.allocate(new Clutter.ActorBox({ x1: x1, y1: y1, x2: x2, y2: y2 }),
Packit d345d1
	                   flags);
Packit d345d1
	}
Packit d345d1
Packit d345d1
	alloc(this.child, 0, 0, this.width, this.height);
Packit d345d1
	alloc(this.minWidthLine, this.minWidth, 0, this.minWidth + 1, allocHeight);
Packit d345d1
	alloc(this.naturalWidthLine, this.naturalWidth, 0, this.naturalWidth + 1, allocHeight);
Packit d345d1
	alloc(this.currentWidthLine, this.width, 0, this.width + 1, allocHeight);
Packit d345d1
	alloc(this.minHeightLine, 0, this.minHeight, allocWidth, this.minHeight + 1);
Packit d345d1
	alloc(this.naturalHeightLine, 0, this.naturalHeight, allocWidth, this.naturalHeight + 1);
Packit d345d1
	alloc(this.currentHeightLine, 0, this.height, allocWidth, this.height + 1);
Packit d345d1
	alloc(this.handle, this.width, this.height, this.width + 10, this.height + 10);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _handlePressed(handle, event) {
Packit d345d1
	if (event.get_button() == 1) {
Packit d345d1
	    this._inDrag = true;
Packit d345d1
	    let [handleX, handleY] = handle.get_transformed_position();
Packit d345d1
	    let [x, y] = event.get_coords();
Packit d345d1
	    this._dragX = x - handleX;
Packit d345d1
	    this._dragY = y - handleY;
Packit d345d1
	    Clutter.grab_pointer(handle);
Packit d345d1
	}
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _handleReleased(handle, event) {
Packit d345d1
	if (event.get_button() == 1) {
Packit d345d1
	    this._inDrag = false;
Packit d345d1
	    Clutter.ungrab_pointer(handle);
Packit d345d1
	}
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _handleMotion(handle, event) {
Packit d345d1
	if (this._inDrag) {
Packit d345d1
	    let [x, y] = event.get_coords();
Packit d345d1
            let [actorX, actorY] = this.get_transformed_position();
Packit d345d1
	    this.width = x - this._dragX - actorX;
Packit d345d1
	    this.height = y - this._dragY - actorY;
Packit d345d1
            this.queue_relayout();
Packit d345d1
	}
Packit d345d1
    }
Packit d345d1
});
Packit d345d1
Packit d345d1
/****************************************************************************/
Packit d345d1
Packit d345d1
function test() {
Packit d345d1
    let stage = new Clutter.Stage({ width: 600, height: 600 });
Packit d345d1
    UI.init(stage);
Packit d345d1
Packit d345d1
    let mainBox = new St.BoxLayout({ width: stage.width,
Packit d345d1
				     height: stage.height,
Packit d345d1
				     vertical: true,
Packit d345d1
			             style: 'padding: 10px;'
Packit d345d1
                                     + 'spacing: 5px;'
Packit d345d1
                                     + 'font: 16px sans-serif;'
Packit d345d1
                                     + 'background: black;'
Packit d345d1
                                     + 'color: white;' });
Packit d345d1
    stage.add_actor(mainBox);
Packit d345d1
Packit d345d1
    const DOCS = 'Red lines represent minimum size, blue lines natural size. Drag yellow handle to resize ScrollView. Click on options to change.';
Packit d345d1
Packit d345d1
    let docsLabel = new St.Label({ text: DOCS });
Packit d345d1
    docsLabel.clutter_text.line_wrap = true;
Packit d345d1
    mainBox.add(docsLabel);
Packit d345d1
Packit d345d1
    let bin = new St.Bin({ x_fill: true, y_fill: true, style: 'border: 2px solid #666666;' });
Packit d345d1
    mainBox.add(bin, { x_fill: true, y_fill: true, expand: true });
Packit d345d1
Packit d345d1
    let illustrator = new SizingIllustrator();
Packit d345d1
    bin.add_actor(illustrator);
Packit d345d1
Packit d345d1
    let scrollView = new St.ScrollView();
Packit d345d1
    illustrator.add(scrollView);
Packit d345d1
Packit d345d1
    let box = new St.BoxLayout({ vertical: true });
Packit d345d1
    scrollView.add_actor(box);
Packit d345d1
Packit d345d1
    let flowedBoxes = new FlowedBoxes();
Packit d345d1
    box.add(flowedBoxes, { expand: false, x_fill: true, y_fill: true });
Packit d345d1
Packit d345d1
    let policyBox = new St.BoxLayout({ vertical: false });
Packit d345d1
    mainBox.add(policyBox);
Packit d345d1
Packit d345d1
    policyBox.add(new St.Label({ text: 'Horizontal Policy: ' }));
Packit d345d1
    let hpolicy = new St.Button({ label: 'AUTOMATIC', style: 'text-decoration: underline; color: #4444ff;' });
Packit d345d1
    policyBox.add(hpolicy);
Packit d345d1
Packit d345d1
    let spacer = new St.Bin();
Packit d345d1
    policyBox.add(spacer, { expand: true });
Packit d345d1
Packit d345d1
    policyBox.add(new St.Label({ text: 'Vertical Policy: '}));
Packit d345d1
    let vpolicy = new St.Button({ label: 'AUTOMATIC', style: 'text-decoration: underline; color: #4444ff;' });
Packit d345d1
    policyBox.add(vpolicy);
Packit d345d1
Packit d345d1
    function togglePolicy(button) {
Packit d345d1
        switch(button.label) {
Packit d345d1
        case 'AUTOMATIC':
Packit d345d1
	    button.label = 'ALWAYS';
Packit d345d1
	    break;
Packit d345d1
        case 'ALWAYS':
Packit d345d1
	    button.label = 'NEVER';
Packit d345d1
	    break;
Packit d345d1
        case 'NEVER':
Packit d345d1
	    button.label = 'EXTERNAL';
Packit d345d1
	    break;
Packit d345d1
        case 'EXTERNAL':
Packit d345d1
	    button.label = 'AUTOMATIC';
Packit d345d1
	    break;
Packit d345d1
        }
Packit d345d1
        scrollView.set_policy(Gtk.PolicyType[hpolicy.label], Gtk.PolicyType[vpolicy.label]);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    hpolicy.connect('clicked', () => { togglePolicy(hpolicy); });
Packit d345d1
    vpolicy.connect('clicked', () => { togglePolicy(vpolicy); });
Packit d345d1
Packit d345d1
    let fadeBox = new St.BoxLayout({ vertical: false });
Packit d345d1
    mainBox.add(fadeBox);
Packit d345d1
Packit d345d1
    spacer = new St.Bin();
Packit d345d1
    fadeBox.add(spacer, { expand: true });
Packit d345d1
Packit d345d1
    fadeBox.add(new St.Label({ text: 'Padding: '}));
Packit d345d1
    let paddingButton = new St.Button({ label: 'No', style: 'text-decoration: underline; color: #4444ff;padding-right:3px;' });
Packit d345d1
    fadeBox.add(paddingButton);
Packit d345d1
Packit d345d1
    fadeBox.add(new St.Label({ text: 'Borders: '}));
Packit d345d1
    let borderButton = new St.Button({ label: 'No', style: 'text-decoration: underline; color: #4444ff;padding-right:3px;' });
Packit d345d1
    fadeBox.add(borderButton);
Packit d345d1
Packit d345d1
    fadeBox.add(new St.Label({ text: 'Vertical Fade: '}));
Packit d345d1
    let vfade = new St.Button({ label: 'No', style: 'text-decoration: underline; color: #4444ff;' });
Packit d345d1
    fadeBox.add(vfade);
Packit d345d1
Packit d345d1
    fadeBox.add(new St.Label({ text: 'Overlay scrollbars: '}));
Packit d345d1
    let overlay = new St.Button({ label: 'No', style: 'text-decoration: underline; color: #4444ff;' });
Packit d345d1
    fadeBox.add(overlay);
Packit d345d1
Packit d345d1
    function togglePadding(button) {
Packit d345d1
        switch(button.label) {
Packit d345d1
        case 'No':
Packit d345d1
	    button.label = 'Yes';
Packit d345d1
	    break;
Packit d345d1
        case 'Yes':
Packit d345d1
	    button.label = 'No';
Packit d345d1
	    break;
Packit d345d1
        }
Packit d345d1
        if (scrollView.style == null)
Packit d345d1
            scrollView.style = (button.label == 'Yes' ? 'padding: 10px;' : 'padding: 0;');
Packit d345d1
        else
Packit d345d1
            scrollView.style += (button.label == 'Yes' ? 'padding: 10px;' : 'padding: 0;');
Packit d345d1
    }
Packit d345d1
Packit d345d1
    paddingButton.connect('clicked', () => { togglePadding(paddingButton); });
Packit d345d1
Packit d345d1
    function toggleBorders(button) {
Packit d345d1
        switch(button.label) {
Packit d345d1
        case 'No':
Packit d345d1
	    button.label = 'Yes';
Packit d345d1
	    break;
Packit d345d1
        case 'Yes':
Packit d345d1
	    button.label = 'No';
Packit d345d1
	    break;
Packit d345d1
        }
Packit d345d1
        if (scrollView.style == null)
Packit d345d1
            scrollView.style = (button.label == 'Yes' ? 'border: 2px solid red;' : 'border: 0;');
Packit d345d1
        else
Packit d345d1
            scrollView.style += (button.label == 'Yes' ? 'border: 2px solid red;' : 'border: 0;');
Packit d345d1
    }
Packit d345d1
Packit d345d1
    borderButton.connect('clicked', () => { toggleBorders(borderButton); });
Packit d345d1
Packit d345d1
    function toggleFade(button) {
Packit d345d1
        switch(button.label) {
Packit d345d1
        case 'No':
Packit d345d1
	    button.label = 'Yes';
Packit d345d1
	    break;
Packit d345d1
        case 'Yes':
Packit d345d1
	    button.label = 'No';
Packit d345d1
	    break;
Packit d345d1
        }
Packit d345d1
        scrollView.set_style_class_name(button.label == 'Yes' ? 'vfade' : '');
Packit d345d1
    }
Packit d345d1
Packit d345d1
    vfade.connect('clicked', () => { toggleFade(vfade); });
Packit d345d1
    toggleFade(vfade);
Packit d345d1
Packit d345d1
    function toggleOverlay(button) {
Packit d345d1
        switch(button.label) {
Packit d345d1
        case 'No':
Packit d345d1
	    button.label = 'Yes';
Packit d345d1
	    break;
Packit d345d1
        case 'Yes':
Packit d345d1
	    button.label = 'No';
Packit d345d1
	    break;
Packit d345d1
        }
Packit d345d1
        scrollView.overlay_scrollbars = (button.label == 'Yes');
Packit d345d1
    }
Packit d345d1
Packit d345d1
    overlay.connect('clicked', () => { toggleOverlay(overlay); });
Packit d345d1
Packit d345d1
    UI.main(stage);
Packit d345d1
}
Packit d345d1
test();