[gnome-shell/wip/background-rework: 5/13] background: add slide show support



commit 94a325a1310068695d99eb31cd989fa9010e27bc
Author: Ray Strode <rstrode redhat com>
Date:   Mon Feb 4 16:50:36 2013 -0500

    background: add slide show support
    
    gnome-desktop's background drawing code supports an
    XML format for presenting backgrounds based on time of day,
    monitor geometry, etc. Now that we don't use gnome-desktop for drawing the
    background, we need to implement that support ourselves to maintain
    feature parity.
    
    This commit implements that.

 js/ui/background.js |  275 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 274 insertions(+), 1 deletions(-)
---
diff --git a/js/ui/background.js b/js/ui/background.js
index b658d1f..b4933bc 100644
--- a/js/ui/background.js
+++ b/js/ui/background.js
@@ -27,6 +27,8 @@ const BACKGROUND_STYLE_KEY = 'picture-options';
 const PICTURE_OPACITY_KEY = 'picture-opacity';
 const PICTURE_URI_KEY = 'picture-uri';
 
+const SLIDE_OPACITY_STEP_INCREMENT = 4.0;
+
 let _backgroundCache = null;
 
 const BackgroundCache = new Lang.Class({
@@ -153,6 +155,25 @@ const BackgroundCache = new Lang.Class({
         }
 
         return content;
+    },
+
+    getSlideShow: function(filename) {
+        if (this._slideShowFilename == filename) {
+            return this._slideShow;
+        }
+
+        let slideShow = new SlideShow({ layoutManager: this._layoutManager,
+                                        filename: filename });
+
+        if (slideShow.load()) {
+            this._monitorFile(filename);
+            this._slideShowFilename = filename;
+            this._slideShow = slideShow;
+        } else {
+            slideShow = null;
+        }
+
+        return slideShow;
     }
 });
 Signals.addSignalMethods(BackgroundCache.prototype);
@@ -249,6 +270,70 @@ const Background = new Lang.Class({
         return false;
     },
 
+    _updateSlideContent: function(style) {
+        this._slideShow.update();
+
+        let slideFiles = this._slideShow.getSlideFiles(this._layoutManager.monitors[this._monitorIndex]);
+
+        if (!slideFiles)
+            return;
+
+        let fromContent = this._cache.getImageContent({ monitorIndex: this._monitorIndex,
+                                                        effects: this._effects,
+                                                        style: style,
+                                                        filename: slideFiles[0] });
+
+        if (fromContent) {
+            if (!this._fromImage) {
+                this._fromImage = new Meta.BackgroundActor(global.screen, this._monitorIndex);
+                this.actor.add_child(this._fromImage);
+            }
+
+            if (this._fromImage.content != fromContent)
+                this._fromImage.content = fromContent;
+        }
+
+        if (slideFiles.length > 1) {
+            let toContent = this._cache.getImageContent({ monitorIndex: this._monitorIndex,
+                                                          effects: this._effects,
+                                                          style: style,
+                                                          filename: slideFiles[1] });
+
+            if (toContent) {
+                if (!this._toImage) {
+                    this._toImage = new Meta.BackgroundActor(global.screen, this._monitorIndex);
+                    this.actor.add_child(this._toImage);
+                }
+
+                this._toImage.opacity = this._slideShow.slideProgress * 255;
+
+                if (this._toImage.content != toContent)
+                    this._toImage.content = toContent;
+            } else {
+                this._toImage = null;
+            }
+        }
+    },
+
+    _loadSlides: function(filename, style) {
+        this._slideShow = this._cache.getSlideShow(filename);
+
+        if (!this._slideShow)
+            return false;
+
+        this._updateSlideContent(style);
+        this._watchCacheFile(filename);
+
+        let interval = Math.max(250, SLIDE_OPACITY_STEP_INCREMENT / this._slideShow.duration);
+        this._slideUpdateTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
+                                                      interval,
+                                                      Lang.bind(this, function() {
+                                                                    this._updateSlideContent(style);
+                                                                    return true;
+                                                                }));
+        return true;
+    },
+
     _load: function () {
         this._cache = getBackgroundCache(this._layoutManager);
 
@@ -259,7 +344,9 @@ const Background = new Lang.Class({
 
         let style = this._settings.get_enum(BACKGROUND_STYLE_KEY);
         if (style != GDesktopEnums.BackgroundStyle.NONE) {
-            this._loadImage(filename, style);
+            if (!this._loadImage(filename, style)) {
+                this._loadSlides(filename, style);
+            }
         }
 
         if (!this._settings.get_boolean(DRAW_BACKGROUND_KEY)) {
@@ -312,3 +399,189 @@ const StillFrame = new Lang.Class({
     }
 });
 Signals.addSignalMethods(StillFrame.prototype);
+
+const SlideShow = new Lang.Class({
+    Name: 'SlideShow',
+
+    _init: function(params) {
+        params = Params.parse(params, { layoutManager: null,
+                                        filename: null });
+        this.filename = params.filename;
+        this._slides = [];
+        this.duration = 0.0;
+        this._layoutManager = params.layoutManager;
+        this.slideProgress = 0.0;
+    },
+
+    _loadFileElement: function(fileElement) {
+        if (!fileElement)
+            return [];
+
+        let files = [];
+        let elements = fileElement.children();
+        if (elements.length() == 1 && elements.children().length() == 0) {
+            files.push({ path: elements[0].toString() });
+        } else {
+            for (let i = 0; i < elements.length(); i++) {
+                if (elements[i].name() == 'size' &&
+                    elements[i]  width &&
+                    elements[i]  height &&
+                    elements[i].children().length() == 0) {
+                    files.push({ path: elements[i].toString(),
+                                 width: parseFloat(elements[i]  width),
+                                 height: parseFloat(elements[i]  height) });
+                }
+            }
+        }
+
+        return files;
+    },
+
+    load: function() {
+        let file = Gio.File.new_for_path(this.filename);
+
+        let [result, text] = file.load_contents(null);
+
+        if (!result)
+            return false;
+
+        let rootElement;
+        try {
+            rootElement = XML(text.toString());
+        } catch(e) {
+            return false;
+        }
+
+        if (rootElement.length() != 1 || rootElement.name() != 'background')
+            return false;
+
+        let elements = rootElement.children();
+        for (let i = 0; i < elements.length(); i++) {
+            if (elements[i].name() == 'starttime') {
+                let startDate = new Date(parseInt(elements[i].year),
+                                         parseInt(elements[i].month) - 1,
+                                         parseInt(elements[i].day),
+                                         parseInt(elements[i].hour),
+                                         parseInt(elements[i].minute),
+                                         parseInt(elements[i].second));
+
+                this._startTime = startDate.getTime();
+            } else if (elements[i].name() == 'static') {
+                let slide = {};
+                slide['start-time'] = this._startTime + this.duration;
+                slide['duration'] = parseFloat(elements[i].duration) * 1000.0;
+                this.duration += slide['duration'];
+                slide['start-files'] = this._loadFileElement(elements[i].file);
+
+                this._slides.push(slide);
+            } else if (elements[i].name() == 'transition' &&
+                       elements[i]  type == 'overlay') {
+                let slide = {};
+                slide['start-time'] = this._startTime + this.duration;
+                slide['duration'] = parseFloat(elements[i].duration) * 1000.0;
+                this.duration += slide['duration'];
+                slide['start-files'] = this._loadFileElement(elements[i].from);
+                slide['end-files'] = this._loadFileElement(elements[i].to);
+
+                this._slides.push(slide);
+            }
+        }
+
+        return true;
+    },
+
+    _findFileForSize: function(files, width, height) {
+        // Find the file entry that best matches the given size.
+        // Do two passes; the first pass only considers entries
+        // that are larger than the given size.
+        // We are looking for the image that best matches the aspect ratio.
+        // When two images have the same aspect ratio, prefer the one whose
+        // width is closer to the given width.
+        // (algorithm taken from gnome-desktop)
+        let aspectRatio = (1.0 * width) / height;
+
+        let bestAspectCloseness;
+        let bestFile = null;
+        for (let pass = 0; pass < 2; pass++) {
+            for (let i = 0; i < files.length; i++) {
+                let file = files[i];
+
+                if (pass == 0 && ((file['width'] < width) || (file['height'] < height)))
+                    continue;
+
+                let candidateAspectRatio;
+                if (!file['width'] || !file['height'])
+                    candidateAspectRatio = 1.0;
+                else
+                    candidateAspectRatio = file['width'] / file['height'];
+
+                let aspectCloseness = Math.abs(aspectRatio - candidateAspectRatio);
+
+                if ((bestAspectCloseness == undefined) ||
+                    (aspectCloseness < bestAspectCloseness)) {
+                    bestAspectCloseness = aspectCloseness;
+                    bestFile = file;
+
+                } else if (aspectCloseness == bestAspectCloseness) {
+                    if (Math.abs(file['width'] - width) < Math.abs(bestFile['width'] - width)) {
+                        bestFile = file;
+                    }
+                }
+
+                if (bestFile)
+                    break;
+            }
+        }
+
+        return bestFile;
+    },
+
+    update: function() {
+        this._slide = null;
+
+        // The slideshow loops indefinitely, so roll back the
+        // current time as many slideshow intervals as necessary
+        // to find the effective time offset of the passed in time.
+        let timeOffset = (Date.now() - this._startTime) % this.duration;
+
+        let effectiveTime = this._startTime + timeOffset;
+
+        for (let i = 0; i < this._slides.length; i++) {
+            let slide = this._slides[i];
+
+            let slideStartTime = slide['start-time'];
+            let duration = slide['duration'];
+
+            if ((effectiveTime > slideStartTime) && effectiveTime < (slideStartTime + duration)) {
+                this._slide = slide;
+                this.slideProgress = (effectiveTime - slideStartTime) / duration;
+            }
+        }
+    },
+
+    getSlideFiles: function(monitor) {
+        if (!this._slide)
+            return null;
+
+        let file = this._findFileForSize(this._slide['start-files'],
+                                         monitor.width,
+                                         monitor.height);
+        if (!file)
+            return null;
+
+        let files = [];
+        files.push(file['path']);
+
+        if (this._slide['end-files']) {
+            file = this._findFileForSize(this._slide['end-files'],
+                                         monitor.width,
+                                         monitor.height);
+            if (file)
+                files.push(file['path']);
+        }
+
+        return files;
+    },
+});
+Signals.addSignalMethods(SlideShow.prototype);
+


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]