/**
|
|
* @constructor Controller
|
|
*
|
|
* A Controller controls the reflows and repaints of all visual components
|
|
*/
|
|
function Controller () {
|
|
this.id = util.randomUUID();
|
|
this.components = {};
|
|
|
|
this.repaintTimer = undefined;
|
|
this.reflowTimer = undefined;
|
|
}
|
|
|
|
/**
|
|
* Add a component to the controller
|
|
* @param {Component | Controller} component
|
|
*/
|
|
Controller.prototype.add = function (component) {
|
|
// validate the component
|
|
if (component.id == undefined) {
|
|
throw new Error('Component has no field id');
|
|
}
|
|
if (!(component instanceof Component) && !(component instanceof Controller)) {
|
|
throw new TypeError('Component must be an instance of ' +
|
|
'prototype Component or Controller');
|
|
}
|
|
|
|
// add the component
|
|
component.controller = this;
|
|
this.components[component.id] = component;
|
|
};
|
|
|
|
/**
|
|
* Request a reflow. The controller will schedule a reflow
|
|
*/
|
|
Controller.prototype.requestReflow = function () {
|
|
if (!this.reflowTimer) {
|
|
var me = this;
|
|
this.reflowTimer = setTimeout(function () {
|
|
me.reflowTimer = undefined;
|
|
me.reflow();
|
|
}, 0);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Request a repaint. The controller will schedule a repaint
|
|
*/
|
|
Controller.prototype.requestRepaint = function () {
|
|
if (!this.repaintTimer) {
|
|
var me = this;
|
|
this.repaintTimer = setTimeout(function () {
|
|
me.repaintTimer = undefined;
|
|
me.repaint();
|
|
}, 0);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Repaint all components
|
|
*/
|
|
Controller.prototype.repaint = function () {
|
|
var changed = false;
|
|
|
|
// cancel any running repaint request
|
|
if (this.repaintTimer) {
|
|
clearTimeout(this.repaintTimer);
|
|
this.repaintTimer = undefined;
|
|
}
|
|
|
|
var done = {};
|
|
|
|
function repaint(component, id) {
|
|
if (!(id in done)) {
|
|
// first repaint the components on which this component is dependent
|
|
if (component.depends) {
|
|
component.depends.forEach(function (dep) {
|
|
repaint(dep, dep.id);
|
|
});
|
|
}
|
|
if (component.parent) {
|
|
repaint(component.parent, component.parent.id);
|
|
}
|
|
|
|
// repaint the component itself and mark as done
|
|
changed = component.repaint() || changed;
|
|
done[id] = true;
|
|
}
|
|
}
|
|
|
|
util.forEach(this.components, repaint);
|
|
|
|
// immediately reflow when needed
|
|
if (changed) {
|
|
this.reflow();
|
|
}
|
|
// TODO: limit the number of nested reflows/repaints, prevent loop
|
|
};
|
|
|
|
/**
|
|
* Reflow all components
|
|
*/
|
|
Controller.prototype.reflow = function () {
|
|
var resized = false;
|
|
|
|
// cancel any running repaint request
|
|
if (this.reflowTimer) {
|
|
clearTimeout(this.reflowTimer);
|
|
this.reflowTimer = undefined;
|
|
}
|
|
|
|
var done = {};
|
|
|
|
function reflow(component, id) {
|
|
if (!(id in done)) {
|
|
// first reflow the components on which this component is dependent
|
|
if (component.depends) {
|
|
component.depends.forEach(function (dep) {
|
|
reflow(dep, dep.id);
|
|
});
|
|
}
|
|
if (component.parent) {
|
|
reflow(component.parent, component.parent.id);
|
|
}
|
|
|
|
// reflow the component itself and mark as done
|
|
resized = component.reflow() || resized;
|
|
done[id] = true;
|
|
}
|
|
}
|
|
|
|
util.forEach(this.components, reflow);
|
|
|
|
// immediately repaint when needed
|
|
if (resized) {
|
|
this.repaint();
|
|
}
|
|
// TODO: limit the number of nested reflows/repaints, prevent loop
|
|
};
|
|
|
|
// export
|
|
if (typeof exports !== 'undefined') {
|
|
exports.Controller = Controller;
|
|
}
|