Window Scrollable

Tous mes développements

Window Scrollable

Comme tous mes plugins, ce script est publié sous licence CC BY 4.0.

Aujourd’hui un nouveau plugin de mon cru, que j’avais commencé il y a plus d’un an, que j’avais mis de côté, et sur lequel je me suis remis hier et que j’ai terminé aujourd’hui : des fenêtres scrollables.

Le principe, pouvoir afficher tout le contenu qu’on souhaite sans avoir à agrandir la taille de la fenêtre. Deux usages que j’entrevois, c’est pour afficher un texte assez long (trop pour qu’il tienne dans une fenêtre classique) ou encore une ou des images de grande taille (comme une carte du monde) sans avoir à soit la découper en morceaux, soit en réduire la taille la rendant plus difficile à lire. Mais je suis sûr que, vous autres amis et collègues scripteurs, aurez d’autres idées.

Le script

//=============================================================================
// Genie23-WindowScrollable.js
//=============================================================================

/*:
 * @plugindesc Fenêtre scrollable
 * @author Genie23 - Version : 1.0.0
 * 
 * @help
 * //==========================================================================
 * // Plugin : Genie23-WindowScrollable
 * // Date   : 20 septembre 2021
 * // Auteur : Genie23
 * //==========================================================================
 * Ce script permet de créer une fenêtre scrollable verticalement. Cette classe
 * remplace le bitmap affiché (contents) par un autre bitmap (viewport) qui
 * la même taille. L'intérêt est de ne pas afficher contents, mais un fragment
 * de contents de la taille de la fenêtre. Ainsi, même si le contenu dépasse la
 * taille de la fenêtre, tout ce qui dépasse sera masqué. En agissant sur le
 * scroll, le joueur pourra faire défiler le contenu.
 * Lorsque RMMV crée une fenêtre normale, il crée un bitmap de la taille de la
 * fenêtre (moins le padding) accessible par la propriété contents.
 * Window_Scrollable ne modifie pas ce comportement. Mais le bitmap contents 
 * est renommé viewport, et un nouveau bitmap contents est créé. Afin
 * d'afficher du contenu, il faut bien entendu continuer de se servir du bitmap
 * contents. A chaque modification du bitmap contents, le bitmap viewport sera
 * automatiquement mis à jour.
 * Usage :
 *     // Créer une nouvelle fenêtre scrollable
 *     var scrollable = new Window_Scrollable(x, y, width, height, autoResize);
 *         - x, y : position de la fenêtre
 *         - width, height : dimensions de la fenêtre
 *         - autoResize : option pour l'ajustement automatique du contenu
 *                        affiché dans la fenêtre scrollable
 *                        Valeurs possibles :
 *                            - false : pas de redimensionnement automatique
 *                                      du contenu
 *                            - true / 0 : redimensionnement automatique du
 *                                         contenu de la fenêtre, sans aucune
 *                                         marge de chaque côté
 *                            - un nombre : padding en pixel appliqué au
 *                                          contenu sur les 4 côtés (le bitmap
 *                                          contents de la fenêtre se
 *                                          redimensionnera tout seul pour
 *                                          appliquer ce padding des 4 côtés)
 *                            - un objet : padding appliqué pour chaque côté
 *                                         en fonction des clés renseignées.
 *                                         La valeur de chaque clé sera
 *                                         toujours un nombre représentant le
 *                                         padding à appliquer sur ce ou ces
 *                                         côtés en pixel.
 *                                         Clés possibles :
 *                                             - x : padding à droite et à
 *                                                   gauche
 *                                             - left : padding à gauche
 *                                             - right : padding à droite
 *                                             - y : padding en haut et en bas
 *                                             - top : padding en haut
 *                                             - bottom : padding en bas
 *                                         Si x est renseigné, left et right
 *                                         seront ignorés. De la même façon,
 *                                         si y est renseigné, top et bottom
 *                                         seront ignorés.
 * Les fonctions contentsWidth() et contentsHeight() retournent la largeur et
 * la hauteur, en tenant compte de la taille des scrollbars si l'autre
 * dimension dépasse la taille de la fenêtre.
 * Afin de forcer cette prise en compte pour pouvoir retailler le bitmap
 * contents sans avoir à s'y reprendre à deux fois ni voir apparaitre une
 * seconde scrollbar non voulue, ces deux fonctions prennent un booléen (true)
 * pour retourner la taille en déduisant la taille de la scrollbar.
 * Ainsi :
 *     scrollable.contents = new Bitmap(scrollable.contentsWidth(), 1000);
 * va créer un bitmap qui dépassera probablement la hauteur de la fenêtre, ce
 * qui entrainera l'apparition d'une scrollbar à droite. Mais au moment où
 * scrollable.contentsWidth() va être exécuté, le bitmap contents n'aura pas
 * encore été redimensionné. Aussi, scrollable.contentsWidth() va retourner la
 * largeur du contenu de la fenêtre sans tenir compte de la scrollbar
 * verticale, ce qui va aussi entrainer un dépassement de la taille horizontale
 * de la fenêtre et l'apparition d'une scrollbar horizontale non désirée.
 * Il est possible de faire :
 *     scrollable.contents = new Bitmap(scrollable.contentsWidth(true), 1000);
 * En ajoutant la valeur true comme paramètre, scrollable.contentsWidth(true)
 * va retourner la taille du contenu en tenant compte de la scrollbar. Et du
 * coup, plus de scrollbar horizontale.
 * 
 * Dépendances :
 *     - Genie23-SpriteButton (facultatif, correction du bug des Sprite_Button)
 */

//-----------------------------------------------------------------------------
// AutoResize_Bitmap
//
// Bitmap autodimensionné en fonction de son contenu
function AutoResize_Bitmap() {
    this.initialize.apply(this, arguments);
}

AutoResize_Bitmap.prototype = Object.create(Bitmap.prototype);
AutoResize_Bitmap.prototype.constructor = AutoResize_Bitmap;
AutoResize_Bitmap.prototype.initialize = function(autoResize) {
    this.internalPadding = { top: 0, bottom: 0, left: 0, right: 0 };
    switch(typeof autoResize) {
        case "number":
            this.internalPadding = { top: autoResize, bottom: autoResize, left: autoResize, right: autoResize };
            break;
        case "object":
            if(autoResize.x) {
                this.internalPadding.left = autoResize.x;
                this.internalPadding.right = autoResize.x;
            }
            else {
                if(autoResize.left) {
                    this.internalPadding.left = autoResize.left;
                }
                if(autoResize.right) {
                    this.internalPadding.right = autoResize.right;
                }
            }
            
            if(autoResize.y) {
                this.internalPadding.top = autoResize.y;
                this.internalPadding.bottom = autoResize.y;
            }
            else {
                if(autoResize.top) {
                    this.internalPadding.top = autoResize.top;
                }
                if(autoResize.bottom) {
                    this.internalPadding.bottom = autoResize.bottom;
                }
            }
            break;
    }
    Bitmap.prototype.initialize.call(this, this.internalPadding.left + this.internalPadding.right, this.internalPadding.top + this.internalPadding.bottom);
    this._actions = [];
};

AutoResize_Bitmap.prototype.blt = function(source, sx, sy, sw, sh, dx, dy, dw, dh) {
    dx += this.internalPadding.left;
    dy += this.internalPadding.top;
    dw = dw || sw;
    dh = dh || sh;
    newWidth = dx + dw + this.internalPadding.right;
    newHeight = dy + dh + this.internalPadding.bottom;
    if(this.width < newWidth || this.height < newHeight) {
        this.adapt(newWidth, newHeight);
    }
    Bitmap.prototype.blt.call(this, source, sx, sy, sw, sh, dx, dy, dw, dh);
    this._actions.push({ type: "blt", parameters: [ source, sx, sy, sw, sh, dx, dy, dw, dh ] });
};

AutoResize_Bitmap.prototype.bltImage = function(source, sx, sy, sw, sh, dx, dy, dw, dh) {
    dx += this.internalPadding.left;
    dy += this.internalPadding.top;
    dw = dw || sw;
    dh = dh || sh;
    newWidth = dx + dw + this.internalPadding.right;
    newHeight = dy + dh + this.internalPadding.bottom;
    if(this.width < newWidth || this.height < newHeight) {
        this.adapt(newWidth, newHeight);
    }
    Bitmap.prototype.bltImage.call(this, source, sx, sy, sw, sh, dx, dy, dw, dh);
    this._actions.push({ type: "bltImage", parameters: [ source, sx, sy, sw, sh, dx, dy, dw, dh ] });
};

AutoResize_Bitmap.prototype.clearRect = function(x, y, width, height) {
    x += this.internalPadding.left;
    y += this.internalPadding.top;
    newWidth = x + width + this.internalPadding.right;
    newHeight = y + height + this.internalPadding.bottom;
    if(this.width < newWidth || this.height < newHeight) {
        this.adapt(newWidth, newHeight);
    }
    Bitmap.prototype.clearRect.call(this, x, y, width, height);
    this._actions.push({ type: "clearRect", parameters: [ x, y, width, height ] });
};

AutoResize_Bitmap.prototype.clear = function() {
    Bitmap.prototype.clear.call(this);
    this._actions = [];
};

AutoResize_Bitmap.prototype.fillRect = function(x, y, width, height, color) {
    x += this.internalPadding.left;
    y += this.internalPadding.top;
    newWidth = x + width + this.internalPadding.right;
    newHeight = y + height + this.internalPadding.bottom;
    if(this.width < newWidth || this.height < newHeight) {
        this.adapt(newWidth, newHeight);
    }
    Bitmap.prototype.fillRect.call(this, x, y, width, height, color);
    this._actions.push({ type: "fillRect", parameters: [ x, y, width, height, color ] });
};

AutoResize_Bitmap.prototype.fillAll = function(color) {
    this.fillRect(this.internalPadding.left, this.internalPadding.top, this.width - this.internalPadding.left - this.internalPadding.right, this.height - this.internalPadding.top - this.internalPadding.bottom, color);
};

AutoResize_Bitmap.prototype.gradientFillRect = function(x, y, width, height, color1, color2, vertical) {
    x += this.internalPadding.left;
    y += this.internalPadding.top;
    newWidth = x + width + this.internalPadding.right;
    newHeight = y + height + this.internalPadding.bottom;
    if(this.width < newWidth || this.height < newHeight) {
        this.adapt(newWidth, newHeight);
    }
    Bitmap.prototype.gradientFillRect.call(this, x, y, width, height, color1, color2, vertical);
    this._actions.push({ type: "gradientFillRect", parameters: [ x, y, width, height, color1, color2, vertical ] });
};

AutoResize_Bitmap.prototype.drawCircle = function(x, y, radius, color) {
    x += this.internalPadding.left;
    y += this.internalPadding.top;
    newWidth = x + radius * 2 + this.internalPadding.right;
    newHeight = y + radius * 2 + this.internalPadding.bottom;
    if(this.width < newWidth || this.height < newHeight) {
        this.adapt(newWidth, newHeight);
    }
    Bitmap.prototype.drawCircle.call(this, x, y, radius, color);
    this._actions.push({ type: "drawCircle", parameters: [ x, y, radius, color ] });
};

AutoResize_Bitmap.prototype.drawText = function(text, x, y, maxWidth, lineHeight, align) {
    x += this.internalPadding.left;
    y += this.internalPadding.top;
    newWidth = x + Math.min(this.measureTextWidth(text), maxWidth) + this.internalPadding.right;
    newHeight = y + lineHeight + this.internalPadding.bottom;
    if(this.width < newWidth || this.height < newHeight) {
        this.adapt(newWidth, newHeight);
    }
    Bitmap.prototype.drawText.call(this, text, x, y, maxWidth, lineHeight, align);
    this._actions.push({ type: "drawText", parameters: [ text, x, y, maxWidth, lineHeight, align ] });
};

AutoResize_Bitmap.prototype.adjustTone = function(r, g, b) {
    Bitmap.prototype.adjustTone.call(this, r, g, b);
    this._actions.push({ type: "adjustTone", parameters: [ r, g, b ] });
};

AutoResize_Bitmap.prototype.rotateHue = function(offset) {
    Bitmap.prototype.rotateHue.call(this, offset);
    this._actions.push({ type: "rotateHue", parameters: [ offset ] });
};

AutoResize_Bitmap.prototype.blur = function() {
    Bitmap.prototype.blur.call(this);
    this._actions.push({ type: "blur" });
};

AutoResize_Bitmap.prototype.redraw = function() {
    Bitmap.prototype.clearRect.call(this, 0, 0, this.width, this.height);
    for(const action of this._actions) {
        switch(action.type) {
            case "blt":
                Bitmap.prototype.blt.apply(this, action.parameters);
                break;
            case "bltImage":
                Bitmap.prototype.bltImage.apply(this, action.parameters);
                break;
            case "clearRect":
                Bitmap.prototype.clearRect.apply(this, action.parameters);
                break;
            case "fillRect":
                Bitmap.prototype.fillRect.apply(this, action.parameters);
                break;
            case "fillAll":
                Bitmap.prototype.fillAll.apply(this, action.parameters);
                break;
            case "gradientFillRect":
                Bitmap.prototype.gradientFillRect.apply(this, action.parameters);
                break;
            case "drawCircle":
                Bitmap.prototype.drawCircle.apply(this, action.parameters);
                break;
            case "drawText":
                Bitmap.prototype.drawText.apply(this, action.parameters);
                break;
            case "adjustTone":
                Bitmap.prototype.adjustTone.apply(this, action.parameters);
                break;
            case "rotateHue":
                Bitmap.prototype.rotateHue.apply(this, action.parameters);
                break;
            case "blur":
                Bitmap.prototype.blur.apply(this);
                break;
        }
    }
};


AutoResize_Bitmap.prototype.adapt = function(newWidth, newHeight) {
    Bitmap.prototype.resize.call(this, newWidth, newHeight);
    this.redraw();
};

//-----------------------------------------------------------------------------
// Window_ScrollPosition
//
// Enum pour indiquer la position par rapport à la fenêtre du placement du scroll, utilisé dans Window_Scrollable.setScroll()
Window_ScrollPosition = {
    TOP_LEFT: 1,
    TOP_CENTER: 2,
    TOP_RIGHT: 3,
    MIDDLE_LEFT: 4,
    MIDDLE_CENTER: 5,
    MIDDLE_RIGHT: 6,
    BOTTOM_LEFT: 7,
    BOTTOM_CENTER: 8,
    BOTTOM_RIGHT: 9,
};

//-----------------------------------------------------------------------------
// Window_Scrollable
//
// Fenêtre scrollable
function Window_Scrollable() {
    this.initialize.apply(this, arguments);
}

Window_Scrollable.prototype = Object.create(Window_Base.prototype);
Window_Scrollable.prototype.constructor = Window_Scrollable;

Object.defineProperty(Window_Scrollable.prototype, "contents", {
    get: function() {
        return this._windowFullContentsSprite.bitmap;
    },
    set: function(value) {
        this._windowFullContentsSprite.bitmap = value;
        this.viewport = new Bitmap(this.contentsWidth(), this.contentsHeight());
    },
    configurable: true
});

Object.defineProperty(Window_Scrollable.prototype, "viewport", {
    get: function() {
        return this._windowContentsSprite.bitmap;
    },
    set: function(value) {
        this._windowContentsSprite.bitmap = value;
    },
    configurable: true
});

Window_Scrollable.prototype._createAllParts = function() {
    Window_Base.prototype._createAllParts.call(this);
    this._windowFullContentsSprite = new Sprite();
};

Window_Scrollable.prototype.createContents = function() {
    Window_Base.prototype.createContents.call(this);
    if(this.autoResize !== false) {
        this.contents = new AutoResize_Bitmap(this.autoResize);
    }
};

Window_Scrollable.prototype.initialize = function(x, y, width, height, autoResize = false) {
    this.autoResize = autoResize;
    
    Window_Base.prototype.initialize.call(this, x, y, width, height);
    
    // Données de la scrollbar
    this.scrollSpeed = 100;
    this.scrollX = 0;
    this.scrollY = 0;
    this.needRefreshViewport = true;
    
    // Scrollbar X
    this._scrollbarX = new Sprite_Base();
    this._scrollbarX.opacity = 0;
    this._scrollbarX.width = width - this.standardPadding() * 5;
    this._scrollbarX.height = this.standardPadding();
    this._scrollbarX.bitmap = new Bitmap(this._scrollbarX.width, this._scrollbarX.height);
    this._scrollbarX.bitmap.fillAll("white");
    this._scrollbarX.x = this.standardPadding() * 2;
    this._scrollbarX.y = height - this.standardPadding() * 2;
    
    // Scrollbar Y
    this._scrollbarY = new Sprite_Base();
    this._scrollbarY.opacity = 0;
    this._scrollbarY.width = this.standardPadding();
    this._scrollbarY.height = height - this.standardPadding() * 5;
    this._scrollbarY.bitmap = new Bitmap(this._scrollbarY.width, this._scrollbarY.height);
    this._scrollbarY.bitmap.fillAll("white");
    this._scrollbarY.x = width - this.standardPadding() * 2;
    this._scrollbarY.y = this.standardPadding() * 2;
    
    // Boutons de la scrollbar
    var buttonSet = ImageManager.loadSystem("ButtonSet");
    var buttonSize = 48;
    var buttons = new Bitmap(this.standardPadding() * 2, this.standardPadding() * 2);
    buttons.bltImage(buttonSet, buttonSize, 0, buttonSize * 2, buttonSize * 2, 0, 0, this.standardPadding() * 2, this.standardPadding() * 2);
    
    // Boutons de défilement vers la gauche / vers la droite (scrollX)
    this._buttonLeft = new Sprite_Button();
    this._buttonLeft.opacity = 0;
    this._buttonLeft.bitmap = buttons;
    this._buttonLeft.setColdFrame(0, 0, this.standardPadding(), this.standardPadding());
    this._buttonLeft.setHotFrame(0, this.standardPadding(), this.standardPadding(), this.standardPadding());
    this._buttonLeft.x = this.standardPadding() * 2;
    this._buttonLeft.y = height - this.standardPadding() * 2;
    this._buttonLeft.rotation = 90 * Math.PI / 180;
    this._buttonLeft.setClickHandler(() => {
        this.scrollX = Math.max(0, this.scrollX - this.scrollSpeed);
        this.setNeedRefresh();
    });
    this._buttonRight = new Sprite_Button();
    this._buttonRight.opacity = 0;
    this._buttonRight.bitmap = buttons;
    this._buttonRight.setColdFrame(0, 0, this.standardPadding(), this.standardPadding());
    this._buttonRight.setHotFrame(0, this.standardPadding(), this.standardPadding(), this.standardPadding());
    this._buttonRight.x = width - this.standardPadding() * 3;
    this._buttonRight.y = height - this.standardPadding();
    this._buttonRight.rotation = 270 * Math.PI / 180;
    this._buttonRight.setClickHandler(() => {
        this.scrollX = Math.min(Math.max(0, this.contents.width - this.viewport.width), this.scrollX + this.scrollSpeed);
        this.setNeedRefresh();
    });
    
    // Boutons de défilement vers le haut / vers le bas (scrollY)
    this._buttonUp = new Sprite_Button();
    this._buttonUp.opacity = 0;
    this._buttonUp.bitmap = buttons;
    this._buttonUp.setColdFrame(this.standardPadding(), 0, this.standardPadding(), this.standardPadding());
    this._buttonUp.setHotFrame(this.standardPadding(), this.standardPadding(), this.standardPadding(), this.standardPadding());
    this._buttonUp.x = width - this.standardPadding() * 2;
    this._buttonUp.y = this.standardPadding();
    this._buttonUp.setClickHandler(() => {
        this.scrollY = Math.max(0, this.scrollY - this.scrollSpeed);
        this.setNeedRefresh();
    });
    this._buttonDown = new Sprite_Button();
    this._buttonDown.opacity = 0;
    this._buttonDown.bitmap = buttons;
    this._buttonDown.setColdFrame(0, 0, this.standardPadding(), this.standardPadding());
    this._buttonDown.setHotFrame(0, this.standardPadding(), this.standardPadding(), this.standardPadding());
    this._buttonDown.x = width - this.standardPadding() * 2;
    this._buttonDown.y = height - this.standardPadding() * 3;
    this._buttonDown.setClickHandler(() => {
        this.scrollY = Math.min(Math.max(0, this.contents.height - this.viewport.height), this.scrollY + this.scrollSpeed);
        this.setNeedRefresh();
    });
    
    // Ajout des éléments à la fenêtre
    this.addChild(this._scrollbarX);
    this.addChild(this._scrollbarY);
    this.addChild(this._buttonLeft);
    this.addChild(this._buttonRight);
    this.addChild(this._buttonUp);
    this.addChild(this._buttonDown);
};

Window_Scrollable.prototype.setNeedRefresh = function() {
    this.needRefreshViewport = true;
};

Window_Scrollable.prototype.needScrollX = function() {
    if(this.contents) {
        if(
            (this.contents.width > this.width - this.standardPadding() * 2 && this.contents.height <= this.height - this.standardPadding() * 2) ||
            (this.contents.width > this.width - this.standardPadding() * 3 && this.contents.height > this.height - this.standardPadding() * 2)
        ) return true;
    }
    return false;
};

Window_Scrollable.prototype.needScrollY = function() {
    if(this.contents) {
        if(
            (this.contents.height > this.height - this.standardPadding() * 2 && this.contents.width <= this.width - this.standardPadding() * 2) ||
            (this.contents.height > this.height - this.standardPadding() * 3 && this.contents.width > this.width - this.standardPadding() * 2)
        ) return true;
    }
    return false;
};

Window_Scrollable.prototype.scrollXWidth = function() {
    return Math.round(this._scrollbarX.width * Math.min(1, this.viewport.width / this.contents.width));
};

Window_Scrollable.prototype.scrollYHeight = function() {
    return Math.round(this._scrollbarY.height * Math.min(1, this.viewport.height / this.contents.height));
};

Window_Scrollable.prototype.scrollXMax = function() {
    return this._scrollbarX.width - this.scrollXWidth();
};

Window_Scrollable.prototype.scrollYMax = function() {
    return this._scrollbarY.height - this.scrollYHeight();
};

Window_Scrollable.prototype.contentsWidth = function(withScrollbar = false) {
    if(this.needScrollY() || withScrollbar) return this.width - this.standardPadding() * 3;
    return this.width - this.standardPadding() * 2;
};

Window_Scrollable.prototype.contentsHeight = function(withScrollbar = false) {
    if(this.needScrollX() || withScrollbar) return this.height - this.standardPadding() * 3;
    return this.height - this.standardPadding() * 2;
};

Window_Scrollable.prototype.setScroll = function(x = null, y = null, position = Window_ScrollPosition.TOP_LEFT) {
    if(x === null) {
        x = this.scrollX
    }
    if(y === null) {
        y = this.scrollY
    }
    switch(position) {
        case Window_ScrollPosition.TOP_LEFT:
            this.scrollX = x;
            this.scrollY = y;
            break;
        case Window_ScrollPosition.TOP_CENTER:
            this.scrollX = Math.max(0, x - this.viewport.width / 2);
            this.scrollY = y;
            break;
        case Window_ScrollPosition.TOP_RIGHT:
            this.scrollX = Math.max(0, x - this.viewport.width);
            this.scrollY = y;
            break;
        case Window_ScrollPosition.MIDDLE_LEFT:
            this.scrollX = x;
            this.scrollY = Math.max(0, y - this.viewport.height / 2);
            break;
        case Window_ScrollPosition.MIDDLE_CENTER:
            this.scrollX = Math.max(0, x - this.viewport.width / 2);
            this.scrollY = Math.max(0, y - this.viewport.height / 2);
            break;
        case Window_ScrollPosition.MIDDLE_RIGHT:
            this.scrollX = Math.max(0, x - this.viewport.width);
            this.scrollY = Math.max(0, y - this.viewport.height / 2);
            break;
        case Window_ScrollPosition.BOTTOM_LEFT:
            this.scrollX = x;
            this.scrollY = Math.max(0, y - this.viewport.height);
            break;
        case Window_ScrollPosition.BOTTOM_CENTER:
            this.scrollX = Math.max(0, x - this.viewport.width / 2);
            this.scrollY = Math.max(0, y - this.viewport.height);
            break;
        case Window_ScrollPosition.BOTTOM_RIGHT:
            this.scrollX = Math.max(0, x - this.viewport.width);
            this.scrollY = Math.max(0, y - this.viewport.height);
            break;
    }
    this.setNeedRefresh();
};

Window_Scrollable.prototype.refreshViewport = function() {
    this._scrollbarX.bitmap.clear();
    this._scrollbarX.bitmap.fillRect(this.scrollX / (this.contents.width - this.viewport.width) * (this._scrollbarX.width - this.scrollXWidth()), 0, this.scrollXWidth(), this._scrollbarX.height, "white");
    this._scrollbarY.bitmap.clear();
    this._scrollbarY.bitmap.fillRect(0, this.scrollY / (this.contents.height - this.viewport.height) * (this._scrollbarY.height - this.scrollYHeight()), this._scrollbarY.width, this.scrollYHeight(), "white");
    if(this.needScrollX()) {
        this._buttonLeft.opacity = 255;
        this._scrollbarX.opacity = 255;
        this._buttonRight.opacity = 255;
    } else {
        this._buttonLeft.opacity = 0;
        this._scrollbarX.opacity = 0;
        this._buttonRight.opacity = 0;
    }
    if(this.needScrollY()) {
        this._buttonUp.opacity = 255;
        this._scrollbarY.opacity = 255;
        this._buttonDown.opacity = 255;
    } else {
        this._buttonUp.opacity = 0;
        this._scrollbarY.opacity = 0;
        this._buttonDown.opacity = 0;
    }
    this.viewport = new Bitmap(this.contentsWidth(), this.contentsHeight());
    this.viewport.blt(
        this.contents, // source
        this.scrollX, // source x
        this.scrollY, // source y
        Math.min(this.contents.width, this.viewport.width), // source width
        Math.min(this.contents.height, this.viewport.height), // source height
        0, // dest x
        0 // dest y
    );
};

Window_Scrollable.prototype.update = function() {
    Window_Base.prototype.update.call(this);
    if(Input.isTriggered("left")) {
        this.scrollX = Math.max(0, this.scrollX - this.scrollSpeed);
        this.setNeedRefresh();
    }
    if(Input.isTriggered("right")) {
        this.scrollX = Math.min(Math.max(0, this.contents.width - this.viewport.width), this.scrollX + this.scrollSpeed);
        this.setNeedRefresh();
    }
    if(Input.isTriggered("up")) {
        this.scrollY = Math.max(0, this.scrollY - this.scrollSpeed);
        this.setNeedRefresh();
    }
    if(Input.isTriggered("down")) {
        this.scrollY = Math.min(Math.max(0, this.contents.height - this.viewport.height), this.scrollY + this.scrollSpeed);
        this.setNeedRefresh();
    }
    if(this.needRefreshViewport) this.refreshViewport();
};

Utilisation

Ce script définit deux nouvelles classes. Tout d’abord la classe Window_Scrollable, qui correspond à la fenêtre équipée de deux ascenseurs (un vertical et un horizontal) affichés uniquement si nécessaire, mais aussi AutoResize_Bitmap, qui représente un objet Bitmap classique, mais dont la taille s’adapte au contenu (il s’agrandit tout seul, autant que nécessaire).

Window_Scrollable peut s’utiliser comme un objet Window classique, auquel cas, pour que cette classe serve à quelque chose, il faut recréer un bitmap dont la taille dépasse celle de la fenêtre :

window = new Window_Scrollable(x, y, width, height);
window.contents = new Bitmap(width, height);

Si vous utilisez window.contents.width ou window.contents.height pour l’une ou l’autre des dimensions du bitmap et que la seconde dimension dépasse la taille de la fenêtre, vous aurez une surprise : le contenu dépassera dans les deux sens. En effet, window.contents.width et window.contents.height retourneront les dimensions sans tenir compte de la place occupée par les scrollbars. C’est pourquoi j’ai redéfini dans cette classe les deux méthodes contentsWidth et contentsHeight. Elles peuvent prendre true en paramètre pour forcer l’obtention de l’une ou l’autre des deux dimensions en déduisant cette fois-ci de l’espace occupé par la scrollbar (ce qui permet de ne pas dépasser dans la seconde dimension lorsqu’on est sûr de dépasser dans la première).

Mais il est aussi possible de dire à la fenêtre de s’adapter automatiquement au contenu qui sera ajouté à window.contents, simplement en spécifiant un dernier paramètre au constructeur :

window = new Window_Scrollable(x, y, width, height, autoResize);

Ce dernier paramètre peut prendre soit :

  • Un booléen :
    • false, valeur par défaut, window.contents ne s’adaptera pas au contenu qui lui sera ajouté
    • true, window.contents s’adaptera au contenu qui lui sera ajouté et s’agrandira chaque fois qu’un nouvel élément sera ajouté et débordera du Bitmap. Aucun espace ne sera maintenu autour du contenu
  • Un nombre :
    • Cette valeur est l’espace qui entourera le contenu du Bitmap en pixel. Elle sera alors appliquée aux 4 côtés.
  • Un objet dans lequel il faudra définir une ou plusieurs des clés suivantes :
    • Pour l’espacement à droite et à gauche du contenu :
      • x : valeur commune aux deux côtés
      • left : valeur appliquée à gauche (ignorée si x est défini)
      • right : valeur appliquée à droite (ignorée si x est défini)
    • Pour l’espacement au dessus et en dessous du contenu :
      • y : valeur commune aux deux côtés
      • top : valeur appliquée au dessus (ignorée si y est défini)
      • bottom : valeur appliquée en dessous (ignorée si y est défini)

Ce même paramètre autoResize est utilisé comme unique paramètre du constructeur de la classe AutoResize_Bitmap et a le même comportement. En fait, AutoResize_Bitmap est la classe utilisée en interne dans la classe Window_Scrollable si le paramètre autoResize est spécifié.

Si vous voulez avoir deux exemples de ce qui peut être fait avec cette classe Window_Scrollable, vous trouverez une démo sur mon site de démonstration, onglet Systèmes > Window Scrollable. N’hésitez pas à tester et à me faire un retour.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *