discordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloudtweetdeckhipchattelegramhangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teams
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
978 lines
29 KiB
978 lines
29 KiB
/** |
|
* A Surface is an interface to render methods inside {@link Ext.draw.Component}. |
|
* |
|
* Most of the Surface methods are abstract and they have a concrete implementation |
|
* in {@link Ext.draw.engine.Vml VML} or {@link Ext.draw.engine.Svg SVG} engines. |
|
* |
|
* A Surface contains methods to render {@link Ext.draw.Sprite sprites}, get bounding |
|
* boxes of sprites, add sprites to the canvas, initialize other graphic components, etc. |
|
* |
|
* ## Adding sprites to surface |
|
* |
|
* One of the most used methods for this class is the {@link #add} method, to add Sprites to |
|
* the surface. For example: |
|
* |
|
* drawComponent.surface.add({ |
|
* type: 'circle', |
|
* fill: '#ffc', |
|
* radius: 100, |
|
* x: 100, |
|
* y: 100 |
|
* }); |
|
* |
|
* The configuration object passed in the `add` method is the same as described in the |
|
* {@link Ext.draw.Sprite} class documentation. |
|
* |
|
* Sprites can also be added to surface by setting their surface config at creation time: |
|
* |
|
* var sprite = Ext.create('Ext.draw.Sprite', { |
|
* type: 'circle', |
|
* fill: '#ff0', |
|
* surface: drawComponent.surface, |
|
* radius: 5 |
|
* }); |
|
* |
|
* In order to properly apply properties and render the sprite we have to |
|
* `show` the sprite setting the option `redraw` to `true`: |
|
* |
|
* sprite.show(true); |
|
* |
|
*/ |
|
Ext.define('Ext.draw.Surface', { |
|
|
|
/* Begin Definitions */ |
|
|
|
mixins: { |
|
observable: 'Ext.util.Observable' |
|
}, |
|
|
|
requires: ['Ext.draw.CompositeSprite'], |
|
uses: ['Ext.draw.engine.Svg', 'Ext.draw.engine.Vml', 'Ext.draw.engine.SvgExporter', 'Ext.draw.engine.ImageExporter'], |
|
|
|
separatorRe: /[, ]+/, |
|
|
|
enginePriority: ['Svg', 'Vml'], |
|
|
|
statics: { |
|
/** |
|
* Creates and returns a new concrete Surface instance appropriate for the current environment. |
|
* @param {Object} config Initial configuration for the Surface instance |
|
* @param {String[]} enginePriority (Optional) order of implementations to use; the first one that is |
|
* available in the current environment will be used. Defaults to `['Svg', 'Vml']`. |
|
* @return {Object} The created Surface or false. |
|
* @static |
|
*/ |
|
create: function(config, enginePriority) { |
|
enginePriority = enginePriority || this.prototype.enginePriority; |
|
|
|
var i = 0, |
|
len = enginePriority.length; |
|
|
|
for (; i < len; i++) { |
|
if (Ext.supports[enginePriority[i]]) { |
|
return Ext.create('Ext.draw.engine.' + enginePriority[i], config); |
|
} |
|
} |
|
return false; |
|
}, |
|
|
|
/** |
|
* Exports a {@link Ext.draw.Surface surface} in a different format. |
|
* The surface may be exported to an SVG string, using the |
|
* {@link Ext.draw.engine.SvgExporter}. It may also be exported |
|
* as an image using the {@link Ext.draw.engine.ImageExporter ImageExporter}. |
|
* Note that this requires sending data to a remote server to process |
|
* the SVG into an image, see the {@link Ext.draw.engine.ImageExporter} for |
|
* more details. |
|
* @param {Ext.draw.Surface} surface The surface to export. |
|
* @param {Object} [config] The configuration to be passed to the exporter. |
|
* See the export method for the appropriate exporter for the relevant |
|
* configuration options |
|
* @return {Object} See the return types for the appropriate exporter |
|
* @static |
|
*/ |
|
save: function(surface, config) { |
|
config = config || {}; |
|
var exportTypes = { |
|
'image/png': 'Image', |
|
'image/jpeg': 'Image', |
|
'image/svg+xml': 'Svg' |
|
}, |
|
prefix = exportTypes[config.type] || 'Svg', |
|
exporter = Ext.draw.engine[prefix + 'Exporter']; |
|
|
|
return exporter.generate(surface, config); |
|
|
|
} |
|
}, |
|
|
|
/* End Definitions */ |
|
|
|
// @private |
|
availableAttrs: { |
|
blur: 0, |
|
"clip-rect": "0 0 1e9 1e9", |
|
cursor: "default", |
|
cx: 0, |
|
cy: 0, |
|
'dominant-baseline': 'auto', |
|
fill: "none", |
|
"fill-opacity": 1, |
|
font: '10px "Arial"', |
|
"font-family": '"Arial"', |
|
"font-size": "10", |
|
"font-style": "normal", |
|
"font-weight": 400, |
|
gradient: "", |
|
height: 0, |
|
hidden: false, |
|
href: "http://sencha.com/", |
|
opacity: 1, |
|
path: "M0,0", |
|
radius: 0, |
|
rx: 0, |
|
ry: 0, |
|
scale: "1 1", |
|
src: "", |
|
stroke: "none", |
|
"stroke-dasharray": "", |
|
"stroke-linecap": "butt", |
|
"stroke-linejoin": "butt", |
|
"stroke-miterlimit": 0, |
|
"stroke-opacity": 1, |
|
"stroke-width": 1, |
|
target: "_blank", |
|
text: "", |
|
"text-anchor": "middle", |
|
title: "Ext Draw", |
|
width: 0, |
|
x: 0, |
|
y: 0, |
|
zIndex: 0 |
|
}, |
|
|
|
/** |
|
* @cfg {Number} height |
|
* The height of this component in pixels (defaults to auto). |
|
*/ |
|
/** |
|
* @cfg {Number} width |
|
* The width of this component in pixels (defaults to auto). |
|
*/ |
|
|
|
container: undefined, |
|
height: 352, |
|
width: 512, |
|
x: 0, |
|
y: 0, |
|
|
|
/** |
|
* @cfg {Ext.draw.Sprite[]} items |
|
* Array of sprites or sprite config objects to add initially to the surface. |
|
*/ |
|
|
|
/** |
|
* @private Flag indicating that the surface implementation requires sprites to be maintained |
|
* in order of their zIndex. Impls that don't require this can set it to false. |
|
*/ |
|
orderSpritesByZIndex: true, |
|
|
|
/** |
|
* @event |
|
* Fires when a mousedown is detected within the surface. |
|
* @param {Ext.EventObject} e An object encapsulating the DOM event. |
|
*/ |
|
|
|
/** |
|
* @event |
|
* Fires when a mouseup is detected within the surface. |
|
* @param {Ext.EventObject} e An object encapsulating the DOM event. |
|
*/ |
|
|
|
/** |
|
* @event |
|
* Fires when a mouseover is detected within the surface. |
|
* @param {Ext.EventObject} e An object encapsulating the DOM event. |
|
*/ |
|
|
|
/** |
|
* @event |
|
* Fires when a mouseout is detected within the surface. |
|
* @param {Ext.EventObject} e An object encapsulating the DOM event. |
|
*/ |
|
|
|
/** |
|
* @event |
|
* Fires when a mousemove is detected within the surface. |
|
* @param {Ext.EventObject} e An object encapsulating the DOM event. |
|
*/ |
|
|
|
/** |
|
* @event |
|
* Fires when a mouseenter is detected within the surface. |
|
* @param {Ext.EventObject} e An object encapsulating the DOM event. |
|
*/ |
|
|
|
/** |
|
* @event |
|
* Fires when a mouseleave is detected within the surface. |
|
* @param {Ext.EventObject} e An object encapsulating the DOM event. |
|
*/ |
|
|
|
/** |
|
* @event |
|
* Fires when a click is detected within the surface. |
|
* @param {Ext.EventObject} e An object encapsulating the DOM event. |
|
*/ |
|
|
|
/** |
|
* @event |
|
* Fires when a dblclick is detected within the surface. |
|
* @param {Ext.EventObject} e An object encapsulating the DOM event. |
|
*/ |
|
|
|
/** |
|
* Creates new Surface. |
|
* @param {Object} config (optional) Config object. |
|
*/ |
|
constructor: function(config) { |
|
var me = this; |
|
config = config || {}; |
|
Ext.apply(me, config); |
|
|
|
me.domRef = Ext.getDoc().dom; |
|
|
|
me.customAttributes = {}; |
|
|
|
me.mixins.observable.constructor.call(me); |
|
|
|
me.getId(); |
|
me.initGradients(); |
|
me.initItems(); |
|
if (me.renderTo) { |
|
me.render(me.renderTo); |
|
delete me.renderTo; |
|
} |
|
me.initBackground(config.background); |
|
}, |
|
|
|
// @private called to initialize components in the surface |
|
// this is dependent on the underlying implementation. |
|
initSurface: Ext.emptyFn, |
|
|
|
// @private called to setup the surface to render an item |
|
//this is dependent on the underlying implementation. |
|
renderItem: Ext.emptyFn, |
|
|
|
// @private |
|
renderItems: Ext.emptyFn, |
|
|
|
// @private |
|
setViewBox: function (x, y, width, height) { |
|
if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) { |
|
this.viewBox = {x: x, y: y, width: width, height: height}; |
|
this.applyViewBox(); |
|
} |
|
}, |
|
|
|
/** |
|
* Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out. |
|
* |
|
* For example: |
|
* |
|
* drawComponent.surface.addCls(sprite, 'x-visible'); |
|
* |
|
* @param {Object} sprite The sprite to add the class to. |
|
* @param {String/String[]} className The CSS class to add, or an array of classes |
|
* @method |
|
*/ |
|
addCls: Ext.emptyFn, |
|
|
|
/** |
|
* Removes one or more CSS classes from the element. |
|
* |
|
* For example: |
|
* |
|
* drawComponent.surface.removeCls(sprite, 'x-visible'); |
|
* |
|
* @param {Object} sprite The sprite to remove the class from. |
|
* @param {String/String[]} className The CSS class to remove, or an array of classes |
|
* @method |
|
*/ |
|
removeCls: Ext.emptyFn, |
|
|
|
/** |
|
* Sets CSS style attributes to an element. |
|
* |
|
* For example: |
|
* |
|
* drawComponent.surface.setStyle(sprite, { |
|
* 'cursor': 'pointer' |
|
* }); |
|
* |
|
* @param {Object} sprite The sprite to add, or an array of classes to |
|
* @param {Object} styles An Object with CSS styles. |
|
* @method |
|
*/ |
|
setStyle: Ext.emptyFn, |
|
|
|
// @private |
|
initGradients: function() { |
|
if (this.hasOwnProperty('gradients')) { |
|
var gradients = this.gradients, |
|
fn = this.addGradient, |
|
g, gLen; |
|
|
|
if (gradients) { |
|
for (g = 0, gLen = gradients.length; g < gLen; g++) { |
|
if (fn.call(this, gradients[g], g, gLen) === false) { |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
}, |
|
|
|
// @private |
|
initItems: function() { |
|
var items = this.items; |
|
this.items = new Ext.draw.CompositeSprite(); |
|
this.items.autoDestroy = true; |
|
this.groups = new Ext.draw.CompositeSprite(); |
|
if (items) { |
|
this.add(items); |
|
} |
|
}, |
|
|
|
// @private |
|
initBackground: function(config) { |
|
var me = this, |
|
width = me.width, |
|
height = me.height, |
|
gradientId, gradient; |
|
if (Ext.isString(config)) { |
|
config = { |
|
fill : config |
|
}; |
|
} |
|
if (config) { |
|
if (config.gradient) { |
|
gradient = config.gradient; |
|
gradientId = gradient.id; |
|
me.addGradient(gradient); |
|
me.background = me.add({ |
|
type: 'rect', |
|
isBackground: true, |
|
x: 0, |
|
y: 0, |
|
width: width, |
|
height: height, |
|
fill: 'url(#' + gradientId + ')', |
|
zIndex: -1 |
|
}); |
|
} else if (config.fill) { |
|
me.background = me.add({ |
|
type: 'rect', |
|
isBackground: true, |
|
x: 0, |
|
y: 0, |
|
width: width, |
|
height: height, |
|
fill: config.fill, |
|
zIndex: -1 |
|
}); |
|
} else if (config.image) { |
|
me.background = me.add({ |
|
type: 'image', |
|
isBackground: true, |
|
x: 0, |
|
y: 0, |
|
width: width, |
|
height: height, |
|
src: config.image, |
|
zIndex: -1 |
|
}); |
|
} |
|
// prevent me.background to jeopardize me.items.getBBox |
|
me.background.bboxExcluded = true; |
|
} |
|
}, |
|
|
|
/** |
|
* Sets the size of the surface. Accomodates the background (if any) to fit the new size too. |
|
* |
|
* For example: |
|
* |
|
* drawComponent.surface.setSize(500, 500); |
|
* |
|
* This method is generally called when also setting the size of the draw Component. |
|
* |
|
* @param {Number} w The new width of the canvas. |
|
* @param {Number} h The new height of the canvas. |
|
*/ |
|
setSize: function(w, h) { |
|
this.applyViewBox(); |
|
}, |
|
|
|
// @private |
|
scrubAttrs: function(sprite) { |
|
var i, |
|
attrs = {}, |
|
exclude = {}, |
|
sattr = sprite.attr; |
|
for (i in sattr) { |
|
// Narrow down attributes to the main set |
|
if (this.translateAttrs.hasOwnProperty(i)) { |
|
// Translated attr |
|
attrs[this.translateAttrs[i]] = sattr[i]; |
|
exclude[this.translateAttrs[i]] = true; |
|
} |
|
else if (this.availableAttrs.hasOwnProperty(i) && !exclude[i]) { |
|
// Passtrhough attr |
|
attrs[i] = sattr[i]; |
|
} |
|
} |
|
return attrs; |
|
}, |
|
|
|
// @private |
|
onClick: function(e) { |
|
this.processEvent('click', e); |
|
}, |
|
|
|
// @private |
|
onDblClick: function(e) { |
|
this.processEvent('dblclick', e); |
|
}, |
|
|
|
// @private |
|
onMouseUp: function(e) { |
|
this.processEvent('mouseup', e); |
|
}, |
|
|
|
// @private |
|
onMouseDown: function(e) { |
|
this.processEvent('mousedown', e); |
|
}, |
|
|
|
// @private |
|
onMouseOver: function(e) { |
|
this.processEvent('mouseover', e); |
|
}, |
|
|
|
// @private |
|
onMouseOut: function(e) { |
|
this.processEvent('mouseout', e); |
|
}, |
|
|
|
// @private |
|
onMouseMove: function(e) { |
|
this.fireEvent('mousemove', e); |
|
}, |
|
|
|
// @private |
|
onMouseEnter: Ext.emptyFn, |
|
|
|
// @private |
|
onMouseLeave: Ext.emptyFn, |
|
|
|
/** |
|
* Adds a gradient definition to the Surface. Note that in some surface engines, adding |
|
* a gradient via this method will not take effect if the surface has already been rendered. |
|
* Therefore, it is preferred to pass the gradients as an item to the surface config, rather |
|
* than calling this method, especially if the surface is rendered immediately (e.g. due to |
|
* 'renderTo' in its config). For more information on how to create gradients in the Chart |
|
* configuration object please refer to {@link Ext.chart.Chart}. |
|
* |
|
* The gradient object to be passed into this method is composed by: |
|
* |
|
* - **id** - string - The unique name of the gradient. |
|
* - **angle** - number, optional - The angle of the gradient in degrees. |
|
* - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values. |
|
* |
|
* For example: |
|
* |
|
* drawComponent.surface.addGradient({ |
|
* id: 'gradientId', |
|
* angle: 45, |
|
* stops: { |
|
* 0: { |
|
* color: '#555' |
|
* }, |
|
* 100: { |
|
* color: '#ddd' |
|
* } |
|
* } |
|
* }); |
|
* |
|
* @param {Object} gradient A gradient config. |
|
* @method |
|
*/ |
|
addGradient: Ext.emptyFn, |
|
|
|
/** |
|
* Adds a Sprite to the surface. See {@link Ext.draw.Sprite} for the configuration object to be |
|
* passed into this method. |
|
* |
|
* For example: |
|
* |
|
* drawComponent.surface.add({ |
|
* type: 'circle', |
|
* fill: '#ffc', |
|
* radius: 100, |
|
* x: 100, |
|
* y: 100 |
|
* }); |
|
* |
|
* @param {Ext.draw.Sprite[]/Ext.draw.Sprite...} args One or more Sprite objects or configs. |
|
* @return {Ext.draw.Sprite[]/Ext.draw.Sprite} The sprites added. |
|
*/ |
|
add: function() { |
|
var args = Array.prototype.slice.call(arguments), |
|
sprite, |
|
hasMultipleArgs = args.length > 1, |
|
items, |
|
results, |
|
i, |
|
ln, |
|
item; |
|
|
|
if (hasMultipleArgs || Ext.isArray(args[0])) { |
|
items = hasMultipleArgs ? args : args[0]; |
|
results = []; |
|
|
|
for (i = 0, ln = items.length; i < ln; i++) { |
|
item = items[i]; |
|
item = this.add(item); |
|
results.push(item); |
|
} |
|
|
|
return results; |
|
} |
|
sprite = this.prepareItems(args[0], true)[0]; |
|
this.insertByZIndex(sprite); |
|
this.onAdd(sprite); |
|
return sprite; |
|
}, |
|
|
|
/** |
|
* @private |
|
* Inserts a given sprite into the correct position in the items collection, according to |
|
* its zIndex. It will be inserted at the end of an existing series of sprites with the same or |
|
* lower zIndex. By ensuring sprites are always ordered, this allows surface subclasses to render |
|
* the sprites in the correct order for proper z-index stacking. |
|
* @param {Ext.draw.Sprite} sprite |
|
* @return {Number} the sprite's new index in the list |
|
*/ |
|
insertByZIndex: function(sprite) { |
|
var me = this, |
|
sprites = me.items.items, |
|
len = sprites.length, |
|
ceil = Math.ceil, |
|
zIndex = sprite.attr.zIndex, |
|
idx = len, |
|
high = idx - 1, |
|
low = 0, |
|
otherZIndex; |
|
|
|
if (me.orderSpritesByZIndex && len && zIndex < sprites[high].attr.zIndex) { |
|
// Find the target index via a binary search for speed |
|
while (low <= high) { |
|
idx = ceil((low + high) / 2); |
|
otherZIndex = sprites[idx].attr.zIndex; |
|
if (otherZIndex > zIndex) { |
|
high = idx - 1; |
|
} |
|
else if (otherZIndex < zIndex) { |
|
low = idx + 1; |
|
} |
|
else { |
|
break; |
|
} |
|
} |
|
// Step forward to the end of a sequence of the same or lower z-index |
|
while (idx < len && sprites[idx].attr.zIndex <= zIndex) { |
|
idx++; |
|
} |
|
} |
|
|
|
me.items.insert(idx, sprite); |
|
return idx; |
|
}, |
|
|
|
onAdd: function(sprite) { |
|
var group = sprite.group, |
|
draggable = sprite.draggable, |
|
groups, ln, i; |
|
if (group) { |
|
groups = [].concat(group); |
|
ln = groups.length; |
|
for (i = 0; i < ln; i++) { |
|
group = groups[i]; |
|
this.getGroup(group).add(sprite); |
|
} |
|
delete sprite.group; |
|
} |
|
if (draggable) { |
|
sprite.initDraggable(); |
|
} |
|
}, |
|
|
|
/** |
|
* Removes a given sprite from the surface, optionally destroying the sprite in the process. |
|
* You can also call the sprite own `remove` method. |
|
* |
|
* For example: |
|
* |
|
* drawComponent.surface.remove(sprite); |
|
* //or... |
|
* sprite.remove(); |
|
* |
|
* @param {Ext.draw.Sprite} sprite |
|
* @param {Boolean} destroySprite |
|
*/ |
|
remove: function(sprite, destroySprite) { |
|
if (sprite) { |
|
this.items.remove(sprite); |
|
|
|
var groups = [].concat(this.groups.items), |
|
gLen = groups.length, |
|
g; |
|
|
|
for (g = 0; g < gLen; g++) { |
|
groups[g].remove(sprite); |
|
} |
|
|
|
sprite.onRemove(); |
|
if (destroySprite === true) { |
|
sprite.destroy(); |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* Removes all sprites from the surface, optionally destroying the sprites in the process. |
|
* |
|
* For example: |
|
* |
|
* drawComponent.surface.removeAll(); |
|
* |
|
* @param {Boolean} destroySprites Whether to destroy all sprites when removing them. |
|
*/ |
|
removeAll: function(destroySprites) { |
|
var items = this.items.items, |
|
ln = items.length, |
|
i; |
|
for (i = ln - 1; i > -1; i--) { |
|
this.remove(items[i], destroySprites); |
|
} |
|
}, |
|
|
|
onRemove: Ext.emptyFn, |
|
|
|
onDestroy: Ext.emptyFn, |
|
|
|
/** |
|
* @private Using the current viewBox property and the surface's width and height, calculate the |
|
* appropriate viewBoxShift that will be applied as a persistent transform to all sprites. |
|
*/ |
|
applyViewBox: function() { |
|
var me = this, |
|
viewBox = me.viewBox, |
|
width = me.width || 1, // Avoid problems in division |
|
height = me.height || 1, |
|
viewBoxX, viewBoxY, viewBoxWidth, viewBoxHeight, |
|
relativeHeight, relativeWidth, size; |
|
|
|
if (viewBox && (width || height)) { |
|
viewBoxX = viewBox.x; |
|
viewBoxY = viewBox.y; |
|
viewBoxWidth = viewBox.width; |
|
viewBoxHeight = viewBox.height; |
|
relativeHeight = height / viewBoxHeight; |
|
relativeWidth = width / viewBoxWidth; |
|
size = Math.min(relativeWidth, relativeHeight); |
|
|
|
if (viewBoxWidth * size < width) { |
|
viewBoxX -= (width - viewBoxWidth * size) / 2 / size; |
|
} |
|
if (viewBoxHeight * size < height) { |
|
viewBoxY -= (height - viewBoxHeight * size) / 2 / size; |
|
} |
|
|
|
me.viewBoxShift = { |
|
dx: -viewBoxX, |
|
dy: -viewBoxY, |
|
scale: size |
|
}; |
|
|
|
if (me.background) { |
|
me.background.setAttributes(Ext.apply({}, { |
|
x: viewBoxX, |
|
y: viewBoxY, |
|
width: width / size, |
|
height: height / size |
|
}, { hidden: false }), true); |
|
} |
|
} else { |
|
if (me.background && width && height) { |
|
me.background.setAttributes(Ext.apply({x: 0, y: 0, width: width, height: height}, { hidden: false }), true); |
|
} |
|
} |
|
}, |
|
|
|
|
|
getBBox: function (sprite, isWithoutTransform) { |
|
var realPath = this["getPath" + sprite.type](sprite); |
|
if (isWithoutTransform) { |
|
sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath); |
|
return sprite.bbox.plain; |
|
} |
|
if (sprite.dirtyTransform) { |
|
this.applyTransformations(sprite, true); |
|
} |
|
sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix)); |
|
return sprite.bbox.transform; |
|
}, |
|
|
|
transformToViewBox: function (x, y) { |
|
if (this.viewBoxShift) { |
|
var me = this, shift = me.viewBoxShift; |
|
return [x / shift.scale - shift.dx, y / shift.scale - shift.dy]; |
|
} else { |
|
return [x, y]; |
|
} |
|
}, |
|
|
|
// @private |
|
applyTransformations: function(sprite, onlyMatrix) { |
|
if (sprite.type == 'text') { |
|
// TODO: getTextBBox function always take matrix into account no matter whether `isWithoutTransform` is true. Fix that. |
|
sprite.bbox.transform = 0; |
|
this.transform(sprite, false); |
|
} |
|
|
|
|
|
sprite.dirtyTransform = false; |
|
|
|
var me = this, |
|
attr = sprite.attr; |
|
|
|
if (attr.translation.x != null || attr.translation.y != null) { |
|
me.translate(sprite); |
|
} |
|
if (attr.scaling.x != null || attr.scaling.y != null) { |
|
me.scale(sprite); |
|
} |
|
if (attr.rotation.degrees != null) { |
|
me.rotate(sprite); |
|
} |
|
|
|
sprite.bbox.transform = 0; |
|
this.transform(sprite, onlyMatrix); |
|
sprite.transformations = []; |
|
}, |
|
|
|
// @private |
|
rotate: function (sprite) { |
|
var bbox, |
|
deg = sprite.attr.rotation.degrees, |
|
centerX = sprite.attr.rotation.x, |
|
centerY = sprite.attr.rotation.y; |
|
if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) { |
|
bbox = this.getBBox(sprite, true); |
|
centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX; |
|
centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY; |
|
} |
|
sprite.transformations.push({ |
|
type: "rotate", |
|
degrees: deg, |
|
x: centerX, |
|
y: centerY |
|
}); |
|
}, |
|
|
|
// @private |
|
translate: function(sprite) { |
|
var x = sprite.attr.translation.x || 0, |
|
y = sprite.attr.translation.y || 0; |
|
sprite.transformations.push({ |
|
type: "translate", |
|
x: x, |
|
y: y |
|
}); |
|
}, |
|
|
|
// @private |
|
scale: function(sprite) { |
|
var bbox, |
|
x = sprite.attr.scaling.x || 1, |
|
y = sprite.attr.scaling.y || 1, |
|
centerX = sprite.attr.scaling.centerX, |
|
centerY = sprite.attr.scaling.centerY; |
|
|
|
if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) { |
|
bbox = this.getBBox(sprite, true); |
|
centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX; |
|
centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY; |
|
} |
|
sprite.transformations.push({ |
|
type: "scale", |
|
x: x, |
|
y: y, |
|
centerX: centerX, |
|
centerY: centerY |
|
}); |
|
}, |
|
|
|
// @private |
|
rectPath: function (x, y, w, h, r) { |
|
if (r) { |
|
return [["M", x + r, y], ["l", w - r * 2, 0], ["a", r, r, 0, 0, 1, r, r], ["l", 0, h - r * 2], ["a", r, r, 0, 0, 1, -r, r], ["l", r * 2 - w, 0], ["a", r, r, 0, 0, 1, -r, -r], ["l", 0, r * 2 - h], ["a", r, r, 0, 0, 1, r, -r], ["z"]]; |
|
} |
|
return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]]; |
|
}, |
|
|
|
// @private |
|
ellipsePath: function (x, y, rx, ry) { |
|
if (ry == null) { |
|
ry = rx; |
|
} |
|
return [["M", x, y], ["m", 0, -ry], ["a", rx, ry, 0, 1, 1, 0, 2 * ry], ["a", rx, ry, 0, 1, 1, 0, -2 * ry], ["z"]]; |
|
}, |
|
|
|
// @private |
|
getPathpath: function (el) { |
|
return el.attr.path; |
|
}, |
|
|
|
// @private |
|
getPathcircle: function (el) { |
|
var a = el.attr; |
|
return this.ellipsePath(a.x, a.y, a.radius, a.radius); |
|
}, |
|
|
|
// @private |
|
getPathellipse: function (el) { |
|
var a = el.attr; |
|
return this.ellipsePath(a.x, a.y, |
|
a.radiusX || (a.width / 2) || 0, |
|
a.radiusY || (a.height / 2) || 0); |
|
}, |
|
|
|
// @private |
|
getPathrect: function (el) { |
|
var a = el.attr; |
|
return this.rectPath(a.x || 0, a.y || 0, a.width || 0, a.height || 0, a.r || 0); |
|
}, |
|
|
|
// @private |
|
getPathimage: function (el) { |
|
var a = el.attr; |
|
return this.rectPath(a.x || 0, a.y || 0, a.width, a.height); |
|
}, |
|
|
|
// @private |
|
getPathtext: function (el) { |
|
var bbox = this.getBBoxText(el); |
|
return this.rectPath(bbox.x, bbox.y, bbox.width, bbox.height); |
|
}, |
|
|
|
createGroup: function(id) { |
|
var group = this.groups.get(id); |
|
if (!group) { |
|
group = new Ext.draw.CompositeSprite({ |
|
surface: this |
|
}); |
|
group.id = id || Ext.id(null, 'ext-surface-group-'); |
|
this.groups.add(group); |
|
} |
|
return group; |
|
}, |
|
|
|
/** |
|
* Returns a new group or an existent group associated with the current surface. |
|
* The group returned is a {@link Ext.draw.CompositeSprite} group. |
|
* |
|
* For example: |
|
* |
|
* var spriteGroup = drawComponent.surface.getGroup('someGroupId'); |
|
* |
|
* @param {String} id The unique identifier of the group. |
|
* @return {Object} The {@link Ext.draw.CompositeSprite}. |
|
*/ |
|
getGroup: function(id) { |
|
var group; |
|
if (typeof id == "string") { |
|
group = this.groups.get(id); |
|
if (!group) { |
|
group = this.createGroup(id); |
|
} |
|
} else { |
|
group = id; |
|
} |
|
return group; |
|
}, |
|
|
|
// @private |
|
prepareItems: function(items, applyDefaults) { |
|
items = [].concat(items); |
|
// Make sure defaults are applied and item is initialized |
|
var item, i, ln; |
|
for (i = 0, ln = items.length; i < ln; i++) { |
|
item = items[i]; |
|
if (!(item instanceof Ext.draw.Sprite)) { |
|
// Temporary, just take in configs... |
|
item.surface = this; |
|
items[i] = this.createItem(item); |
|
} else { |
|
item.surface = this; |
|
} |
|
} |
|
return items; |
|
}, |
|
|
|
/** |
|
* Changes the text in the sprite element. The sprite must be a `text` sprite. |
|
* This method can also be called from {@link Ext.draw.Sprite}. |
|
* |
|
* For example: |
|
* |
|
* var spriteGroup = drawComponent.surface.setText(sprite, 'my new text'); |
|
* |
|
* @param {Object} sprite The Sprite to change the text. |
|
* @param {String} text The new text to be set. |
|
* @method |
|
*/ |
|
setText: Ext.emptyFn, |
|
|
|
// @private Creates an item and appends it to the surface. Called |
|
// as an internal method when calling `add`. |
|
createItem: Ext.emptyFn, |
|
|
|
/** |
|
* Retrieves the id of this component. |
|
* Will autogenerate an id if one has not already been set. |
|
*/ |
|
getId: function() { |
|
return this.id || (this.id = Ext.id(null, 'ext-surface-')); |
|
}, |
|
|
|
/** |
|
* Destroys the surface. This is done by removing all components from it and |
|
* also removing its reference to a DOM element. |
|
* |
|
* For example: |
|
* |
|
* drawComponent.surface.destroy(); |
|
*/ |
|
destroy: function() { |
|
var me = this; |
|
delete me.domRef; |
|
if (me.background) { |
|
me.background.destroy(); |
|
} |
|
me.removeAll(true); |
|
Ext.destroy(me.groups.items); |
|
} |
|
});
|
|
|