Hands free CSS – no pixels

Some time ago I watched this great video: https://youtu.be/zdI2Z64Jdw8 . Seriously – if you are doing front end html and css, go and watch it, it is great. Basically what guy is showing is how to get responsive pages that will adjust to screen size without using media queries. While I’m pretty certain it is not always worth doing, I do think it is worth trying out first on side projects.

Now I’m not doing front end all that much. I can write html and css to do the things I want them to, but more often than not I need to use big hammer to make it all fit in place. Raim on the other hand is so small and simple that I decided to give it a try and not use any media queries for now (not that I have used any to this point) as well as to avoid absolute units. So no pixels for you, mister!

There are just a few things at this point I want to position on the page. Those are: name of the application along with competition logo, list of players and game arena, where canvas is injected. Before today the code looked like this:

.titleSection {
    position: fixed;
    left:    10px;
    bottom:  7px;
    opacity: 0.3;
    font-weight: bold;
    /*transition: opacity ease-in .2s;*/
}

.titleSection:hover {
    opacity: 0.9;
}

.dsp2016 {
    background-image: url(/Content/DSP2016.png);
    background-size: contain;
    width: 21px;
    height: 48px;
    display: inline-block;
}

#playersList {
    opacity: 0.7;
    font-size: small;
    display: inline-block;
    position: absolute;
    bottom: 70px;
}

#arena {
    z-index: -1;
    border: 1px solid black;
    position: fixed;
    top:    5px;
    right:  5px;
    bottom: 5px;
    left:   5px;
}

Some pixels are creeping out. Positioning content some pixels above or below will most likely fail on screens with higher dpi. Well, it will still work, but it might not put as much spacing between elements as I would like. Size of logo, too, should be responsive and adjust itself, to always be visible, but not get too big.

After improvements page looks pretty much the same way it did before, but the css file now has this inside of it:

.titleSection {
    position: fixed;
    left:    1em;
    bottom:  0.5em;
    opacity: 0.3;
    font-weight: bold;
    transition: opacity ease-in .2s;
}

.titleSection:hover {
    opacity: 0.9;
}

.dsp2016 {
    background-image: url(/Content/DSP2016.png);
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
    width: 2em;
    height: 2.5em;
    display: inline-block;
}

#playersList {
    opacity: 0.7;
    font-size: smaller;
    display: inline-block;
    position: absolute;
    bottom: 5em;
    left: 1em;
}
    #playersList span {
        display:block;
    }

#arena {
    z-index: -1;
    border: 0.1em solid black;
    position: fixed;
    top:    0.3em;
    right:  0.3em;
    bottom: 0.3em;
    left:   0.3em;
}

First look at dsp2016 class. Width and height are in relative units, 2.5×2 units in size, with also added no repeat and centering of background image. This places the image nicely, with some spacing around it. All the positions of other elements are also relative to font size now. I can now preserve the ratio of view with simply changing body font size. All will stay in the same relation to each other, players list and game logo will stay inside bordered game arena, never overlaying each other.

Advertisements

Fixing canvas Y axis

Time to tackle one of the problems that gets on my nerver every time I do something graphic related in html. And I guess canvas is not the only element in the world with that problem.

My whole life I’ve been using X axis with values getting bigger to the right, and Y axis with values getting bigger the higher I go. But of course that’s not how things work in HTML and Canvas at all. Y axis is flipped to be precise. The higher Y value element gets, the lower it is displayed. That goes agains my every intuition. If someone is experienced with canvas drawing – that may as well make sense. Heck, it makes sense to me as well, when displaying elements on forms, pages etc. But it does not make sense when I calculate 2D graphics.

I could accept the world as it is and just leave the code the way I wrote it originally – pressign up arrow decreasing Y values, and down arrow increasing them. But that is not what I want. I need to have code that matches my domain model – even if the domain model is piece of paper from second or third grade math class.

Fortunately, doing amendments in code to display the graphics correctly is not all that hard. First problem is – what do I have to do to make it work? Well, I need to flip the Y axis coordinates on screen, that’s for sure. Simple enough:

function drawObject(gameObject) {
    var x, y;
    drawingContext.beginPath();

    drawingContext.strokeStyle = "#F00";
    drawingContext.fillStyle = "#F00";

    x = gameObject.Position.X;
    y = gameObject.Position.Y;
    drawingContext.arc(x, -y, gameObject.Size, 0, 2 * Math.PI);
    drawingContext.stroke();
    drawingContext.fill();

    drawingContext.closePath();

    drawingContext.beginPath();

    drawingContext.strokeStyle = "#0F0";

    x = gameObject.Position.X + gameObject.FacingDirection.X * gameObject.Size / 2;
    y = gameObject.Position.Y + gameObject.FacingDirection.Y * gameObject.Size / 2;
    drawingContext.moveTo(x, -y);

    x = gameObject.Position.X + gameObject.FacingDirection.X * gameObject.Size;
    y = gameObject.Position.Y + gameObject.FacingDirection.Y * gameObject.Size;
    drawingContext.lineTo(x, -y);
    drawingContext.stroke();
}

This does fix the problem, Y values are flipped, resulting in picture mirrored around X axis. Perfect. Well, to a point. If I put an object on position 50, 50 – it is not visible on the screen. Well, if I flip the Y part, I get 50, -50, so the object will get displayed above the top part of canvas. Makes total sense, but is not exactly what I wanted – I don’t want to create my objects with negative Y values (although I could do just that). I think I would like to move entire 0, 0 point from top left to bottom left corner of the screen. To do that, I just have to decrease X values of any object by canvas height.

That would do, of course. But what if later on I will want to move displayed area to different game part? Say for example if player moves to the edge of the screen? Since I’m already deep in messing with displaying graphics, why not do it now?

The idea is simple – I will hold the viewport area in variable and adjust display screen to fit the viewport. For the moment I will only hold the upper limits of the viewport, limiting displayed area to those values, assuming that whole screen should be used to display as much of the area as possible. In future that viewport could be extended to support different size of devices and scale graphics appropriately to display more or less the same game area size (so all players will be able to see the same part of playing area).

var viewport = { x: 0, y: canvas.height };

function drawObject(gameObject) {
    var x, y;
    drawingContext.beginPath();

    drawingContext.strokeStyle = "#F00";
    drawingContext.fillStyle = "#F00";

    x = gameObject.Position.X - viewport.x;
    y = gameObject.Position.Y - viewport.y;
    drawingContext.arc(x, -y, gameObject.Size, 0, 2 * Math.PI);
    drawingContext.stroke();
    drawingContext.fill();

    drawingContext.closePath();

    drawingContext.beginPath();

    drawingContext.strokeStyle = "#0F0";

    x = gameObject.Position.X + gameObject.FacingDirection.X * gameObject.Size / 2;
    y = gameObject.Position.Y + gameObject.FacingDirection.Y * gameObject.Size / 2;
    x -= viewport.x;
    y -= viewport.y;
    drawingContext.moveTo(x, -y);

    x = gameObject.Position.X + gameObject.FacingDirection.X * gameObject.Size;
    y = gameObject.Position.Y + gameObject.FacingDirection.Y * gameObject.Size;
    x -= viewport.x;
    y -= viewport.y;
    drawingContext.lineTo(x, -y);
    drawingContext.stroke();
}

As you can see all I am doing is decreasing x and y parts of every drawn element by viewport limits. This way if my display size is 220, 220 and my object is at 50, 50, after calculations I will get 50, -170 and object will get displayed 50 units above bottom left corner. Notice that x part of viewport is set to 0 – I don’t want to do any translation along x axis after all – left corner stays left. I just want to move it from top to bottom – hence viewport y is initialized to canvas height. Now if player moved to the top right edge of the screen all I have to do is update the viewport with appropriate values and display area will get corrected.

But that’s not all. View looks OK, but the input from player is all messed up! I’m getting mouse position in canvas coordinates, and those no longer match object posiotion in any reasonable way. The same translations and flipping has to be applied to any user input as well:

var mouse = input.mouse;
var vector = {
    x: (mouse.x - gfx.viewport.x) - p.Position.X,
    y: -(mouse.y - gfx.viewport.y) - p.Position.Y
}; // invert y axis input

First I apply viewport translations, then I flip the value around to get it to game area coordinates. And now it works the same way it did before. Just better :)

Mouse input – element offset from window

With keyboard movement it is time to handle some mouse inputs as well. Player should be able to move with keyboard but he or she needs to be able to face some directions, aim and probably shoot by mouse interaction.

First part seems pretty straightforward. There is mouseMove event waiting to be used. Let’s start with just that:

// renamed from keyboardInput to userInput
function userInput(args) {
    // old code

    function mouseMove(e) {
        console.log(e);
    }

    (function () {
        // ...
        document.addEventListener("mousemove", mouseMove);
    })();
};

Now whenever mouse moves over document event will be raised and its arguments get logged to console. That’s a lot of inputs but we can handle that, just need to make processing short to not block it for too long.

Inspecting event arguments there are many different properties, including many different positions of mouse event: screen, client, layer, offset. Uh, easy to get lost. Thanks for google and Mozilla Developer Network, which tells that offset is exactly what I want – x and y offset from event target’s edge. Ah, no, wait – that will only work when mouse is moved over canvas area. How about other parts of window? Well, one might assume that only canvas is appropriate for handling those events. Others might argue that whole window area should be used. I guess both can be right, depends on what one wants to achieve. I am slightly more attracted by the whole window option. If user points to players list – I would still like to have events handled correctly. So that’s what I will do.

But if player moves mouse outside of canvas area, offset will be of no use. Thankfully – client can be used, with a little bit more code. All we need to do is – to calculate position of mouse in context of canvas. Of course those values can get weird – like negative position etc. but that should not scare anyone. To do this calculation I need position of canvas in context of window. That’s quite easy:

document.getElementById("arena").children[0].getBoundingClientRect()

This gives rectangle with coordinates of canvas (first and only child of arena element) in relation to window. In my case, when there are no scrollbars, that’s perfect. In other cases, if you scroll below canvas start – values get negative – and it makes sense – canvas would start higher than window reaches.

And all this adds to:

function mouseMove(e) {
    console.log({ x: e.clientX, y: e.clientY });
    var targetRect = document.getElementById("arena").children[0].getBoundingClientRect();
    console.log({ x: e.clientX - targetRect.left, y: e.clientY - targetRect.top });
}

Logged values are first screen position and then position inside canvas, which can later be used to my calculations.

Sending player movement to all players

Once the player has pressed the key and we got the info, everyone would expect the application to react to it somehow. Fortunately that is pretty simple – I defined callback in my Area code that will be called every time game notices player wanting to change the position. To this callback client side hub will subscribe a method sending communication to server. This in turn will modify player’s status on the server side and send updated position to all other players, letting them see position change.

Arena.js

var keyDown = function (e) {
    var key = 0;
    if (e.which === 87 || e.which === 119 || e.which === 38)
        key |= moveDirections.Up;

    if (e.which === 83 || e.which === 115 || e.which === 40)
        key |= moveDirections.Down;

    if (e.which === 65 || e.which === 97 || e.which === 37)
        key |= moveDirections.Left;

    if (e.which === 68 || e.which === 100 || e.which === 39)
        key |= moveDirections.Right;

    if (key > 0)
        playerMoving({ direction: key });
};

var playerMoved = function (who) {
    var player = players.get(who.Name);
    player.Position = who.Position;
    drawArena();
};

raim.js

var arena = new Arena({
    playerMoving: function (e) {
        raim.server.playerMoving(e.direction);
    }
});

raim.client.registered = arena.addNewPlayer;
raim.client.playerMoved = arena.playerMoved;

RaimHub.cs

public void PlayerMoving(int direction)
{
    var player = players[Context.ConnectionId];
    player.Position.X += 10; // draft implementation
    Clients.All.PlayerMoved(player);
}

It gets very ‘drafty’ at points, but the idea is clear. And it works – server gets notification, sends player position back, using player’s name I get his object and set new position (and name will be later changed to Id of some sort in future, of course). There is one problem though – the new player position is printied, true, but the old one gets displayed as well.

Well, not quite. The old one was just not removed from screen. Canvas needs to be cleared between refreshes.

var drawArena = function () {
    drawingContext.clearRect(0, 0, view.width, view.height);
    for (var i = 0; i < players.count() ; i++) {
        var player = players.get(i);

        drawingContext.strokeStyle = "#F00";
        drawingContext.fillStyle = "#F00";
        drawingContext.arc(player.Position.X, player.Position.Y, player.Size, 0, 2 * Math.PI);
        drawingContext.stroke();
        drawingContext.fill();
    }
};

clearRect is supposed to remove content form the part of the screen it was pointed to (view is initialised in constructor with actual available screen width and height). This implementation however does not work. clearRect is not clearing screen, marking pixels as white, transparent or whatever.

What is missing is – every time we draw on canvas, we do it to create some path. And every time we call fill or stroke that path gets filled with colors defined. I haven’t defined any path though, so it is putting all requests into the same path, never closing it. So even though the clearRect clears the screen, first time stroke and fill functions are called, all historical changes to path are being repainted again and again – since the path was not cleared.

How to solve it? Oh, simple – open new path for every object that is being drawn (or even for every part of an object, once they get more complex).

var drawArena = function () {
    drawingContext.clearRect(0, 0, view.width, view.height);

    for (var i = 0; i < players.count() ; i++) {
        var player = players.get(i);

        drawingContext.beginPath();
        drawingContext.strokeStyle = "#F00";
        drawingContext.fillStyle = "#F00";
        drawingContext.arc(player.Position.X, player.Position.Y, player.Size, 0, 2 * Math.PI);
        drawingContext.stroke();
        drawingContext.fill();
        drawingContext.closePath();
    }
};

User input and Canvas

With player’s token displayed on screen, next step would be to actually provide player with any interaction with application. Standing still is not much fun I think. Time to handle user input.

My first thought was to do it like this:

var keyDown = function (e) {
    console.log(e.keyCode);


(function init() {
    players = args.playersList || new PlayersList(args.playersListOptions);
    var canvas = document.createElement("canvas");
    canvas.addEventListener("keydown", keyDown);
    arena.appendChild(canvas);
    drawingContext = canvas.getContext("2d");
})();

However, to my surprise, that does not work at all. Nothing got logged to console, no matter how hard I pressed those keys, no matter how many times I clicked on canvas area to make it focus.

Focus. Canvas. Yea, not gonna work. Canvas does not capture focus it seems. Makes sense, at least at some level. How to work around it? First idea was to attach event listener to document directly. And while it sounds reasonable, I thought that there must be other solution.
Five minutes with google and I have it:

var canvas = document.createElement("canvas");
canvas.setAttribute("tabindex", 1);
canvas.addEventListener("keydown", keyDown);
arena.appendChild(canvas);

Notice setting tabindex attribute. Turns out it is quite well know (among UI programmers I guess) workaround – setting tab index on any element will make it focusable. That, too, makes some sense. But unfortunately it gives blue border around canvas. This I guess could be handled by CSS styling of the element.

But dirty tricks like that in code, especially at the begining, might bite back in future. Will I remember what is this useless attribute there for in month or two? Knowing myself – absolutely not. And it is just one step away from removing it thinking I’ve just optimised the code. And broke it at the same time, but who’s counting?

I guess I will go with attaching listeners directly to document object then. That makes more sense to me.

Drawing on canvas

Sure would be nice to see some players on the screen, not just players’ list right? In a spirit of doing everything myself (which will cause me a lot of pain later on, I’m sure, but I’ll let future me worry about it) I won’t get any graphics engine to help me with this task. Well, nothing more than plain HTML canvas has to offer.

First, it is time to encapsulate players’ list inside game object. I don’t want to handle all the logic inside my hub after all.

function Arena(args) {
    args = args || {};
    var players;
    var arenaHandler = args.arena || "arena";
    var arena = document.getElementById(arenaHandler);

    (function init() {
        players = args.playersList || new PlayersList(args.playersListOptions);
    })();

    var addNewPlayer = function (who) {
        players.addNewPlayer(who);
    }

    return {
        addNewPlayer: addNewPlayer,
    };
};

Ok, that was more work then benefit for now, just another layer of abstraction. But it will serve its purpose. Here I hope to have all the logic behind how the game works, at least at top level. It has handle for arena game display element. But I won’t be painting on a div directly, since HTML provides useful canvas with some helpful functions.

(function init() {
    players = args.playersList || new PlayersList(args.playersListOptions);
    var canvas = document.createElement("canvas");
    arena.appendChild(canvas);

    drawingContext = canvas.getContext("2d");
})();

Simple for now – create canvas, append it to game arena and get drawing context. This context is what HTML offers to help us draw lines, arcs, dots etc. All the basics. Visual studio even offers great intelisense for that feature, very helpful along the way if you are new to it.

Since I have where to draw, I need something to draw. Best would be to start with player’s position indicator on the game board. On server side Player object is created and added when registering to the game. It holds Position structure, with X and Y, as well as Size, which is plain integer for now. What can be drawn with that information? Square maybe? Yea, squares are fine, but I think I will go for something more curved. Like a circle. Since the player’s object is returned to all players, it can be used to draw the position on the arena in response to register event.

var addNewPlayer = function (who) {
    players.addNewPlayer(who);
    drawingContext.strokeStyle = "#F00";
    drawingContext.fillStyle = "#F00";
    drawingContext.arc(who.Position.X, who.Position.Y, who.Size, 0, 2 * Math.PI);
    drawingContext.stroke();
    drawingContext.fill();
}

Once player is added to the list of players, drawing context picks color for strike (border) and filling, then using arc function given by HTML canvas circle is being drawn. First two parameters are circle’s center, then the radius, after that comes starting angle and ending angle. Angles are given in radians; hence the closing angle is calculated to 2*Pi. After that all that is left is to draw the stroke and fill the content.

Voilà – player has his representation on game area!

AngularJS for web UI?

Well, yes. AngularJS. Why not? It got a lot of attention recently (or maybe I just noticed that) and it seemed like a good idea to try something new after having most of my .NET web experience with ASP.NET WebForms (and you don’t want to go that way if you don’t need to). So I picked it and started having fun.

And fun it was, indeed. Pretty simple to start and create something. Nicely working, updating UI form model, updating model from UI. Clean code, clean HTML markup. I must say I’m impressed, didn’t expect it to be that easy and nice (the same feeling I had with NancyFX recently, I start to see a pattern – I’m doing what I want, how I want and with tools I want – me like it).

But there are some dark corners in Angular that got me when I least expected it. First – double declared ng-app attribute. Well, my fault of course. I set it on html element first in Master Page, done few things, changed few things and added by mistake second ng-app attribute in actual page, using this master page. Small error, but application stopped working, nothing behaved like expected. Blank screen, no bindings. Took me quite a while to figure it out. Oh, how I wish Angular told me – hey, man. I’ve noticed you put two ng-app attributes in your page. Sure you wanted to do that? But nope, nothing, zero, nada.

Second time it bite me was when I was doing some model data manipulation. Everything working fine in JavaScript, but in UI – nothing gets refreshed. I used ng-click binding to get function executed. Oh, how much time I spent (but not wasted completely) looking for error. Everything seemed to be ok, code works, just UI isn’t right. Looked at documentation, looked at StackOverflow, looked at blogs. Nothing helped. Learned a lot about Angular, but I just wanted my bindings to work! Well, look at this code:
<a href="#/" ng-click="manipulate()">Link</a>
Nothing extremely complicated. What’s wrong? It have href attribute set. Oh boy, I had nice facepalm once I noticed it. With Angular routing set up, going to #/ caused re-creation of my controller, reloading data and refreshing page. But it all happened so fast that I simply did not noticed and it all looked to me like my code is not working. Remove href and guess what? It was working like a charm from the beginning.

I’m pretty sure I will have quite a few problems with Angular. But it seems to be such a great tool that I am willing to solve them and learn along the way just for fun of working with something that clever.