diff --git a/ui (gen4)/features/ui-chrome.js b/ui (gen4)/features/ui-chrome.js index 5f51eded..5ed51edb 100755 --- a/ui (gen4)/features/ui-chrome.js +++ b/ui (gen4)/features/ui-chrome.js @@ -310,7 +310,8 @@ module.CurrentImageIndicator = core.ImageGridFeatures.Feature({ // - before animation when scaling up // - after when scaling down // This is done to make the visuals consistent... - ['fitImage.pre fitRibbon.pre setScale.pre', + //['fitImage.pre fitRibbon.pre setScale.pre', + ['resizing.pre', function(w1){ var w0 = this.screenwidth w1 = w1 || 1 diff --git a/ui (gen4)/features/ui-single-image.js b/ui (gen4)/features/ui-single-image.js index 94f890dd..1dedf26a 100755 --- a/ui (gen4)/features/ui-single-image.js +++ b/ui (gen4)/features/ui-single-image.js @@ -206,8 +206,8 @@ var SingleImageActions = actions.Actions({ // Set scale 'units' for different viewes... // // NOTE: the units are actually properties used to get/set the values. - 'single-image-scale-unit': 'screenwidth', - 'ribbon-scale-unit': 'screenfit', + 'single-image-scale-unit': 'screenfit', + 'ribbon-scale-unit': 'screenwidth', // The threshold from which the image block starts to tend to // screen proportions... @@ -252,30 +252,7 @@ module.SingleImageView = core.ImageGridFeatures.Feature({ actions: SingleImageActions, handlers:[ - // XXX HACK: force browser to redraw off-screen images... - // ...it appears that chrome cheats by not resizing off-screen - // images properly after changing scale... - // XXX this is still not perfect... - // ...if needed do a .reload() / ctrl-r - // XXX try doing this a timeout after resize.... - ['focusImage', - function(){ - var img = this.ribbons.getImage() - var d = Math.max(img.attr('preview-width')*1, img.attr('preview-width')*1) - var D = this.ribbons.getVisibleImageSize('max') - - if(this.config['-single-image-redraw-on-focus'] - // NOTE: redraw only when close to original preview - // size -- this is where chrome cheats and - // shows images blurry... - // XXX this causes some images to jump a bit, aligning - // to nearest pixel might fix this... - && Math.abs(D-d)/D < 0.30 - && this.toggleSingleImage('?') == 'on'){ - this.scale = this.scale - } - }], - ['fitImage.post setScale.post', + ['resizing.post', function(){ // prevent this from doing anything while no viewer... if(!this.ribbons @@ -321,6 +298,7 @@ module.SingleImageView = core.ImageGridFeatures.Feature({ if(state != pre_state){ this.config['ribbon-scale'] = this[this.config['ribbon-scale-unit']] + this[this.config['single-image-scale-unit']] = this.config['single-image-scale'] || this[this.config['single-image-scale-unit']] @@ -346,6 +324,7 @@ module.SingleImageView = core.ImageGridFeatures.Feature({ if(state != pre_state){ this.config['single-image-scale'] = this[this.config['single-image-scale-unit']] + this[this.config['ribbon-scale-unit']] = this.config['ribbon-scale'] || this[this.config['ribbon-scale-unit']] @@ -353,6 +332,43 @@ module.SingleImageView = core.ImageGridFeatures.Feature({ } } }], + + // Force browser to redraw off-screen images... + // + // This appears that chrome cheats by not resizing off-screen + // images properly after changing scale... + // + // XXX this is still not perfect... + // ...if needed do a .reload() / ctrl-r + [[ + 'resizing.post', + 'toggleSingleImage.pre', + ], + function(){ + this.__did_resize = true + }], + [[ + 'focusImage', + 'toggleSingleImage', + ], + function(){ + var img = this.ribbons.getImage() + var d = Math.max(img.attr('preview-width')*1, img.attr('preview-width')*1) + var D = this.ribbons.getVisibleImageSize('max') + + if(this.config['-single-image-redraw-on-focus'] + && this.toggleSingleImage('?') == 'on' + && this.__did_resize + // only when close to original preview size + && Math.abs(D-d)/D < 0.30){ + + // this forces chrome to redraw off-screen images... + this.scale = this.scale + + // reset the resize flag... + delete this.__did_resize + } + }], ], }) diff --git a/ui (gen4)/features/ui.js b/ui (gen4)/features/ui.js index f3a22d7b..f4189588 100755 --- a/ui (gen4)/features/ui.js +++ b/ui (gen4)/features/ui.js @@ -175,41 +175,60 @@ module.ViewerActions = actions.Actions({ 'ribbon-focus-mode': 'visual', }, - // Images... - // XXX this seems like a hack... - // ...should this be here??? - get images(){ - return this.ribbons != null ? this.ribbons.images : null + // Ribbons... + // + // NOTE: this expects that ribbons will maintain .parent.images... + // NOTE: when getting rid of ribbons need to also remove the .parent + // reference... + get ribbons(){ + return this.__ribbons }, + set ribbons(ribbons){ + this.__ribbons = ribbons + ribbons.parent = this }, - // NOTE: if ribbons are null this will have no effect... - set images(value){ - if(this.ribbons != null){ - this.ribbons.images = value + + // Current image data... + // + // XXX experimental... + get image(){ + return this.images && this.images[this.current] }, + set image(val){ + if(this.images){ + this.images[this.current] = val } }, + // Scaling... + // + // Normal scale... get scale(){ - return this.ribbons != null ? this.ribbons.scale() : null - }, + return this.ribbons != null ? this.ribbons.scale() : null }, set scale(s){ - this.setScale(s) - }, - + this.setScale(s) }, + + // Screen width in image blocks... + // + // NOTE: this will change depending on image block sizing... + // NOTE: this not usable for image blocks of different sizes... get screenwidth(){ - return this.ribbons != null ? this.ribbons.getScreenWidthImages() : null - }, + return this.ribbons != null ? this.ribbons.getScreenWidthImages() : null }, set screenwidth(n){ - this.fitImage(n) - }, + this.fitImage(n, false) }, + // Screen height in image blocks... get screenheight(){ - return this.ribbons != null ? this.ribbons.getScreenHeightRibbons() : null - }, + return this.ribbons != null ? this.ribbons.getScreenHeightRibbons() : null }, set screenheight(n){ - this.fitRibbon(n, false) - }, + this.fitRibbon(n, false) }, - // this is the size in image radii on the narrow side of the screen... + // Screen size in image radii on the narrow side of the screen... + // + // E.g. + // + // min(screenwidth, screenheight) + // screenfit = -------------------------------------- + // min(image.width, image.height) + // get screenfit(){ if(!this.ribbons || !this.ribbons.viewer){ return null @@ -569,16 +588,65 @@ module.ViewerActions = actions.Actions({ this.nextImage(w) }], - // zooming... + + // Zooming/scaling root action... + // + // Protocol: + // - all root zoom/scale action bust be wrapped in the .resizing action + // + // This will enable clients to attach to a single in/out point. + // + // NOTE: not intended for direct use... + resizing: ['- Zoom/', function(){}], + + // Root zoom/sclae actions... // // XXX need to account for animations... setScale: ['- Zoom/', function(scale){ - this.ribbons && scale && this.ribbons.scale(scale) - - this.refresh() + this.resizing.chainCall(this, function(){ + this.ribbons && scale && this.ribbons.scale(scale) + this.refresh() + }) + }], + fitOrig: ['Zoom/Fit to original scale', + function(){ + this.resizing.chainCall(this, function(){ + this.ribbons.scale(1) + this.refresh() + }) + }], + // NOTE: if this gets a count argument it will fit count images, + // default is one. + // NOTE: this will add .config['fit-overflow'] to odd counts if no + // overflow if passed. + // ...this is done to add ability to control scroll indication. + fitImage: ['Zoom/Fit image', + function(count, overflow){ + this.resizing.chainCall(this, function(){ + if(count != null){ + overflow = overflow == false ? 0 : overflow + var o = overflow != null ? overflow + : count % 2 != 1 ? 0 + : (this.config['fit-overflow'] || 0) + count += o + } + this.ribbons.fitImage(count) + this.refresh() + }) + }], + // NOTE: this does not accout for ribbon spacing... + fitRibbon: ['Zoom/Fit ribbon vertically', + function(count, whole){ + this.resizing.chainCall(this, function(){ + this.ribbons.fitRibbon(count, whole) + this.refresh() + }) }], + + // Zooming... + // // Zooming is done by multiplying the current scale by config['zoom-step'] // and rounding to nearest discrete number of images to fit on screen. zoomIn: ['Zoom/Zoom in', @@ -609,51 +677,19 @@ module.ViewerActions = actions.Actions({ this.scale /= (this.config['zoom-step'] || 1.2) } }], - fitOrig: ['Zoom/Fit to original scale', - function(){ - this.ribbons.scale(1) - this.refresh() - }], - // NOTE: if this gets a count argument it will fit count images, - // default is one. - // NOTE: this will add .config['fit-overflow'] to odd counts if no - // overflow if passed. - // ...this is done to add ability to control scroll indication. - // - // XXX make these neutral to screen and image proportions... - fitImage: ['Zoom/Fit image', - function(count, overflow){ - if(count != null){ - overflow = overflow == false ? 0 : overflow - var o = overflow != null ? overflow - : count % 2 != 1 ? 0 - : (this.config['fit-overflow'] || 0) - count += o - } - this.ribbons.fitImage(count) - this.refresh() - }], + // Scale presets... + // fitMax: ['Zoom/Fit the maximum number of images', function(){ this.fitImage(this.config['max-screen-images']) }], - - // XXX make this viewer/image proportion independent.... fitSmall: ['Zoom/Show small image', function(){ this.screenfit = 4 }], - // XXX make this viewer/image proportion independent.... fitNormal: ['Zoom/Show normal image', function(){ this.screenfit = 1.2 }], fitScreen: ['Zoom/Fit image to screen', function(){ this.screenfit = 1 }], - fitRibbon: ['Zoom/Fit ribbon vertically', - function(count, whole){ - this.ribbons.fitRibbon(count, whole) - this.refresh() - }], - - // NOTE: these work by getting the target position from .data... shiftImageTo: [ function(target){ return updateImagePosition(this, target) }], @@ -1456,7 +1492,7 @@ module.PartialRibbons = core.ImageGridFeatures.Feature({ this.updateRibbon('current', this.screenwidth / s || 1) //this.preCacheJumpTargets() }], - ['fitImage.pre', + ['resizing.pre', function(n){ this.updateRibbon('current', n || 1) //this.preCacheJumpTargets() @@ -2088,7 +2124,7 @@ module.AutoSingleImage = core.ImageGridFeatures.Feature({ }, handlers: [ - ['fitImage.pre', + ['resizing.pre', function(count){ count = count || 1 diff --git a/ui (gen4)/lib/actions.js b/ui (gen4)/lib/actions.js index b9e763a6..6e3a4a30 100755 --- a/ui (gen4)/lib/actions.js +++ b/ui (gen4)/lib/actions.js @@ -270,70 +270,14 @@ function Action(name, doc, ldoc, func){ // create the actual instance we will be returning... var meth = function(){ - var that = this - var args = args2array(arguments) - var res = this - - var getHandlers = this.getHandlers || MetaActions.getHandlers - var isToggler = this.isToggler || MetaActions.isToggler - - // get the handler list... - var handlers = getHandlers.call(this, name) - - // special case: toggler -- do not handle special args... - if(isToggler.call(this, name) - && args.length == 1 - && (args[0] == '?' || args[0] == '??')){ - return handlers.slice(-1)[0].pre.apply(this, args) - } - - // handlers: pre phase... - handlers - // NOTE: current action will get included and called by the code - // above and below, so no need to explicitly call func... - // NOTE: pre handlers are called FIFO, i.e. the last defined first... - .map(function(a){ - if(a.pre){ - res = a.pre.apply(that, args) - - // if a handler returns a function or a deferred, - // register is as a post handler... - if(res - && res !== that - && (res instanceof Function - || res.resolve instanceof Function)){ - a.post = res - - // reset the result... - res = that - } - } - return a - }) - - // return this if nothing specific is returned... - res = res === undefined ? this : res - // the post handlers get the result as the first argument... - args.splice(0, 0, res) - - // handlers: post phase... - handlers - // NOTE: post handlers are called LIFO -- last defined last... - .reverse() - .forEach(function(a){ - a.post - && (a.post.resolve ? - a.post.resolve.apply(a.post, args) - : a.post.apply(that, args)) - }) - - // action result... - return res - } + return meth.chainApply(this, null, arguments) } meth.__proto__ = this.__proto__ // populate the action attributes... - meth.name = name + //meth.name = name + Object.defineProperty(meth, 'name', { + value: name, + }) meth.doc = doc meth.long_doc = ldoc @@ -344,6 +288,84 @@ function Action(name, doc, ldoc, func){ // this will make action instances behave like real functions... Action.prototype.__proto__ = Function +// XXX revise the structure.... +// ...is it a better idea to define these in an object and assign that??? +Action.prototype.chainApply = function(context, inner, args){ + args = [].slice.call(args || []) + var res = context + var outer = this.name + + var getHandlers = context.getHandlers || MetaActions.getHandlers + var isToggler = context.isToggler || MetaActions.isToggler + + // get the handler list... + var handlers = getHandlers.call(context, outer) + + // special case: toggler -- do not handle special args... + if(isToggler.call(context, outer) + && args.length == 1 + && (args[0] == '?' || args[0] == '??')){ + return handlers.slice(-1)[0].pre.apply(context, args) + } + + // handlers: pre phase... + handlers + // NOTE: current action will get included and called by the code + // above and below, so no need to explicitly call func... + // NOTE: pre handlers are called FIFO, i.e. the last defined first... + .map(function(a){ + if(a.pre){ + res = a.pre.apply(context, args) + + // if a handler returns a function or a deferred, + // register is as a post handler... + if(res + && res !== context + && (res instanceof Function + || res.resolve instanceof Function)){ + a.post = res + + // reset the result... + res = context + } + } + return a + }) + + // call the inner action/function if preset.... + if(inner){ + //res = inner instanceof Function ? + inner instanceof Function ? + inner.call(context, args) + : inner instanceof Array && inner.length > 0 ? + context[inner.pop()].chainCall(context, inner, args) + : typeof(inner) == typeof('str') ? + context[inner].chainCall(context, null, args) + : null + } + + // return this if nothing specific is returned... + res = res === undefined ? context : res + // the post handlers get the result as the first argument... + args.splice(0, 0, res) + + // handlers: post phase... + handlers + // NOTE: post handlers are called LIFO -- last defined last... + .reverse() + .forEach(function(a){ + a.post + && (a.post.resolve ? + a.post.resolve.apply(a.post, args) + : a.post.apply(context, args)) + }) + + // action result... + return res +} +Action.prototype.chainCall = function(context, inner){ + return this.chainApply(context, inner, args2array(arguments).slice(2)) +} // A base action-set object... @@ -403,7 +425,7 @@ module.MetaActions = { // go up the proto chain... while(cur.__proto__ != null){ if(cur[n] != null && cur[n].doc != null){ - res[n] = [ cur[n].doc, cur[n].long_doc ] + res[n] = [ cur[n].doc, cur[n].long_doc, cur[n].name ] break } cur = cur.__proto__ @@ -516,7 +538,6 @@ module.MetaActions = { isToggler: doWithRootAction(function(action){ return action instanceof toggler.Toggler }), - // Register an action callback... // // Register a post action callback @@ -704,6 +725,36 @@ module.MetaActions = { return this }, + // Apply/call a function/action "inside" an action... + // + // .chainApply(outer, inner) + // .chainApply(outer, inner, arguments) + // -> result + // + // .chainCall(outer, inner) + // .chainCall(outer, inner, ..) + // -> result + // + // + // The inner action call is completely nested as base of the outer + // action. + // + // Outer action o-------x o-------x + // v ^ + // Inner action o---|---x + // + // The given arguments are passed as-is to both the outer and inner + // actions. + // The base inner action return value is passed to the outer action + // .post handlers. + // + // NOTE: these call the action's .chainApply(..) and .chainCall(..) + // methods, thus is not compatible with non-action methods... + // NOTE: .chianCall('action', ..) is equivalent to .action.chianCall(..) + chainApply: function(outer, inner, args){ + return this[outer].chainApply(this, inner, args) }, + chainCall: function(outer, inner){ + return this[outer].chainApply(this, inner, args2array(arguments).slice(2)) }, // Get mixin object in inheritance chain... // diff --git a/ui (gen4)/ribbons.js b/ui (gen4)/ribbons.js index 2acdc035..932de41a 100755 --- a/ui (gen4)/ribbons.js +++ b/ui (gen4)/ribbons.js @@ -300,6 +300,34 @@ var RibbonsPrototype = { setElemGID: RibbonsClassPrototype.setElemGID, + get parent(){ + return this.__parent + }, + // NOTE: this will reset locally referenced .images to .parent.images + set parent(parent){ + this.__parent = parent + + delete this.__images + }, + + // maintain images in .parent.images if available... + // + // NOTE: images can be stored locally if no parent is set but will + // get overridden as soon as .parent is set. + get images(){ + return this.parent ? this.parent.images : this.__images + }, + set images(images){ + if(this.parent){ + this.parent.images = images + delete this.__images + + } else { + this.__images = images + } + }, + + // Helpers... // Prevent CSS transitions...