/** * サーフェイスは、{@link Ext.draw.Component}内でメソッドを描画するインターフェイスです。サーフェイスには、スプライトを描画する、スプライトのバウンディングボックスを取得する、スプライトをキャンバスに追加する、その他のグラフィックコンポーネントを初期化するなど、さまざまなメソッドが含まれています。このクラスで最も使用されるメソッドの1つは、スプライトをサーフェイスに追加する`add`メソッドです。 * * サーフェイスメソッドのほとんどは抽象的であり、VMLエンジンまたはSVGエンジン内での実装で具体化します。 * * サーフェイスインスタンスには、描画コンポーネントのプロパティとしてアクセスできます。例えば、 * * drawComponent.getSurface('main').add({ * type: 'circle', * fill: '#ffc', * radius: 100, * x: 100, * y: 100 * }); * * `add`メソッド内で渡される設定オブジェクトは、{@link Ext.draw.sprite.Sprite}クラスのドキュメンテーションで述べられているものと同じです。 * * ##例 * * drawComponent.getSurface('main').add([ * { * type: 'circle', * radius: 10, * fill: '#f00', * x: 10, * y: 10, * group: 'circles' * }, * { * type: 'circle', * radius: 10, * fill: '#0f0', * x: 50, * y: 50, * group: 'circles' * }, * { * type: 'circle', * radius: 10, * fill: '#00f', * x: 100, * y: 100, * group: 'circles' * }, * { * type: 'rect', * radius: 10, * x: 10, * y: 10, * group: 'rectangles' * }, * { * type: 'rect', * radius: 10, * x: 50, * y: 50, * group: 'rectangles' * }, * { * type: 'rect', * radius: 10, * x: 100, * y: 100, * group: 'rectangles' * } * ]); * */ Ext.define('Ext.draw.Surface', { extend: 'Ext.Component', xtype: 'surface', requires: [ 'Ext.draw.sprite.*', 'Ext.draw.gradient.*', 'Ext.draw.sprite.AttributeDefinition', 'Ext.draw.Matrix', 'Ext.draw.Draw', 'Ext.draw.Group' ], uses: [ "Ext.draw.engine.Canvas" ], defaultIdPrefix: 'ext-surface-', /** * ピクセル密度を報告されているデバイス。 */ devicePixelRatio: window.devicePixelRatio || 1, statics: { /** * zIndexによるスプライトリストの確実なソート。やるべきこと:性能のを向上させる。GCの影響を減らす。 * @param {Array} list */ stableSort: function (list) { if (list.length < 2) { return; } var keys = {}, sortedKeys, result = [], i, ln, zIndex; for (i = 0, ln = list.length; i < ln; i++) { zIndex = list[i].attr.zIndex; if (!keys[zIndex]) { keys[zIndex] = [list[i]]; } else { keys[zIndex].push(list[i]); } } sortedKeys = Ext.Object.getKeys(keys).sort(function (a, b) {return a - b;}); for (i = 0, ln = sortedKeys.length; i < ln; i++) { result.push.apply(result, keys[sortedKeys[i]]); } for (i = 0, ln = list.length; i < ln; i++) { list[i] = result[i]; } } }, config: { /** * @cfg {Array} * このコンポーネントに関連するサーフェイスの領域。 */ region: null, /** * @cfg {Object} * 現在のサーフェイスの背景スプライトのコンフィグ */ background: null, /** * @cfg {Ext.draw.Group} * サーフェイスのデフォルトグループ。 */ items: [], /** * @cfg {Array} * グループの配列。 */ groups: [], /** * @cfg {Boolean} * サーフェイスに再描画が必要かどうかを示します。 */ dirty: false }, dirtyPredecessor: 0, constructor: function (config) { var me = this; me.predecessors = []; me.successors = []; me.pendingRenderFrame = false; me.callSuper([config]); me.matrix = new Ext.draw.Matrix(); me.inverseMatrix = me.matrix.inverse(me.inverseMatrix); me.resetTransform(); }, /** * デバイスのピクセル数に揃えるために値を丸めます。 * @param {Number} num 揃える値。 * @return {Number} 揃えた結果。 */ roundPixel: function (num) { return Math.round(this.devicePixelRatio * num) / this.devicePixelRatio; }, /** * 別のサーフェイスが更新された後に、描画するサーフェイスをマークします。 * @param {Ext.draw.Surface} surface 待機中のサーフェイス。 */ waitFor: function (surface) { var me = this, predecessors = me.predecessors; if (!Ext.Array.contains(predecessors, surface)) { predecessors.push(surface); surface.successors.push(me); if (surface._dirty) { me.dirtyPredecessor++; } } }, setDirty: function (dirty) { if (this._dirty !== dirty) { var successors = this.successors, successor, i, ln = successors.length; for (i = 0; i < ln; i++) { successor = successors[i]; if (dirty) { successor.dirtyPredecessor++; successor.setDirty(true); } else { successor.dirtyPredecessor--; if (successor.dirtyPredecessor === 0 && successor.pendingRenderFrame) { successor.renderFrame(); } } } this._dirty = dirty; } }, applyElement: function (newElement, oldElement) { if (oldElement) { oldElement.set(newElement); } else { oldElement = Ext.Element.create(newElement); } this.setDirty(true); return oldElement; }, applyBackground: function (background, oldBackground) { this.setDirty(true); if (Ext.isString(background)) { background = { fillStyle: background }; } return Ext.factory(background, Ext.draw.sprite.Rect, oldBackground); }, applyRegion: function (region, oldRegion) { if (oldRegion && region[0] === oldRegion[0] && region[1] === oldRegion[1] && region[2] === oldRegion[2] && region[3] === oldRegion[3]) { return; } if (Ext.isArray(region)) { return [region[0], region[1], region[2], region[3]]; } else if (Ext.isObject(region)) { return [ region.x || region.left, region.y || region.top, region.width || (region.right - region.left), region.height || (region.bottom - region.top) ]; } }, updateRegion: function (region) { var me = this, l = region[0], t = region[1], r = l + region[2], b = t + region[3], background = this.getBackground(), element = me.element; element.setBox({ top: Math.floor(t), left: Math.floor(l), width: Math.ceil(r - Math.floor(l)), height: Math.ceil(b - Math.floor(t)) }); if (background) { background.setAttributes({ x: 0, y: 0, width: Math.ceil(r - Math.floor(l)), height: Math.ceil(b - Math.floor(t)) }); } me.setDirty(true); }, /** * サーフェイスのマトリクスをリセットします。 */ resetTransform: function () { this.matrix.set(1, 0, 0, 1, 0, 0); this.inverseMatrix.set(1, 0, 0, 1, 0, 0); this.setDirty(true); }, updateComponent: function (component, oldComponent) { if (component) { component.element.dom.appendChild(this.element.dom); } }, /** * サーフェイスへスプライトを追加します。オブジェクトの数(任意)をパラメータとして入力することができます。このメソッドに渡すための設定オブジェクトについては、{@link Ext.draw.sprite.Sprite}を参照してください。 * * 例えば、 * * drawComponent.surface.add({ * type: 'circle', * fill: '#ffc', * radius: 100, * x: 100, * y: 100 * }); * */ add: function () { var me = this, args = Array.prototype.slice.call(arguments), argIsArray = Ext.isArray(args[0]), results = [], sprite, sprites, items, i, ln, group, groups; items = Ext.Array.clean(argIsArray ? args[0] : args); sprites = me.prepareItems(items); for (i = 0, ln = sprites.length; i < ln; i++) { sprite = sprites[i]; groups = sprite.group; if (groups.length) { for (i = 0, ln = groups.length; i < ln; i++) { group = groups[i]; me.getGroup(group).add(sprite); } } me.getItems().add(sprite); results.push(sprite); sprite.setParent(this); me.onAdd(sprite); } me.dirtyZIndex = true; me.setDirty(true); if (!argIsArray && results.length === 1) { return results[0]; } else { return results; } }, /** * @protected * サーフェイスにスプライトを追加する際に起動されます。 * @param {Ext.draw.sprite.Sprite} sprite 追加されるスプライト。 */ onAdd: Ext.emptyFn, /** * 指定したスプライトをサーフェイスから削除し、オプションでプロセス中のスプライトを廃棄します。スプライト自身の`remove`メソッドを呼び出すことも出来ます。 * * 例えば、 * * drawComponent.surface.remove(sprite); * // or... * sprite.remove(); * * @param {Ext.draw.sprite.Sprite} sprite * @param {Boolean} destroySprite */ remove: function (sprite, destroySprite) { if (sprite) { if (destroySprite === true) { sprite.destroy(); } else { this.getGroups().each(function (item) { item.remove(sprite); }); this.getItems().remove(sprite); } this.dirtyZIndex = true; this.setDirty(true); } }, /** * サーフェースから全てのスプライトを削除し、オプションでプロセス中からスプライトを廃棄します。 * * 例えば、 * * drawComponent.surface.removeAll(); * */ removeAll: function () { this.getItems().clear(); this.dirtyZIndex = true; }, // @private applyItems: function (items, oldItems) { var result; if (items instanceof Ext.draw.Group) { result = items; } else { result = new Ext.draw.Group({surface: this}); result.autoDestroy = true; result.addAll(this.prepareItems(items)); } this.setDirty(true); return result; }, /** * @private * サーフェイスアイテムを初期化し、デフォルトに戻します。 */ prepareItems: function (items) { items = [].concat(items); // Make sure defaults are applied and item is initialized var me = this, item, i, ln, j, removeSprite = function (sprite) { this.remove(sprite, false); }; for (i = 0, ln = items.length; i < ln; i++) { item = items[i]; if (!(item instanceof Ext.draw.sprite.Sprite)) { // Temporary, just take in configs... item = items[i] = me.createItem(item); } for (j = 0; j < item.group.length; j++) { me.getGroup(item.group[j]).add(item); } item.on('beforedestroy', removeSprite, me); } return items; }, applyGroups: function (groups, oldGroups) { var result; if (groups instanceof Ext.util.MixedCollection) { result = groups; } else { result = new Ext.util.MixedCollection(); result.addAll(groups); } if (oldGroups) { oldGroups.each(function (group) { if (!result.contains()) { group.destroy(); } }); oldGroups.destroy(); } this.setDirty(true); return result; }, /** * @deprecated グループを直接使用しない。新しいグループまたは現在のサーフェイスに関連づけられた既存のグループを返します。返されるのは{@link Ext.draw.Group}グループです。 * * 例えば、 * * var spriteGroup = drawComponent.surface.getGroup('someGroupId'); * * @param {String} id グループの固有の識別子。 * @return {Ext.draw.Group} グループ。 */ getGroup: function (id) { var group; if (typeof id === "string") { group = this.getGroups().get(id); if (!group) { group = this.createGroup(id); } } else { group = id; } return group; }, /** * @private * @deprecated グループを直接使用しない * @param {String} id * @return {Ext.draw.Group} グループ。 */ createGroup: function (id) { var group = this.getGroups().get(id); if (!group) { group = new Ext.draw.Group({surface: this}); group.id = id || Ext.id(null, 'ext-surface-group-'); this.getGroups().add(group); } this.setDirty(true); return group; }, /** * @private * @deprecated グループを直接使用しない * @param {Ext.draw.Group} group */ removeGroup: function (group) { if (Ext.isString(group)) { group = this.getGroups().get(group); } if (group) { this.getGroups().remove(group); group.destroy(); } this.setDirty(true); }, /** * @private アイテムを作成し、そのアイテムをサーフェイスに付与します。 * `add`を呼び出すときに内部メソッドとして呼び出されます。 */ createItem: function (config) { var sprite = Ext.create(config.xclass || 'sprite.' + config.type, config); return sprite; }, /** * @deprecated `sprite.getBBox(isWithoutTransform)`を直接使用してください。 * @param {Ext.draw.sprite.Sprite} sprite * @param {Boolean} isWithoutTransform * @return {Object} */ getBBox: function (sprite, isWithoutTransform) { return sprite.getBBox(isWithoutTransform); }, /** * サーフェイスコンテンツを(スプライトを処理することなく)空にします。 */ clear: Ext.emptyFn, /** * @private * 前回ソートして以来、変更したものがあった場合、z-indexの順序でアイテムを並べます。 */ orderByZIndex: function () { var me = this, items = me.getItems().items, dirtyZIndex = false, i, ln; if (me.getDirty()) { for (i = 0, ln = items.length; i < ln; i++) { if (items[i].attr.dirtyZIndex) { dirtyZIndex = true; break; } } if (dirtyZIndex) { // sort by zIndex Ext.draw.Surface.stableSort(items); this.setDirty(true); } for (i = 0, ln = items.length; i < ln; i++) { items[i].attr.dirtyZIndex = false; } } }, /** * 要素を強制的に再描画させます。 */ repaint: function () { var me = this; me.repaint = Ext.emptyFn; setTimeout(function () { delete me.repaint; me.element.repaint(); }, 1); }, /** * キャンバスの再描画を開始します。 */ renderFrame: function () { if (!this.element) { return; } if (this.dirtyPredecessor > 0) { this.pendingRenderFrame = true; } var me = this, region = this.getRegion(), background = me.getBackground(), items = me.getItems().items, item, i, ln; // Cannot render before the surface is placed. if (!region) { return; } // This will also check the dirty flags of the sprites. me.orderByZIndex(); if (me.getDirty()) { me.clear(); me.clearTransform(); if (background) { me.renderSprite(background); } for (i = 0, ln = items.length; i < ln; i++) { item = items[i]; item.applyTransformations(); if (false === me.renderSprite(item)) { return; } item.attr.textPositionCount = me.textPosition; } me.setDirty(false); } }, /** * @private * 単一のスプライトをサーフェイスに描画します。`renderFrame`メソッドの外側から呼び出さないでください。 * * @param {Ext.draw.sprite.Sprite} sprite 描画されるスプライト。 * @return {Boolean} 描画の継続を停止する場合は、`false`を返します。 */ renderSprite: Ext.emptyFn, /** * @private * サーフェイス上にある現在の変換状態を消去します。 */ clearTransform: Ext.emptyFn, /** * サーフェイスがダーティーな場合、trueを返します。 * @return {Boolean} サーフェイスがダーティーな場合、trueを返します */ getDirty: function () { return this._dirty; }, /** * サーフェースを廃棄します。この処理は全てのコンポーネントがここから削除され、 またそのDOM要素への参照が削除されることによって完了します。 * * 例: * * drawComponent.surface.destroy(); */ destroy: function () { var me = this; me.removeAll(); me.setBackground(null); me.setGroups([]); me.getGroups().destroy(); me.predecessors = null; me.successors = null; me.callSuper(); } });