Thanks, John! This would be a nice contribution to core qooxdoo.

D


On Tue, Jan 12, 2016 at 11:46 AM John Spackman <john-li...@zenesis.com>
wrote:

> No not really, I think it probably should go across just fine but OTOH I
> first wrote it several years ago (e.g. Qx v3.x or maybe even earlier) so
> there could be things that were added to Qx that mine doesn’t have.  I’ll
> have a go at comparing them side by side to give you a better answer!
>
> John.
>
> From: Derrell Lipman <derrell.lip...@unwireduniverse.com>
> Reply-To: qooxdoo Development <qooxdoo-devel@lists.sourceforge.net>
> Date: Tuesday, 12 January 2016 at 13:25
> To: qooxdoo Development <qooxdoo-devel@lists.sourceforge.net>
> Subject: Re: [qooxdoo-devel] Scaling (but not skewing) Atom image
>
> Thanks, John! Do you know if there anything about your Image class that
> would prevent it from directly replacing the native qooxdoo Image class?
> Any known non-backward-compatibilities? As I searched the archives, I found
> that there have been many questions about how to do this over the years...
>
> Derrell
>
>
> On Tue, Jan 12, 2016 at 8:09 AM John Spackman <john-li...@zenesis.com>
> wrote:
>
>> The only solution I have for this is to reimplement qx.core.basic.Image,
>> but with added tweaks for scaling.  I’ve created a playground demo but it
>> is too large for a shortened URL because out is basically my entire Image
>> class plus the demo code, so here it is in email:
>>
>>
>> /**
>>
>>  * Image which preserves the aspect ratio while scaling the image and
>> constrains
>>
>>  * the dimensions to stay within the min/max width/height. The image is
>> placed
>>
>>  * centrally within the dimensions of the widget.
>>
>>  *
>>
>>  * Based on the Qooxdoo image
>>
>>  */
>>
>> qx.Class.define("grasshopper.af.ui.image.Image", {
>>
>>   extend : qx.ui.core.Widget,
>>
>>
>>   /*
>>
>>    *
>> ****************************************************************************
>>
>>    * CONSTRUCTOR
>>
>>    *
>> ****************************************************************************
>>
>>    */
>>
>>
>>   /**
>>
>>    * @param source
>>
>>    *          {String?null} The URL of the image to display.
>>
>>    */
>>
>>   construct : function(source) {
>>
>>     this.__contentElements = {};
>>
>>
>>     this.base(arguments);
>>
>>
>>     if (source) {
>>
>>       this.setSource(source);
>>
>>     }
>>
>>   },
>>
>>
>>   /*
>>
>>    *
>> ****************************************************************************
>>
>>    * PROPERTIES
>>
>>    *
>> ****************************************************************************
>>
>>    */
>>
>>
>>   properties : {
>>
>>     /** The URL of the image */
>>
>>     source : {
>>
>>       check : "String",
>>
>>       init : null,
>>
>>       nullable : true,
>>
>>       event : "changeSource",
>>
>>       apply : "_applySource",
>>
>>       themeable : true
>>
>>     },
>>
>>
>>
>>     /**
>>
>>      * Whether the image should be scaled to the given dimensions
>>
>>      *
>>
>>      * This is disabled by default because it prevents the usage of image
>>
>>      * clipping when enabled.
>>
>>      */
>>
>>     scale : {
>>
>>       check : "Boolean",
>>
>>       init : true,
>>
>>       themeable : true,
>>
>>       apply : "_applyScale"
>>
>>     },
>>
>>
>>     /**
>>
>>      * Whether to preserve the image ratio (ie prevent distortion), and
>> which dimension
>>
>>      * to prioritise
>>
>>      */
>>
>>     forceRatio : {
>>
>>       init : 'auto',
>>
>>       check : [ 'disabled', 'height', 'width', 'auto' ],
>>
>>       apply : '_applyDimension'
>>
>>     },
>>
>>
>>     /**
>>
>>      * Whether to allow scaling the image up
>>
>>      */
>>
>>     allowScaleUp : {
>>
>>       init : false,
>>
>>       check : "Boolean",
>>
>>       apply : "_applyDimension"
>>
>>     },
>>
>>
>>     // overridden
>>
>>     appearance : {
>>
>>       refine : true,
>>
>>       init : "image"
>>
>>     },
>>
>>
>>     // overridden
>>
>>     allowShrinkX : {
>>
>>       refine : true,
>>
>>       init : false
>>
>>     },
>>
>>
>>     // overridden
>>
>>     allowShrinkY : {
>>
>>       refine : true,
>>
>>       init : false
>>
>>     },
>>
>>
>>     // overridden
>>
>>     allowGrowX : {
>>
>>       refine : true,
>>
>>       init : false
>>
>>     },
>>
>>
>>     // overridden
>>
>>     allowGrowY : {
>>
>>       refine : true,
>>
>>       init : false
>>
>>     }
>>
>>   },
>>
>>
>>   /*
>>
>>    *
>> ****************************************************************************
>>
>>    * EVENTS
>>
>>    *
>> ****************************************************************************
>>
>>    */
>>
>>
>>   events : {
>>
>>     /**
>>
>>      * Fired if the image source can not be loaded.
>>
>>      *
>>
>>      * *Attention*: This event is only used for images which are loaded
>>
>>      * externally (aka unmanaged images).
>>
>>      */
>>
>>     loadingFailed : "qx.event.type.Event",
>>
>>
>>     /**
>>
>>      * Fired if the image has been loaded.
>>
>>      *
>>
>>      * *Attention*: This event is only used for images which are loaded
>>
>>      * externally (aka unmanaged images).
>>
>>      */
>>
>>     loaded : "qx.event.type.Event"
>>
>>   },
>>
>>
>>   /*
>>
>>    *
>> ****************************************************************************
>>
>>    * MEMBERS
>>
>>    *
>> ****************************************************************************
>>
>>    */
>>
>>
>>   members : {
>>
>>     __width : null,
>>
>>     __height : null,
>>
>>     __mode : null,
>>
>>     __contentElements : null,
>>
>>     __currentContentElement : null,
>>
>>     __wrapper : null,
>>
>>
>>     // overridden
>>
>>     _onChangeTheme : function() {
>>
>>       this.base(arguments);
>>
>>       // restyle source (theme change might have changed the resolved url
>> )
>>
>>       this._styleSource();
>>
>>     },
>>
>>
>>
>>     /*
>>
>>      *
>> ---------------------------------------------------------------------------
>>
>>      * WIDGET API
>>
>>      *
>> ---------------------------------------------------------------------------
>>
>>      */
>>
>>
>>     // overridden
>>
>>     getContentElement : function() {
>>
>>       return this.__getSuitableContentElement();
>>
>>     },
>>
>>
>>     // overridden
>>
>>     _createContentElement : function() {
>>
>>       return this.__getSuitableContentElement();
>>
>>     },
>>
>>
>>     // overridden
>>
>>     _getContentHint : function() {
>>
>>       return {
>>
>>         width : this.__width || 0,
>>
>>         height : this.__height || 0
>>
>>       };
>>
>>     },
>>
>>
>>
>>     _applyDimension: function(value, oldValue) {
>>
>>       this.base(arguments, value, oldValue);
>>
>>       this.__updateContentHint();
>>
>>     },
>>
>>
>>     // overridden
>>
>>     _applyDecorator : function(value, old) {
>>
>>       this.base(arguments, value, old);
>>
>>
>>       var source = this.getSource();
>>
>>       source = qx.util.AliasManager.getInstance().resolve(source);
>>
>>       var el = this.getContentElement();
>>
>>       if (this.__wrapper) {
>>
>>         el = el.getChild(0);
>>
>>       }
>>
>>       this.__setSource(el, source);
>>
>>     },
>>
>>
>>     // overridden
>>
>>     _applyPadding : function(value, old, name) {
>>
>>       this.base(arguments, value, old, name);
>>
>>
>>       var element = this.getContentElement();
>>
>>       if (this.__wrapper) {
>>
>>         element.getChild(0).setStyles({
>>
>>           top : this.getPaddingTop() || 0,
>>
>>           left : this.getPaddingLeft() || 0
>>
>>         });
>>
>>       } else {
>>
>>         element.setPadding(this.getPaddingLeft() || 0, this.getPaddingTop()
>> || 0);
>>
>>       }
>>
>>
>>     },
>>
>>
>>     renderLayout : function(left, top, width, height) {
>>
>>       this.base(arguments, left, top, width, height);
>>
>>
>>       var element = this.getContentElement();
>>
>>       if (this.__wrapper) {
>>
>>         element.getChild(0).setStyles({
>>
>>           width : width - (this.getPaddingLeft() || 0) - 
>> (this.getPaddingRight()
>> || 0),
>>
>>           height : height - (this.getPaddingTop() || 0) - 
>> (this.getPaddingBottom()
>> || 0),
>>
>>           top : this.getPaddingTop() || 0,
>>
>>           left : this.getPaddingLeft() || 0
>>
>>         });
>>
>>       }
>>
>>     },
>>
>>
>>     /*
>>
>>      *
>> ---------------------------------------------------------------------------
>>
>>      * IMAGE API
>>
>>      *
>> ---------------------------------------------------------------------------
>>
>>      */
>>
>>
>>     // property apply, overridden
>>
>>     _applyEnabled : function(value, old) {
>>
>>       this.base(arguments, value, old);
>>
>>
>>       if (this.getSource()) {
>>
>>         this._styleSource();
>>
>>       }
>>
>>     },
>>
>>
>>     // property apply
>>
>>     _applySource : function(value) {
>>
>>       this._styleSource();
>>
>>     },
>>
>>
>>     // property apply
>>
>>     _applyScale : function(value) {
>>
>>       this._styleSource();
>>
>>     },
>>
>>
>>     /**
>>
>>      * Remembers the mode to keep track which contentElement is currently
>> in
>>
>>      * use.
>>
>>      *
>>
>>      * @param mode
>>
>>      *          {String} internal mode (alphaScaled|scaled|nonScaled)
>>
>>      */
>>
>>     __setMode : function(mode) {
>>
>>       this.__mode = mode;
>>
>>     },
>>
>>
>>     /**
>>
>>      * Returns the current mode if set. Otherwise checks the current
>> source and
>>
>>      * the current scaling to determine the current mode.
>>
>>      *
>>
>>      * @return {String} current internal mode
>>
>>      */
>>
>>     __getMode : function() {
>>
>>       if (this.__mode == null) {
>>
>>         var source = this.getSource();
>>
>>         var isPng = false;
>>
>>         if (source != null) {
>>
>>           isPng = qx.lang.String.endsWith(source, ".png");
>>
>>         }
>>
>>
>>         if (this.getScale() && isPng && qx.core.Environment.get(
>> "css.alphaimageloaderneeded")) {
>>
>>           this.__mode = "alphaScaled";
>>
>>         } else if (this.getScale()) {
>>
>>           this.__mode = "scaled";
>>
>>         } else {
>>
>>           this.__mode = "nonScaled";
>>
>>         }
>>
>>       }
>>
>>
>>       return this.__mode;
>>
>>     },
>>
>>
>>     /**
>>
>>      * Creates a contentElement suitable for the current mode
>>
>>      *
>>
>>      * @param mode
>>
>>      *          {String} internal mode
>>
>>      * @return {qx.html.Image} suitable image content element
>>
>>      */
>>
>>     __createSuitableContentElement : function(mode) {
>>
>>       var scale;
>>
>>       var tagName;
>>
>>       if (mode == "alphaScaled") {
>>
>>         scale = true;
>>
>>         tagName = "div";
>>
>>       } else if (mode == "nonScaled") {
>>
>>         scale = false;
>>
>>         tagName = "div";
>>
>>       } else {
>>
>>         scale = true;
>>
>>         tagName = "img";
>>
>>       }
>>
>>
>>       var element = new qx.html.Image(tagName);
>>
>>       element.setAttribute("$$widget", this.toHashCode());
>>
>>       element.setScale(scale);
>>
>>       element.setStyles({
>>
>>         "overflowX" : "hidden",
>>
>>         "overflowY" : "hidden",
>>
>>         "boxSizing" : "border-box"
>>
>>       });
>>
>>
>>       if (qx.core.Environment.get("css.alphaimageloaderneeded")) {
>>
>>         var wrapper = this.__wrapper = new qx.html.Element("div");
>>
>>         wrapper.setAttribute("$$widget", this.toHashCode());
>>
>>         wrapper.setStyle("position", "absolute");
>>
>>         wrapper.add(element);
>>
>>         return wrapper;
>>
>>       }
>>
>>
>>       return element;
>>
>>     },
>>
>>
>>     /**
>>
>>      * Returns a contentElement suitable for the current mode
>>
>>      *
>>
>>      * @return {qx.html.Image} suitable image contentElement
>>
>>      */
>>
>>     __getSuitableContentElement : function() {
>>
>>       if (this.$$disposed) {
>>
>>         return null;
>>
>>       }
>>
>>
>>       var mode = this.__getMode();
>>
>>
>>       if (this.__contentElements[mode] == null) {
>>
>>         this.__contentElements[mode] = this
>> .__createSuitableContentElement(mode);
>>
>>       }
>>
>>
>>       var element = this.__contentElements[mode];
>>
>>
>>       if (!this.__currentContentElement) {
>>
>>         this.__currentContentElement = element;
>>
>>       }
>>
>>
>>       return element;
>>
>>     },
>>
>>
>>     /**
>>
>>      * Applies the source to the clipped image instance or preload an
>> image to
>>
>>      * detect sizes and apply it afterwards.
>>
>>      *
>>
>>      */
>>
>>     _styleSource : function() {
>>
>>       var source = qx.util.AliasManager.getInstance().resolve(this
>> .getSource());
>>
>>
>>       var element = this.getContentElement();
>>
>>       if (this.__wrapper) {
>>
>>         element = element.getChild(0);
>>
>>       }
>>
>>
>>       if (!source) {
>>
>>         element.resetSource();
>>
>>         return;
>>
>>       }
>>
>>
>>       this.__checkForContentElementSwitch(source);
>>
>>
>>       if ((qx.core.Environment.get("engine.name") == "mshtml")
>>
>>           && (parseInt(qx.core.Environment.get("engine.version"), 10) <
>> 9 || qx.core.Environment.get("browser.documentmode") < 9)) {
>>
>>         var repeat = this.getScale() ? "scale" : "no-repeat";
>>
>>         element.tagNameHint =
>> qx.bom.element.Decoration.getTagName(repeat, source);
>>
>>       }
>>
>>
>>       var contentEl = this.__currentContentElement;
>>
>>       if (this.__wrapper) {
>>
>>         contentEl = contentEl.getChild(0);
>>
>>       }
>>
>>
>>       // Detect if the image registry knows this image
>>
>>       if (qx.util.ResourceManager.getInstance().has(source)) {
>>
>>         this.__setManagedImage(contentEl, source);
>>
>>       } else if (qx.io.ImageLoader.isLoaded(source)) {
>>
>>         this.__setUnmanagedImage(contentEl, source);
>>
>>       } else {
>>
>>         this.__loadUnmanagedImage(contentEl, source);
>>
>>       }
>>
>>     },
>>
>>
>>     /**
>>
>>      * Checks if the current content element is capable to display the
>> image
>>
>>      * with the current settings (scaling, alpha PNG)
>>
>>      *
>>
>>      * @param source
>>
>>      *          {String} source of the image
>>
>>      */
>>
>>     __checkForContentElementSwitch : qx.core.Environment.select("
>> engine.name", {
>>
>>       "mshtml" : function(source) {
>>
>>         var alphaImageLoader = qx.core.Environment.get(
>> "css.alphaimageloaderneeded");
>>
>>         var isPng = qx.lang.String.endsWith(source, ".png");
>>
>>
>>         if (alphaImageLoader && isPng) {
>>
>>           if (this.getScale() && this.__getMode() != "alphaScaled") {
>>
>>             this.__setMode("alphaScaled");
>>
>>           } else if (!this.getScale() && this.__getMode() != "nonScaled")
>> {
>>
>>             this.__setMode("nonScaled");
>>
>>           }
>>
>>         } else {
>>
>>           if (this.getScale() && this.__getMode() != "scaled") {
>>
>>             this.__setMode("scaled");
>>
>>           } else if (!this.getScale() && this.__getMode() != "nonScaled")
>> {
>>
>>             this.__setMode("nonScaled");
>>
>>           }
>>
>>         }
>>
>>
>>         this.__checkForContentElementReplacement(this
>> .__getSuitableContentElement());
>>
>>       },
>>
>>
>>       "default" : function(source) {
>>
>>         if (this.getScale() && this.__getMode() != "scaled") {
>>
>>           this.__setMode("scaled");
>>
>>         } else if (!this.getScale() && this.__getMode("nonScaled")) {
>>
>>           this.__setMode("nonScaled");
>>
>>         }
>>
>>
>>         this.__checkForContentElementReplacement(this
>> .__getSuitableContentElement());
>>
>>       }
>>
>>     }),
>>
>>
>>     /**
>>
>>      * Checks the current child and replaces it if necessary
>>
>>      *
>>
>>      * @param elementToAdd
>>
>>      *          {qx.html.Image} content element to add
>>
>>      */
>>
>>     __checkForContentElementReplacement : function(elementToAdd) {
>>
>>       var currentContentElement = this.__currentContentElement;
>>
>>
>>       if (currentContentElement != elementToAdd) {
>>
>>         if (currentContentElement != null) {
>>
>>           var pixel = "px";
>>
>>           var styles = {};
>>
>>
>>           // Copy dimension and location of the current content element
>>
>>           var bounds = this.getBounds();
>>
>>           if (bounds != null) {
>>
>>             styles.width = bounds.width + pixel;
>>
>>             styles.height = bounds.height + pixel;
>>
>>           }
>>
>>
>>           var insets = this.getInsets();
>>
>>           styles.left = parseInt(currentContentElement.getStyle("left")
>> || insets.left) + pixel;
>>
>>           styles.top = parseInt(currentContentElement.getStyle("top") ||
>> insets.top) + pixel;
>>
>>
>>           styles.zIndex = 10;
>>
>>
>>           var newEl = this.__wrapper ? elementToAdd.getChild(0) :
>> elementToAdd;
>>
>>           newEl.setStyles(styles, true);
>>
>>           newEl.setSelectable(this.getSelectable());
>>
>>
>>           var container = currentContentElement.getParent();
>>
>>
>>           if (container) {
>>
>>             var index =
>> container.getChildren().indexOf(currentContentElement);
>>
>>             container.removeAt(index);
>>
>>             container.addAt(elementToAdd, index);
>>
>>           }
>>
>>           // force re-application of source so __setSource is called
>> again
>>
>>           var hint = newEl.getNodeName();
>>
>>           newEl.setSource(null);
>>
>>           var currentEl = this.__wrapper ? 
>> this.__currentContentElement.getChild(0)
>> : this.__currentContentElement;
>>
>>           newEl.tagNameHint = hint;
>>
>>           newEl.setAttribute("class", currentEl.getAttribute("class"));
>>
>>
>>           // Flush elements to make sure the DOM elements are created.
>>
>>           qx.html.Element.flush();
>>
>>           var currentDomEl = currentEl.getDomElement();
>>
>>           var newDomEl = elementToAdd.getDomElement();
>>
>>           if (currentDomEl && newDomEl) {
>>
>>             // Switch the DOM elements' hash codes. This is required for
>> the
>>
>>             // event
>>
>>             // layer to work [BUG #7447]
>>
>>             var currentHash = currentDomEl.$$hash;
>>
>>             currentDomEl.$$hash = newDomEl.$$hash;
>>
>>             newDomEl.$$hash = currentHash;
>>
>>           }
>>
>>
>>           this.__currentContentElement = elementToAdd;
>>
>>         }
>>
>>       }
>>
>>     },
>>
>>
>>     /**
>>
>>      * Use the ResourceManager to set a managed image
>>
>>      *
>>
>>      * @param el
>>
>>      *          {Element} image DOM element
>>
>>      * @param source
>>
>>      *          {String} source path
>>
>>      */
>>
>>     __setManagedImage : function(el, source) {
>>
>>       var ResourceManager = qx.util.ResourceManager.getInstance();
>>
>>
>>       // Try to find a disabled image in registry
>>
>>       if (!this.getEnabled()) {
>>
>>         var disabled = source.replace(/\.([a-z]+)$/, "-disabled.$1");
>>
>>         if (ResourceManager.has(disabled)) {
>>
>>           source = disabled;
>>
>>           this.addState("replacement");
>>
>>         } else {
>>
>>           this.removeState("replacement");
>>
>>         }
>>
>>       }
>>
>>
>>       // Optimize case for enabled changes when no disabled image was
>> found
>>
>>       if (el.getSource() === source) {
>>
>>         return;
>>
>>       }
>>
>>
>>       // Apply source
>>
>>       this.__setSource(el, source);
>>
>>
>>       // Compare with old sizes and relayout if necessary
>>
>>       this.__updateContentHint(ResourceManager.getImageWidth(source),
>> ResourceManager.getImageHeight(source));
>>
>>     },
>>
>>
>>     /**
>>
>>      * Use the infos of the
>>
> ------------------------------------------------------------------------------
> Site24x7 APM Insight: Get Deep Visibility into Application Performance APM
> + Mobile APM + RUM: Monitor 3 App instances at just $35/Month Monitor
> end-to-end web transactions and take corrective actions now Troubleshoot
> faster and improve end-user experience. Signup Now!
> http://pubads.g.doubleclick.net/gampad/clk?id=267308311&iu=/4140_______________________________________________
> qooxdoo-devel mailing list qooxdoo-devel@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel
>
> ------------------------------------------------------------------------------
> Site24x7 APM Insight: Get Deep Visibility into Application Performance
> APM + Mobile APM + RUM: Monitor 3 App instances at just $35/Month
> Monitor end-to-end web transactions and take corrective actions now
> Troubleshoot faster and improve end-user experience. Signup Now!
> http://pubads.g.doubleclick.net/gampad/clk?id=267308311&iu=/4140
> _______________________________________________
> qooxdoo-devel mailing list
> qooxdoo-devel@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel
>
------------------------------------------------------------------------------
Site24x7 APM Insight: Get Deep Visibility into Application Performance
APM + Mobile APM + RUM: Monitor 3 App instances at just $35/Month
Monitor end-to-end web transactions and take corrective actions now
Troubleshoot faster and improve end-user experience. Signup Now!
http://pubads.g.doubleclick.net/gampad/clk?id=267308311&iu=/4140
_______________________________________________
qooxdoo-devel mailing list
qooxdoo-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel

Reply via email to