Scaling (but not skewing) Atom image

classic Classic list List threaded Threaded
11 messages Options
Reply | Threaded
Open this post in threaded view
|

Scaling (but not skewing) Atom image

Reed
Hello wonderful community,

I am unable to effect what I want with an Atom. I have an image which will be of variable dimensions. I need it to display with the proper proportions always, but with a maximum width and height. If either the width or height are above the maximum, the image should scale down to fit, but maintain proportions. In vanilla HTML/CSS, I would simply set max-width and max-height, and neither explicit width nor height. See this codepen for an example of my desired effect: http://codepen.io/reedspool/pen/adwyvB

With an Atom image, however, I'm unable to make this happen. I've played with the properties scale, allowShrinkX, allowShrinkY, maxWidth, maxHeight, allowStretchX, and allowStretchY. None of my experiments has worked. See this playground link, http://goo.gl/pHcUcE 

As you can see, the best of I've gotten is to maintain proportions by setting scale to false, but the maxWidth doesn't work as I'd expect (from experience with vanilla HTML/CSS), and the image is cut off.

I've also attempted the image manipulation with a `qx.ui.embed.Html()` element as well, with little success. See this playground link, http://goo.gl/cZoYdC Here, the image doesn't show at all (I think because I'm not setting the container height properly.

Any suggestions appreciated, either to fix my current approaches or offer a new one.

Thanks,
Reed



------------------------------------------------------------------------------
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
[hidden email]
https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel
Reply | Threaded
Open this post in threaded view
|

Re: Scaling (but not skewing) Atom image

Dietrich Streifert
Hi Reed

qx.ui.basic.Atom is (I think) not intended to be used that way.

I'll use instead a qx.ui.basic.Label, set the rich property to true which allows you to use regular html as the label value:

http://tinyurl.com/jk897lh


Am 11.01.2016 um 21:35 schrieb Reed:
Hello wonderful community,

I am unable to effect what I want with an Atom. I have an image which will be of variable dimensions. I need it to display with the proper proportions always, but with a maximum width and height. If either the width or height are above the maximum, the image should scale down to fit, but maintain proportions. In vanilla HTML/CSS, I would simply set max-width and max-height, and neither explicit width nor height. See this codepen for an example of my desired effect: http://codepen.io/reedspool/pen/adwyvB

With an Atom image, however, I'm unable to make this happen. I've played with the properties scale, allowShrinkX, allowShrinkY, maxWidth, maxHeight, allowStretchX, and allowStretchY. None of my experiments has worked. See this playground link, http://goo.gl/pHcUcE 

As you can see, the best of I've gotten is to maintain proportions by setting scale to false, but the maxWidth doesn't work as I'd expect (from experience with vanilla HTML/CSS), and the image is cut off.

I've also attempted the image manipulation with a `qx.ui.embed.Html()` element as well, with little success. See this playground link, http://goo.gl/cZoYdC Here, the image doesn't show at all (I think because I'm not setting the container height properly.

Any suggestions appreciated, either to fix my current approaches or offer a new one.

Thanks,
Reed




------------------------------------------------------------------------------
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
[hidden email]
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
[hidden email]
https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel
Reply | Threaded
Open this post in threaded view
|

Re: Scaling (but not skewing) Atom image

Derrell Lipman
Reed, you probably have to click Run twice to view this since the image is not initially loaded and doesn't appear initially. Also, I think Dietrich intended max-height vs maxHeight and max-width vs maxWidth in the image.

Dietrich, thanks! Unfortunately, this doesn't do what Reed needs and I haven't come up with a good solution either. In your playground example, when the window is reduced in size in one dimension, the image does not shrink to fit so that the image is fully visible. Instead, part of the image is cut off. Reed's goal here is to display an entire image in its original aspect ratio, however small as necessary to be fully visible in the provided space. Additional suggestions appreciated!

Derrell


On Tue, Jan 12, 2016 at 3:17 AM Dietrich Streifert <[hidden email]> wrote:
Hi Reed

qx.ui.basic.Atom is (I think) not intended to be used that way.

I'll use instead a qx.ui.basic.Label, set the rich property to true which allows you to use regular html as the label value:

http://tinyurl.com/jk897lh



Am 11.01.2016 um 21:35 schrieb Reed:
Hello wonderful community,

I am unable to effect what I want with an Atom. I have an image which will be of variable dimensions. I need it to display with the proper proportions always, but with a maximum width and height. If either the width or height are above the maximum, the image should scale down to fit, but maintain proportions. In vanilla HTML/CSS, I would simply set max-width and max-height, and neither explicit width nor height. See this codepen for an example of my desired effect: http://codepen.io/reedspool/pen/adwyvB

With an Atom image, however, I'm unable to make this happen. I've played with the properties scale, allowShrinkX, allowShrinkY, maxWidth, maxHeight, allowStretchX, and allowStretchY. None of my experiments has worked. See this playground link, http://goo.gl/pHcUcE 

As you can see, the best of I've gotten is to maintain proportions by setting scale to false, but the maxWidth doesn't work as I'd expect (from experience with vanilla HTML/CSS), and the image is cut off.

I've also attempted the image manipulation with a `qx.ui.embed.Html()` element as well, with little success. See this playground link, http://goo.gl/cZoYdC Here, the image doesn't show at all (I think because I'm not setting the container height properly.

Any suggestions appreciated, either to fix my current approaches or offer a new one.

Thanks,
Reed




------------------------------------------------------------------------------
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
[hidden email]
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
[hidden email]
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
[hidden email]
https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel
Reply | Threaded
Open this post in threaded view
|

Re: Scaling (but not skewing) Atom image

John Spackman-3
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 ImageLoader to set an unmanaged image

     * 

     * @param el

     *          {Element} image DOM element

     * @param source

     *          {String} source path

     */

    __setUnmanagedImage : function(el, source) {

      var ImageLoader = qx.io.ImageLoader;


      // Apply source

      this.__setSource(el, source);


      // Compare with old sizes and relayout if necessary

      var width = ImageLoader.getWidth(source);

      var height = ImageLoader.getHeight(source);

      this.__updateContentHint(width, height);

    },


    /**

     * Use the ImageLoader to load an unmanaged image

     * 

     * @param el

     *          {Element} image DOM element

     * @param source

     *          {String} source path

     */

    __loadUnmanagedImage : function(el, source) {

      var ImageLoader = qx.io.ImageLoader;


      if (qx.core.Environment.get("qx.debug")) {

        // loading external images via HTTP/HTTPS is a common usecase, as is

        // using data URLs.

        var sourceLC = source.toLowerCase();

        var startsWith = qx.lang.String.startsWith;

        if (!startsWith(sourceLC, "http") && !startsWith(sourceLC, "data:image/")) {

          var self = this.self(arguments);


          if (!self.__warned) {

            self.__warned = {};

          }


          if (!self.__warned[source]) {

            this.debug("try to load an unmanaged relative image: " + source);

            self.__warned[source] = true;

          }

        }

      }


      // only try to load the image if it not already failed

      if (!ImageLoader.isFailed(source)) {

        ImageLoader.load(source, this.__loaderCallback, this);

      } else {

        if (el != null) {

          el.resetSource();

        }

      }

    },


    /**

     * Combines the decorator's image styles with our own image to make sure

     * gradient and backgroundImage decorators work on Images.

     * 

     * @param el

     *          {Element} image DOM element

     * @param source

     *          {String} source path

     */

    __setSource : function(el, source) {

      if (el.getNodeName() == "div") {


        var dec = qx.theme.manager.Decoration.getInstance().resolve(this.getDecorator());

        // if the decorator defines any CSS background-image

        if (dec) {

          var hasGradient = (dec.getStartColor() && dec.getEndColor());

          var hasBackground = dec.getBackgroundImage();

          if (hasGradient || hasBackground) {

            var repeat = this.getScale() ? "scale" : "no-repeat";


            // get the style attributes for the given source

            var attr = qx.bom.element.Decoration.getAttributes(source, repeat);

            // get the background image(s) defined by the decorator

            var decStyle = dec.getStyles(true);


            var combinedStyles = {

              "backgroundImage" : attr.style.backgroundImage,

              "backgroundPosition" : (attr.style.backgroundPosition || "0 0"),

              "backgroundRepeat" : (attr.style.backgroundRepeat || "no-repeat")

            };


            if (hasBackground) {

              combinedStyles["backgroundPosition"] += "," + decStyle["background-position"] || "0 0";

              combinedStyles["backgroundRepeat"] += ", " + dec.getBackgroundRepeat();

            }


            if (hasGradient) {

              combinedStyles["backgroundPosition"] += ", 0 0";

              combinedStyles["backgroundRepeat"] += ", no-repeat";

            }


            combinedStyles["backgroundImage"] += "," + decStyle["background-image"];


            // apply combined background images

            el.setStyles(combinedStyles);


            return;

          }

        } else {

          // force re-apply to remove old decorator styles

          el.setSource(null);

        }

      }


      el.setSource(source);

    },


    /**

     * Event handler fired after the preloader has finished loading the icon

     * 

     * @param source

     *          {String} Image source which was loaded

     * @param imageInfo

     *          {Map} Dimensions of the loaded image

     */

    __loaderCallback : function(source, imageInfo) {

      // Ignore the callback on already disposed images

      if (this.$$disposed === true) {

        return;

      }


      // Ignore when the source has already been modified

      if (source !== qx.util.AliasManager.getInstance().resolve(this.getSource())) {

        return;

      }


      // Output a warning if the image could not loaded and quit

      if (imageInfo.failed) {

        this.warn("Image could not be loaded: " + source);

        this.fireEvent("loadingFailed");

      } else if (imageInfo.aborted) {

        // ignore the rest because it is aborted

        return;

      } else {

        this.fireEvent("loaded");

      }


      // Update image (again)

      this._styleSource();

    },


    /**

     * Updates the content hint when the image size has been changed

     * 

     * @param width

     *          {Integer} width of the image

     * @param height

     *          {Integer} height of the image

     */

    __updateContentHint : function(width, height) {

      if (width === undefined || height == undefined) {

        width = this.__width;

        height = this.__height;

      }

      if (this._recalc(width, height))

        qx.ui.core.queue.Layout.add(this);

    },

    

    

    /**

     * Recalculates the size of the image, according to scaling parameters

     * @param maxWidth {Integer?} maximum width restriction

     * @param maxHeight {Integer?} minimum height restriction

     */

    _recalc: function(originalWidth, originalHeight) {

      var fixedWidth = this.getWidth();

      var fixedHeight = this.getHeight();

      var newHeight = originalHeight;

      var newWidth = originalWidth;

      var maxWidth = this.getMaxWidth();

      var maxHeight = this.getMaxHeight();

      var minWidth = this.getMinWidth();

      var minHeight = this.getMinHeight();

      

      if (!this.isAllowScaleUp()) {     

        if (maxHeight > originalHeight)

          maxHeight = originalHeight;

        if (maxWidth > originalWidth)

          maxWidth = originalWidth;

      }

      


      switch(this.getForceRatio()) {

      case 'height':

        if (fixedWidth)

          newHeight = fixedWidth * originalHeight / originalWidth;

        break;

        

      case 'width':

        if (fixedHeight)

          newWidth = fixedHeight * originalWidth / originalHeight;

        break;

      

      case 'auto':

        if (fixedWidth)

          newHeight = fixedWidth * originalHeight / originalWidth;

        if (fixedHeight)

          newWidth = fixedHeight * originalWidth / originalHeight;

        

        if (maxWidth && newWidth > maxWidth) {

          newWidth = maxWidth;

          newHeight = newWidth * originalHeight / originalWidth;

        }

        if (maxHeight && newHeight > maxHeight) {

          newHeight = maxHeight;

          newWidth = newHeight * originalWidth / originalHeight;

          if (maxWidth && newWidth > maxWidth) {

            newWidth = maxWidth;

            newHeight = newWidth * originalHeight / originalWidth;

          }

        }

        newHeight = newWidth * originalHeight / originalWidth;

        break;

      }

      

      var width = parseInt(newWidth);

      var height = parseInt(newHeight);

      if (width != this.__width || height != this.__height) {

        this.__width = width;

        this.__height = height;

        return true;

      }

      return false;

    }

  },


  /*

   * ****************************************************************************

   * DESTRUCTOR

   * ****************************************************************************

   */


  destruct : function() {

    delete this.__currentContentElement;

    this._disposeMap("__contentElements");

  }

});


var SMILEY_URL = "http://img1.123freevectors.com/wp-content/uploads/new/icon/075-smiley-face-vector-art-free-download-l.png";


var container = new qx.ui.container.Composite(new qx.ui.layout.VBox());

this.getRoot().add(container);


var image = new grasshopper.af.ui.image.Image(SMILEY_URL).set({

  maxWidth: 200

});



// Add the atom to the page

container.add(image);


------------------------------------------------------------------------------
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
[hidden email]
https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel
Reply | Threaded
Open this post in threaded view
|

Re: Scaling (but not skewing) Atom image

Derrell Lipman
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 <[hidden email]> 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
[hidden email]
https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel
Reply | Threaded
Open this post in threaded view
|

Re: Scaling (but not skewing) Atom image

John Spackman-3
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 <[hidden email]>
Reply-To: qooxdoo Development <[hidden email]>
Date: Tuesday, 12 January 2016 at 13:25
To: qooxdoo Development <[hidden email]>
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 <[hidden email]> 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 [hidden email] 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
[hidden email]
https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel
Reply | Threaded
Open this post in threaded view
|

Re: Scaling (but not skewing) Atom image

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

D


On Tue, Jan 12, 2016 at 11:46 AM John Spackman <[hidden email]> 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 <[hidden email]>
Reply-To: qooxdoo Development <[hidden email]>
Date: Tuesday, 12 January 2016 at 13:25
To: qooxdoo Development <[hidden email]>
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 <[hidden email]> 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 [hidden email] 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
[hidden email]
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
[hidden email]
https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel
Reply | Threaded
Open this post in threaded view
|

Re: Scaling (but not skewing) Atom image

Reed
Thanks everyone,

John, your code does precisely what I need! Look forward to seeing it proposed for core, if possible.

Thanks John, Dietrich, and Derrell,
Reed

On Tue, Jan 12, 2016 at 11:48 AM, Derrell Lipman <[hidden email]> wrote:
Thanks, John! This would be a nice contribution to core qooxdoo.

D


On Tue, Jan 12, 2016 at 11:46 AM John Spackman <[hidden email]> 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 <[hidden email]>
Reply-To: qooxdoo Development <[hidden email]>
Date: Tuesday, 12 January 2016 at 13:25
To: qooxdoo Development <[hidden email]>
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 <[hidden email]> 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 [hidden email] 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
[hidden email]
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
[hidden email]
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
[hidden email]
https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel
Reply | Threaded
Open this post in threaded view
|

Re: Scaling (but not skewing) Atom image

SQville
In reply to this post by Reed
I was able to match the functionality by changing the image to an svg file and scale = true - http://tinyurl.com/z8uy7ne

Not sure if you are able to go svg vs. png
Reply | Threaded
Open this post in threaded view
|

Re: Scaling (but not skewing) Atom image

Reed
This is really interesting. I expected the scale property to work exactly like this!

Unfortunately, the actual images we'll be displaying are not simple smileys but high-resolution raster images.

Thanks regardless,
Reed

On Tue, Jan 12, 2016 at 7:26 PM, SQville <[hidden email]> wrote:
I was able to match the functionality by changing the image to an svg file
and scale = true - http://tinyurl.com/z8uy7ne

Not sure if you are able to go svg vs. png



--
View this message in context: http://qooxdoo.678.n2.nabble.com/Scaling-but-not-skewing-Atom-image-tp7587939p7587947.html
Sent from the qooxdoo mailing list archive at Nabble.com.

------------------------------------------------------------------------------
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
[hidden email]
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
[hidden email]
https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel
Reply | Threaded
Open this post in threaded view
|

Re: Scaling (but not skewing) Atom image

Tom Saddul
This post has NOT been accepted by the mailing list yet.
In reply to this post by John Spackman-3
Thanks a lot for this code.  You are genius!