Simple menu stacker in CraftyJs

I’ve been reacquainting myself with CraftyJs after over a year away. As part of that process, I came up with the following code snippet for a simple horizontal and/or vertical stacker for elements. I intend to use this for menus, but theoretically it could be used for other components I suppose.

Let’s start with the menu items. A “hoverable” widget, which changes the color on hover:

Crafty.c("Hoverable", {
    _baseColor: 'gray',
    _hoverColor: 'lightgray',
    init: function() {
        this.requires('Color, Mouse');
        this.color(this._baseColor);
        this.bind("MouseOver", function(e){
            this._baseColor = this.color();
            this.color(this._hoverColor);
        });
        this.bind("MouseOut", function(e){
            this.color(this._baseColor);
        });
    },
    hoverColor: function(newColor){
        this._hoverColor = newColor;
        return this;
    }
});

Some simple menu elements; a button and a title bar:

Crafty.c("Button", {
    init: function(){
        this.requires('DOM, 2D, Mouse, Hoverable');
        this.css({
            "border": "solid thin black"
        });
        this.attr({h: 20, w: 60});
    }
});
 
Crafty.c("MenuHeading", {
    init: function(){
        this.requires('DOM, 2D, Color, Text');
        this.color('rgb(200,200,200)');
        this.text("Title");
        this.attr({h: 20, w: 60});
    }        
});

The stacker code:

Crafty.c("StackerContainer", {
    _stackedItems: null,
    _padding: 15,
    _margin: 5,
    init: function(){
        this.requires('DOM, 2D, Hider');
        this._stackedItems = [];
        this.hide();
    },
    appendItem: function(item){
        item.addComponent('Hider');
        item.hide();
        this._stackedItems.push(item);
        return this;
    },
    padding: function(size){
        this._padding = size;
        return this;
    },
    margin: function(size){
        this._margin = size;
        return this;
    },
    render: function(){
        for(var i=0; i<this._stackedItems.length; i++){
            var item=this._stackedItems[i];
            var lastItem = this._stackedItems[i-1];
            this.stack(item, lastItem);
            this.attach(item);
            if(item.has('StackerContainer')){
                item.render();
            }
            else {
                item.show();
            }
        }
        this.show();
        return this;
    },
    removeAll: function(){
        for(var i=0; i<this._stackedItems.length; i++){
            this.detach(this._stackedItems[i]);
        }
        return this;
    }
});
 
Crafty.c("VStackerContainer", {
    init: function(){
        this.requires('StackerContainer');
    },
    stack: function(item, afterItem) {
        item.attr({
            x: afterItem ? afterItem.x : this.x + this._margin,
            y: afterItem ? afterItem.y + afterItem.h+ this._padding: this.y + this._margin,
            w: this.w - 2*this._margin
        });
    }
});
 
Crafty.c("HStackerContainer", {
    init: function(){
        this.requires('StackerContainer');
    },
    stack: function(item, afterItem) {
        item.attr({
            x: afterItem ? afterItem.x + afterItem.w + this._padding: this.x + this._margin,
            y: afterItem ? afterItem.y : this.y + this._margin,
            h: this.h - 2*this._margin
        });
    }
});
 
Crafty.c("Hider", {
    init: function() {
        this.requires('DOM');
    },
    hide: function() {
        this.css({display: "none"});
        return this;
    },
    show: function() {
        this.css({display: ""});
        return this;
    }
});

And finally, some sample application code using these elements:

Crafty.init(480, 320);
Crafty.background('rgb(20, 125, 40)');
 
Crafty.e('VStackerContainer').attr({h:200, w:400, x:10, y:10})
    .appendItem(Crafty.e('MenuHeading').text('Heading 1'))
    .appendItem(Crafty.e('Button, Text').text('Button 1'))
    .appendItem(Crafty.e('HStackerContainer').attr({h:100})
        .margin(20).padding(40).css({border:"solid thin black"})
        .appendItem(Crafty.e('MenuHeading').text('Heading 2'))
        .appendItem(Crafty.e('Button, Text').text('Button 2')))
    .render(true);

The end result looks a bit like this:

A screen capture of the resulting application, with two broad horizontal boxes followed by two tall and thin boxes side-by-side.

It’s far from a general purpose module at this point, but the ability to build up menus on the fly without worrying about layout has the hint of something which could be used in various game concepts I have at this point.

Leave a Reply

Your email address will not be published. Required fields are marked *

*