Simple UI Applications
======================

This tutorial aims to introduce and illustrate the fundamental concepts of the [UI
library](md:/Library_Reference/UI_Library/) by creating the starting point of an application that
visualizes chess games. The code produced in this tutorial is available in the directory
`root/tutorials/ui` in the release of Storm. You can run it by typing `tutorials:ui:main` in the
Basic Storm interactive top-loop.

Setup
-----

Before we start writing code, we need somewhere to work. For this tutorial, we need to create a
directory with an appropriate name. In this case we will name it `ui`. Inside the directory, we also
need a `.bs` file that will contain the code for the application. We will also need a subdirectory
for resources (images in this case). We will name this directory `res`. In our case, we will place
images of chess pieces in the `res` directory. These can be copied from the `root/tutorials/ui/res`
directory in the Storm release. The naming convention of the images is as follows. The first
character indicates the type of the piece, and the second character indicates the color of the
piece. This convention will be used to find the appropriate pieces later on.

The resulting directory structure will look as follows:

```
ui
├─ chess.bs
└─ res
   ├─ bb.png
   ├─ bw.png
   ├─ kb.png
   ├─ kw.png
   ├─ nb.png
   ├─ nw.png
   ├─ pb.png
   ├─ pw.png
   ├─ qb.png
   ├─ qw.png
   ├─ rb.png
   └─ rw.png
```

Creating a Frame
----------------

In the first part of the tutorial we will use the UI library to create a frame that will serve as
the base window for the actual graphical application. Inside the frame, we will create another
window where we will draw the chessboard.

The first step is to create the [frame](md:/Library_Reference/UI_Library/Windows), which is a window
that has borders and can contain other windows. In order to do so we need to create a subclass to
`Frame`, which we will call `ChessVisualizer`. To initialize the `Frame`, we need to provide a title
and an initial size as shown below.

```bs
use ui;
use core:geometry;

class ChessVisualizer extends Frame {
    init() {
        init("ChessVisualizer", Size(500, 500)) {}
        create();
    }
}
```

As seen above, we need to tell the library when we are done initializing a frame by calling the
`create` function. Without this call, the window will not be shown. To avoid flickering, this is
typically done last, after creating any components. Note that this is only needed for frames. Other
windows are created lazily when they are attached to a parent, or when the parent is created.

To run the code, we also need a `main` function that creates our frame. It can be implemented as
follows:

```bs
void main() {
    ChessVisualizer cv;
    cv.waitForClose();
}
```

In the code above, we simply create an instance of our frame. Since the constructor calls `create`,
we do not need to do anything else for it to be shown on the screen. We do, however, need to call
`waitForClose`. Without this call, the `main` function would return almost immediately, and Storm
would shut down before we have a chance of interacting with the window.

At this point we can test the program by running the following in a terminal:

```
storm ui
```


The UI library also provides a syntax extension to Basic Storm to make it more convenient to work
with windows. In particular, this syntax allows specifying layout using the [layout
DSL](md:/Library_Reference/UI_Library/Layout). This does, however, require that we define the
`ClassVisualizer` class using the keyword `frame` instead of as above. This lets the syntax
extension know that we are working with a frame, and that it should be possible to specify layout.
As such, we change our code into the following. Note that we do not have to add `extends Frame`
anymore, as that is implied by the `frame` keyword.

```bs
use ui;
use core:geometry;

frame ChessVisualizer {
    init() {
        init("ChessVisualizer", Size(500, 500)) {}
        create();
    }
}

void main() {
    ChessVisualizer cv;
    cv.waitForClose();
}
```

Creating and Drawing to the Window
----------------------------------

Now that we have a basic frame to work with, the next step is to create a window inside the frame
where we can draw the actual chess board. This is done by adding a
[layout](md:/Library_Reference/UI_Library/Layout) to the ChessVisualizer class. In this particular
case we keep it as minimal as possible in the beginning and simply add our inner window to a grid
layout. We also specify a minimum height and width in the
[DSL](md:/Library_Reference/Layout_Library/) of the layout library. We will expand further on the
layout functionality as we keep adding things to the grid.

```bsfragment:lang.bs.SFile:use=ui
frame ChessVisualizer {
    layout Grid {
        Window chessBoard { minHeight: 500; minWidth: 500; }
    }

    init() {
        init("ChessVisualizer", Size(500, 500)) {}
        create();
    }
}
```

In order to draw to the window, we need to create a subclass of the
[painter](md:/Library_Reference/UI_Library/Rendering) class from the graphics library, and then
attach the painter to the window. The simplest possible custom painter class is one that fills the
window with a solid white color. It is created by giving the painter an empty `init` function and a
`render` function that simply returns false as can be seen below.

```bs
class ChessPainter extends Painter {
    init() {
        init() {}
    }

    Bool render(Size me, Graphics g) {      
        return false; 
    }
}
```

Integrating the painter with the `chessBoard` window creates a frame with a white square inside of
it. The complete code to get this frame up and running is seen below:

```bs
use ui;
use graphics;
use core:geometry;

frame ChessVisualizer {
    layout Grid {
        Window chessBoard { minHeight: 500; minWidth: 500; }
    }
    
    ChessPainter boardPainter;
    
    init() {
        init("ChessVisualizer", Size(500, 500)) {}
        chessBoard.painter(boardPainter);   
        create();
    }
}

class ChessPainter extends Painter {
    init() {
        init() {
        }
    }

    Bool render(Size me, Graphics g) {      
        return false; 
    }
}

void main(){
    ChessVisualizer cv;
    cv.waitForClose();
}
```

As before, we can run the program by typing the following in a terminal:

```
storm ui
```

The next problem to tackle is to turn the `chessBoard` window into something that actually looks
like a chessboard and not just a white square. In order to do so we will set an appropriate
background color for the playing field that will represent the light squares, and draw the dark
squares on top of the background. We will also draw the labels commonly found on chess boards that
indicate the file and rank. Both of these tasks are achieved by extending the functionality of our
`ChessPainter` class.

To set the background color of the window, we can simply set the `bgColor` variable variable to the
desired color in the init function of the class. In order to draw the dark squares however, we need
to create a brush that the painter can use. We will use a so called
[`SolidBrush`](md:/Library_Reference/UI_Library/Rendering), which can be initialized either using
RGB values or one of the predefined [colors](md:/Library_Reference/Graphics_Library/) available from
the graphics library. We choose to store it as a member to the class so that we can reuse it each
time the `render` function is called, rather than having to create it from scratch each time. This
is particularly important for larger resources, such as images, to avoid transferring large
resources to the GPU on each frame. More information about this can be found in the rendering
reference, specifically the [resources](md:/Library_Reference/UI_Library/Rendering) section.
  
```bs
class ChessPainter extends Painter {
    SolidBrush darkBrush;
    
    init() {
        init() {
            darkBrush(Color(118, 150, 86));

        }
        bgColor = Color(238, 238, 210);     
    }
    
    Bool render(Size me, Graphics g) {              
        return false; 
    }
}
```

In order to actually draw things to the window we need to extend the functionality of the `render`
function. The parameters that are available to us in the render function are firstly, a parameter
that represent the current size of the window we are drawing to, and secondly a `ui.Graphics` object
that allows us to draw to the window. The graphics object has a number of members that can be used
to draw to the window. For this part we will use the `fill` function that fills a rectangle using a
specific brush.

If we look specifically at the problem of drawing a chess board we know that it is an 8x8 grid. The
squares are light and dark in an alternating pattern, and the top left square is light. As such, if
we divide the window into 64 parts and step through them we can place a rectangle at every other
step in order to fill in the dark squares. Put another way, loop through an 8x8 grid and draw a dark
square when the manhattan distance from top left is an odd number. To allow the user to resize the
window, we will use the `Size` parameter to compute the size and position of the square. However, at
this point the contents of the window is not actually resized. We will fix that later in the
tutorial.

The below code draws a grid of light and dark squares in the window within the frame:

```bs
class ChessPainter extends Painter {
    SolidBrush darkBrush;
    
    init() {
        init() {
            darkBrush(Color(118, 150, 86));

        }
        bgColor = Color(238, 238, 210);     
    }
    
    Bool render(Size me, Graphics g) {      
        Size squareSz = me / 8;

        for (Nat y = 0; y < 8; y++) {
            for (Nat x = 0; x < 8; x++) {
                if((x + y) % 2 == 1) {
                    g.fill(Rect(Point(squareSz.w * x.float, squareSz.h * y.float), squareSz), darkBrush);
                }
            }
        }
        
        return false; 
    }
}
```

Drawing Text to the Window
--------------------------

Adding the labels to the chess board follows a similar procedure. We do however use the `draw`
member in the graphics object instead of `fill`. To add the labels we will again have to modify the
painter class a bit. In this case we want black text, so we add another `SolidBrush` for this
purpose:

```bsstmt
SolidBrush blackBrush;
blackBrush(black);
```

Formatted text is also similar to brushes in that we want to create them once and then re-use them
where possible. This is to avoid computing the layout of the text each time we draw the window. As
such, we store the labels in two arrays, one for row labels and one for column labels. We populate
them in the init function with the appropriate text as follows:

```bsstmt
Text[] rowLabels;
Text[] colLabels;

for (row in ["8", "7", "6", "5", "4", "3", "2", "1"]) {
    rowLabels << Text(row, defaultFont);
}

for (col in ["a", "b", "c", "d", "e", "f", "g", "h"]) {
    colLabels << Text(col, defaultFont);
}
```

We then iterate through them in the `render` function and use the loop variable as well as the
`squareSz` variable to tell the `draw` function the location of the text. The location is relative
to the top-left corner of the text. As can be seen below, the `Text` object contains the size of the
laid-out text as the `size` member.

```bsstmt
for (i, rowLabel in rowLabels) {
    g.draw(rowLabel, blackBrush, Point(0, squareSz.h * i.float));
}

for (i, colLabel in colLabels) {
    g.draw(colLabel, blackBrush, Point(squareSz.w * (i + 1).float - 1, me.h) - colLabel.size);
}
```

Putting it all together results in the below code that draws the chessboard with the labels in the
window inside the frame:

```bs
class ChessPainter extends Painter {
    SolidBrush darkBrush;
    SolidBrush blackBrush;

    Text[] rowLabels;
    Text[] colLabels;
    
    init() {
        init() {
            blackBrush(black);
            darkBrush(Color(118, 150, 86));
        }
        
        bgColor = Color(238, 238, 210);
        
        for (row in ["8", "7", "6", "5", "4", "3", "2", "1"]) {
            rowLabels << Text(row, defaultFont);
        }
        
        for (col in ["a", "b", "c", "d", "e", "f", "g", "h"]) {
            colLabels << Text(col, defaultFont);
        }
    }
    
    Bool render(Size me, Graphics g) {      
        Size squareSz = me / 8;
        
        for (Nat y = 0; y < 8; y++) {
            for (Nat x = 0; x < 8; x++) {
                if ((x + y) % 2 == 1) {
                    g.fill(Rect(Point(squareSz.w * x.float, squareSz.h * y.float), squareSz), darkBrush);
                }
            }
        }

        for (i, rowLabel in rowLabels) {
            g.draw(rowLabel, blackBrush, Point(0, squareSz.h * i.float));
        }
        
        for (i, colLabel in colLabels) {
            g.draw(colLabel, blackBrush, Point(squareSz.w * (i + 1).float - 1, me.h) - colLabel.size);
        }
        
        return false; 
    }
}
```


Drawing Images to the Window
----------------------------

In this second part of the tutorial we will look at how we can load external resources into our
program and then draw them to a window. This will be illustrated by loading `png` images of the
various chess pieces and drawing them to a window. Since this tutorial is aimed at illustrating the
UI librarys functionality the implementation will be delimited to simply drawing the pieces at their
starting positions. There will be no data structure for the pieces themselves and there will be no
possibility to move them.

For this part of the tutorial we assume that the images are placed in a subdirectory called `res`,
as described in the top of this page. Furthermore we will assume you are familiar with the
information presented in the [files and streams tutorial](md:Files_and_Streams) since it will be
directly relevant to what we are doing in this part of this tutorial.


We will also use the `resUrl` function from the [files and streams
tutorial](md:/Getting_Started/Tutorials/Files_and_Streams/) to conveniently get the path of the
files. In this tutorial, we will use a slightly modified version of the function that simply returns
an `Url` to the `res` directory, rather than to a file inside the directory:

```bsfragment:SFile:use=lang.bs.macro
Url resUrl() {
    if (url = named{res}.url) {
        return url;
    } else {
        throw InternalError("Expected the package 'res' to be non-virtual.");
    }
}
```

If you do not wish to copy the files from the Storm release, you can replace `named{res}` above with
`named{tutorials:ui:res}` to use the files in the Storm distribution instead.

In order to load an image from file into our program we will make use of the
[loadImage](md:/Library_Reference/Graphics_Library/) function. Looking at the function we see that
the parameter is a `Url` object representing a path to the file and that it returns an
[stormname:graphics.Image]. Unfortunately the draw function in the `Graphics` object expects a
[stormname:ui.Bitmap] not an `Image`. As such, we need to convert the `Image` to a `Bitmap` before
we can draw it. The reason for this is that a `Bitmap` is a resource that is stored on the GPU ready
to be rendered, while an `Image` is always in the RAM of the CPU. For easy access, we store the
`Bitmap`s in a `Map` in the `ChessPainter` class. The key is the filename of the image, and the
value is the `Bitmap` that we can draw. Conveniently, we can use the `title` member of the `Url`
object to retrieve the name of each file without the file extension.

As such, we add the following member to the `ChessPainter`:

```bsclass
Str->Bitmap images;
```

We then load the images in the end of the constructor as follows:

```bsstmt
Url imgPath = resUrl(); 
for (child in imgPath.children()) {
    images.put(child.title, Bitmap(child.loadImage));
}
```

Drawing the images to the window is similar to what we have done in the previous parts. We modify
the `render` function of our painter class to call the `draw` function from the `Graphics` class to
draw the `Bitmap`. We also provide a rectangle to tell it that it should stretch the bitmap to fit
the provided rectangle. There are a number of overloads of the `draw` function for drawing bitmaps
for other situations as well. For example, if we wished to avoid scaling alltogether. As previously
stated the full functionality of a chess board will not be implemented, so we will simply draw the
pieces in their correct starting places. To do this, we first create a member variable `order` that
contains the order of the pieces:

```bsstmt
Str[] order;
```

We initialize the array with the correct values inside the `init` block of the `init` function:

```bsstmt
order = ["r", "n", "b", "q", "k", "b", "n", "r"];
```

In the `render` function of the `ChessPainter` class we then iterate throgh the columns of the chess
board and draw the appropriate images in the top and bottom rows. We use the index of the loop to
get the correct piece and append 'b' or 'w' depending on if it is the black or white piece. This
string is then used to fetch the correct `Bitmap` from the `images` container. These are then drawn
to the screen in a `Rect` using the `Graphics` objects `draw` function similarly to the previous
steps.

```bsstmt
for (Nat x = 0; x < 8; x++) {
    Str blackPiece = order[x] + "b";
    g.draw(images.get("pb"), Rect(Point(squareSz.w * x.float, squareSz.h), squareSz));
    g.draw(images.get(blackPiece), Rect(Point(squareSz.w * x.float, squareSz.h * 0), squareSz));

    Str whitePiece = order[x] + "w";
    g.draw(images.get("pw"), Rect(Point(squareSz.w * x.float, squareSz.h * 6), squareSz));
    g.draw(images.get(whitePiece), Rect(Point(squareSz.w * x.float, squareSz.h * 7), squareSz));
}
```

Putting it all together results in the below code that draws the chessboard with the images of the
pieces on their starting positions:

```bs
use ui;
use graphics;
use core:io;
use core:geometry;
use lang:bs:macro;

frame ChessVisualizer {
    layout Grid grid {
        Window chessBoard { minHeight: 500; minWidth: 500; }

    }
    
    ChessPainter boardPainter;
    
    init() {
        init("ChessVisualizer", Size(500, 500)) {}
        chessBoard.painter(boardPainter);

        create();
    }

}
    
class ChessPainter extends Painter {
    SolidBrush darkBrush;
    SolidBrush blackBrush;

    Text[] rowLabels;
    Text[] colLabels;

	Str[] order;

    Str->Bitmap images;

    init() {
        init() {
            blackBrush(black);
            darkBrush(Color(118, 150, 86));
			order = ["r", "n", "b", "q", "k", "b", "n", "r"];
        }
        
        bgColor = Color(238, 238, 210);
        
        for (row in ["8", "7", "6", "5", "4", "3", "2", "1"]) {
            rowLabels << Text(row, defaultFont);
        }
        
        for (col in ["a", "b", "c", "d", "e", "f", "g", "h"]) {
            colLabels << Text(col, defaultFont);
        }

		Url imgPath = resUrl();
		for (child in imgPath.children()) {
			print(child.title);
			images.put(child.title, Bitmap(child.loadImage));
		}
    }
    
    Bool render(Size me, Graphics g) {      
        Size squareSz = me / 8;
        
        for (Nat y = 0; y < 8; y++) {
            for (Nat x = 0; x < 8; x++) {
                if((x + y) % 2 == 1) {
                    g.fill(Rect(Point(squareSz.w * x.float, squareSz.h * y.float), squareSz), darkBrush);
                }
            }
        }

        for (i, rowLabel in rowLabels) {
            g.draw(rowLabel, blackBrush, Point(0, squareSz.h * i.float));
        }
        
        for (i, colLabel in colLabels) {
            g.draw(colLabel, blackBrush, Point(squareSz.w * (i + 1).float - 1, me.h) - colLabel.size);
        }

		for (Nat x = 0; x < 8; x++) {
			Str blackPiece = order[x] + "b";
			g.draw(images.get("pb"), Rect(Point(squareSz.w * x.float, squareSz.h), squareSz));
			g.draw(images.get(blackPiece), Rect(Point(squareSz.w * x.float, squareSz.h * 0), squareSz));

			Str whitePiece = order[x] + "w";
			g.draw(images.get("pw"), Rect(Point(squareSz.w * x.float, squareSz.h * 6), squareSz));
			g.draw(images.get(whitePiece), Rect(Point(squareSz.w * x.float, squareSz.h * 7), squareSz));
		}
        
        return false; 
    }
}

Url resUrl() {
    if (url = named{res}.url) {
        return url;
    } else {
        throw InternalError("Expected the package 'res' to be non-virtual.");
    }
}

void main(){
    ChessVisualizer cv;
    cv.waitForClose();
}
```


Adding a ListView to the Window
-------------------------------

Now that we can draw the chess board, it would also be helpful if we could add a UI element that
shows the moves made in Portable Game Notation (PGN) that is frequently used to record chess games.
In order to do so we want a scrollable list with three of columns, and the ability to highlight a
row to indicate the current move. Thankfully there is such a thing, called [stormname:ui.ListView].

As mentioned before, we will focus on the UI elements in this tutorial, so we will use this example
to illustrate how a `ListView` works without actually implementing the chess moves themselves on the
board. This will simply show how to set up a listview and how to manipulate it.

Adding a `ListView` to our frame is straight forward, we add it to the grid layout as we did with
our `Window` previously. Here we initialize the `ListView` with the column headers "Nr", "White" and
"Black". We also specify a minimum height and width that fits with our other window.

```bsfragment:ui.SWindowBody:use=ui
layout Grid {
    Window chessBoard { minHeight: 500; minWidth: 500; }
    ListView moveList(["Nr", "White", "Black"]) { minHeight: 500; minWidth: 150;}
}
```

A real application should include the ability to load games. However, to keep the tutorial simple we
will simply add moves from a specific game manually. The behaviour we want from the `ListView` is
that the user should only be able to select one move at a time and that the first move should be
highlighted by default. `ListView`s have a member `add` that adds a single row to the `ListView`. It
also has the member `multiSelect` that can be used to enable/disable the multiselect feature of the
`ListView`. Finally, it has a member `selection` that controls which row(s) are currently selected
and highlighted. Thus we use them to initialize the `ListView` with the correct settings in the
`init` function of the `ChessVisualizer` class in the manner as shown below:

```bsfragment:SFile:use=ui
frame ChessVisualizer {
    layout Grid grid {
        Window chessBoard { minHeight: 500; minWidth: 500; }
        ListView moveList(["Nr", "White", "Black"]) { minHeight: 500; minWidth: 150;}
    }
    
    ChessPainter boardPainter;
    
    init() {
        init("ChessVisualizer", Size(500, 500)) {}
        chessBoard.painter(boardPainter);

        moveList.add(["1." , "e4"   , "c6"]);
        moveList.add(["2." , "d4"   , "d5"]);
        moveList.add(["3." , "Nd2"  , "dxe4"]);
        moveList.add(["4." , "Nxe4" , "Nd7"]);
        moveList.add(["5." , "Ng5"  , "Ngf6"]);
        moveList.add(["6." , "Bc4"  , "e6"]);
        moveList.add(["7." , "Qe2"  , "Nb6"]);
        moveList.add(["8." , "Bb3"  , "h6"]);
        moveList.add(["9." , "N5f3" , "c5"]);
        moveList.add(["10.", "Bf4"  , "Bd6"]);
        moveList.add(["11.", "Bg3"  , "Qe7"]);
        moveList.add(["12.", "dxc5" , "Bxc5"]);
        moveList.add(["13.", "Ne5"  , "Bd7"]);
        moveList.add(["14.", "Ngf3" , "Nh5"]);
        moveList.add(["15.", "O-O-O", "Nxg3"]);
        moveList.add(["16.", "hxg3" , "O-O-O"]);
        moveList.add(["17.", "Rh5"  , "Be8"]);
        moveList.add(["18.", "Rxd8+", "Kxd8"]);
        moveList.add(["19.", "Qd2+" , "Bd6"]);
        moveList.add(["20.", "Nd3"  , "Qc7"]);
        moveList.add(["21.", "g4"   , "Kc8"]);
        moveList.add(["22.", "g5"   , "Bf8"]);
        moveList.add(["23.", "Rh4"  , "Kb8"]);
        moveList.add(["24.", "a4"   , "Be7"]);
        moveList.add(["25.", "a5"   , "Nd5"]);
        moveList.add(["26.", "Kb1"  , "Bd8"]);
        moveList.add(["27.", "a6"   , "Qa5"]);
        moveList.add(["28.", "Qe2"  , "Nb6"]);
        moveList.add(["29.", "axb7" , "Bxg5"]);
        moveList.add(["30.", "Nxg5" , "Qxg5"]);
        moveList.add(["31.", "Rh5"  , "Qf6"]);
        moveList.add(["32.", "Ra5"  , "Bc6"]);
        moveList.add(["33.", "Nc5"  , "Bxb7"]);
        moveList.add(["34.", "Nxb7" , "Kxb7"]);
        moveList.add(["35.", "Qa6+" , "Kc6"]);
        moveList.add(["36.", "Ba4+" , "Kd6"]);
        moveList.add(["37.", "Qd3+" , "Nd5"]);
        moveList.add(["38.", "Qg3+" , "Qe5"]);
        moveList.add(["39.", "Qa3+" , "Kc7"]);
        moveList.add(["40.", "Qc5+" , "Kd8"]);
        moveList.add(["41.", "Rxa7" , "1-0"]);

        moveList.multiSelect = false;
        moveList.selection = 0;

        create();
    }
}
```

The program now shows the chessboard to the left and a scrollable `ListView` of the moves of the
game to the right. The `ListView` supports functionality for scrolling, clicks and moving up and
down with the arrow keys out of the box. The next step illustrates how it is possible to manipulate
the selection variable by using separate buttons for the user's convenience.


Adding buttons to a Window
--------------------------

In this step we will add 4 buttons to the window. One moves to the first row of the `ListView`, one
moves to the last row, one that moves forward one row, and one that moves backwards one row. To
achieve this, we need to consider two things. First, how the buttons will be placed in the frame,
and second how to respond to button click events.

We choose to place the 4 buttons under the window that represents the chessboard. As such the grid
will be divided into 5 columns and two rows. The chessboard will span the 4 leftmost columns of the
first row. The `ListView` will span the last column in the first row. The buttons will each have one
of the columns in order from the left in the second row. Additionally we want to ensure that
everything scales with the window when it resized. The exception is that we wish that the buttons
are always the same height regardless of the window size.

To achieve this, we first specify that columns 0-4 should be expanded to fit the available width.
Similarly, we also specify that column 0 should expand to fit the available height. We then update
the lines that initialize the `chessBoard` and `moveList`. We specify that the `chessBoard` should
span 4 columns by adding `colspan: 4;`. We then place the `moveList` in the appropriate column by
adding `col: 4;`. Otherwise, it would end up in column 1, partially obscuring the chess board.
Finally, we add a `nextLine;` statement so that the four buttons will automatically be positioned in
row 1, columns 0-3.

```bsfragment:ui.SWindowBody:use=ui
layout Grid {
    expandCol: 0;
    expandCol: 1;
    expandCol: 2;
    expandCol: 3;
    expandCol: 4;
    expandRow: 0;
    Window chessBoard { colspan: 4; minHeight: 500; minWidth: 500; }
    ListView moveList(["Nr", "White", "Black"]) { col: 4; minHeight: 500; minWidth: 150;}
    nextLine;
    Button a("|<"){}
    Button b("<") {}
    Button c(">") {}
    Button d(">|"){}
}
```

With this change it is now be possible to resize the window with all the UI elements automatically
resizing themselves. Our rendered game board also adapts to the window since we used the `Size`
variable to determine the size and placement of various things in the render function.

What remains is to associate the buttons with logic in our programs. This is achieved by setting the
`onClick` member to a function of our choice. As such, we first need to define functions that modify
the selection in the `ListView`. These will be implemented as member functions in the
`ChessVisualizer` class.

One small note is that the following functions maintain a member variable that represents the
currently hightlighted row. This is not strictly necessary since we could ask the `ListView` using
the `selection` member directly. This choice was made to illustrate how we can listen for changes to
the selection in the `ListView` itself. Similarly to buttons, the `ListView` has a member `onSelect`
that can be set to a function. This function accepts two parameters. The first represents the row
that the event regards. The second parameter states whether the row is now selected or not. When we
select a new row, any previously selected rows will be deselected automatically. For our intents and
pruposes we only care about updating the row to highlight when we press a new row. This can be
implemented as follows:

```bs
void onSelect(Nat row, Bool selected) {
    if (selected) {
        currentHighlight = row;
    }
}
```

In order to associate the ListView with this new behaviour we assign the `onSelect` method to our
new function in the `init` function of the `ChessVisualizer` class:

```bsstmt
moveList.onSelect = &this.onSelect;
```

The functions for the buttons are simple to implement. They ensure that the highlight is not moved
outside the range of the `ListView` when stepping, or simply sets it to the last or first index:

```bs
void highlightFirst() {
    currentHighlight = 0;
    moveList.selection = currentHighlight;
}

void highlightLast() {
    currentHighlight = moveList.count - 1;
    moveList.selection = currentHighlight;
}

void highlightNext() {
    if (currentHighlight + 1 < moveList.count) {
        currentHighlight++;
        moveList.selection = currentHighlight;
    }
}

void highlightPrevious() {
    if (currentHighlight > 0) {
        currentHighlight--;
        moveList.selection = currentHighlight;
    }
}
```

In order to associate the buttons with the functionality of these functions, we assign to the
`onClick` method in the `init`-function of the `ChessVisualizer` class for each corresponding button:

```bsstmt
a.onClick = &this.highlightFirst;
b.onClick = &this.highlightPrevious;
c.onClick = &this.highlightNext;
d.onClick = &this.highlightLast;
```

Putting it all together results in the code below, combined with the custom painter class. This implements an
application with a chessboard, a list of moves, and buttons to step through the list of moves with a
highlight indicating the current move.

```bs
use ui;
use graphics;
use layout;
use core:io;
use core:geometry;
use lang:bs:macro;

frame ChessVisualizer {
    layout Grid grid {
        expandCol: 0;
        expandCol: 1;
        expandCol: 2;
        expandCol: 3;
        expandCol: 4;
        expandRow: 0;
        Window chessBoard { colspan: 4; minHeight: 500; minWidth: 500; }
        ListView moveList(["Nr", "White", "Black"]) { col: 4; minHeight: 500; minWidth: 150;}
        nextLine;
        Button a("|<"){}
        Button b("<") {}
        Button c(">") {}
        Button d(">|"){}
    }
    
    ChessPainter boardPainter;
    Nat currentHighlight;
    
    init() {
        init("ChessVisualizer", Size(500, 500)) {}
        chessBoard.painter(boardPainter);

        moveList.multiSelect = false;
        moveList.add(["1." , "e4"   , "c6"]);
        moveList.add(["2." , "d4"   , "d5"]);
        moveList.add(["3." , "Nd2"  , "dxe4"]);
        moveList.add(["4." , "Nxe4" , "Nd7"]);
        moveList.add(["5." , "Ng5"  , "Ngf6"]);
        moveList.add(["6." , "Bc4"  , "e6"]);
        moveList.add(["7." , "Qe2"  , "Nb6"]);
        moveList.add(["8." , "Bb3"  , "h6"]);
        moveList.add(["9." , "N5f3" , "c5"]);
        moveList.add(["10.", "Bf4"  , "Bd6"]);
        moveList.add(["11.", "Bg3"  , "Qe7"]);
        moveList.add(["12.", "dxc5" , "Bxc5"]);
        moveList.add(["13.", "Ne5"  , "Bd7"]);
        moveList.add(["14.", "Ngf3" , "Nh5"]);
        moveList.add(["15.", "O-O-O", "Nxg3"]);
        moveList.add(["16.", "hxg3" , "O-O-O"]);
        moveList.add(["17.", "Rh5"  , "Be8"]);
        moveList.add(["18.", "Rxd8+", "Kxd8"]);
        moveList.add(["19.", "Qd2+" , "Bd6"]);
        moveList.add(["20.", "Nd3"  , "Qc7"]);
        moveList.add(["21.", "g4"   , "Kc8"]);
        moveList.add(["22.", "g5"   , "Bf8"]);
        moveList.add(["23.", "Rh4"  , "Kb8"]);
        moveList.add(["24.", "a4"   , "Be7"]);
        moveList.add(["25.", "a5"   , "Nd5"]);
        moveList.add(["26.", "Kb1"  , "Bd8"]);
        moveList.add(["27.", "a6"   , "Qa5"]);
        moveList.add(["28.", "Qe2"  , "Nb6"]);
        moveList.add(["29.", "axb7" , "Bxg5"]);
        moveList.add(["30.", "Nxg5" , "Qxg5"]);
        moveList.add(["31.", "Rh5"  , "Qf6"]);
        moveList.add(["32.", "Ra5"  , "Bc6"]);
        moveList.add(["33.", "Nc5"  , "Bxb7"]);
        moveList.add(["34.", "Nxb7" , "Kxb7"]);
        moveList.add(["35.", "Qa6+" , "Kc6"]);
        moveList.add(["36.", "Ba4+" , "Kd6"]);
        moveList.add(["37.", "Qd3+" , "Nd5"]);
        moveList.add(["38.", "Qg3+" , "Qe5"]);
        moveList.add(["39.", "Qa3+" , "Kc7"]);
        moveList.add(["40.", "Qc5+" , "Kd8"]);
        moveList.add(["41.", "Rxa7" , "1-0"]);
        moveList.onSelect = &this.onSelect;
        highlightFirst();
        
        a.onClick = &this.highlightFirst;
        b.onClick = &this.highlightPrevious;
        c.onClick = &this.highlightNext;
        d.onClick = &this.highlightLast;
        
        create();
    }

    void onSelect(Nat row, Bool selected) {
        if(selected){
            currentHighlight = row;
        }
    }
    
    void highlightFirst() {
        currentHighlight = 0;
        moveList.selection = currentHighlight;
    }

    void highlightLast() {
        currentHighlight = moveList.count - 1;
        moveList.selection = currentHighlight;
    }

    void highlightNext() {
        if (currentHighlight + 1 < moveList.count){
            currentHighlight++;
            moveList.selection = currentHighlight;
        }
    }

    void highlightPrevious(){
        if (currentHighlight > 0) {
            currentHighlight--;
            moveList.selection = currentHighlight;
        }
    }
}
```

Adding Menus to the Window
--------------------------

The last thing in the tutorial is to add a menu to the frame, and using the menu to show license
information for the program.

The two things we want to add to achieve this is a [stormname:ui.MenuBar] and a
[stormname:ui.PopupMenu]. We want the `MenuBar` to display the text "Help" and the dropdown menu to
contain an item with the text "About..." that shows license information. We first create the about
menu by initializing a `PopupMenu`, setting the menu text to "About" and associating that choice
with a function called `onAbout` that will show the user the license of the program. The `MenuBar`
itself then needs to be initialized with the text "Help" and then adding the previously created
`PopupMenu` to it. Finally, we set the `menu` member of the `Frame` to our created `MenuBar` to show
the menu in the window.

```bsstmt
PopupMenu helpMenu;
helpMenu << Menu:Text(mnemonic("_About..."), &this.onAbout());

MenuBar m;
m << Menu:Submenu(mnemonic("_Help"), helpMenu);

menu = m;
```

The only thing that remains is to implement the `onAbout` function. We want this function to make
use of the `.license` file functionality available in Storm, we start by creating the `.license`
files themselves. For this particular implementation we want a `.license` file for the program
itself, a `.version` file that indicates what version of the program it is. In this case, we also
need to include the license and attribution for the images of the chess pieces we have used. We
create a separate `.license` file for that. These files should all be placed next to the `.bs` file
as indicated below:

```
ui
├─ chess.bs
├─ CHESS.license
├─ CHESS_VERSION.version
├─ IMAGES.license
└─ res
   ├─ bb.png
   ├─ bw.png
   ├─ kb.png
   ├─ kw.png
   ├─ nb.png
   ├─ nw.png
   ├─ pb.png
   ├─ pw.png
   ├─ qb.png
   ├─ qw.png
   ├─ rb.png
   └─ rw.png
```

Starting with the `.version` file since it is the easiest. It is a file that contains one thing, the
current version number. There is nothing more to it than that. In our case, we can simply enter
`0.1.0`. Any version compliant with *semantic versioning* is supported.

The `.license` file is straight forward as well, but the file contains more information. The first
line in the file is interpreted as a summary of the license that is used. The second line contains a
list of authors. The rest of file is the full license text of the license.

After creating the files, we can implement the `onAbout` function that will make use of the
information. To simplify our implementation, we use the `showLicenseDialog` function that is
included in the UI library. It traverses the [name tree](md:/Language_Reference/Storm/The_Name_Tree)
to find all licenses that are currently used and displays them. As such, this automatically compiles
a list of licenses that are used by the program, and the list will be up-to-date if we use additional
libraries in the future.

To show the license of the current program in a more prominent place, the `showLicenseDialog`
function accepts the license of the current program as a separate parameter. To get the license we
created, we use the `named{}` syntax to get the named entity that corresponds to the `.license` file
we just created. Similarly, to show version information, we need to retrieve the information from
the `.version` file we created. Finally, we also provide information about the name of the program,
and the primary author. In summary, we implement the `onAbout` function as follows. Note that we
need to add `use lang:bs:macro;` to the top of the file to use the `named{}` syntax:

```bsclass:use=lang.bs.macro
private void onAbout() {
    var license = named{CHESS};
    var version = named{CHESS_VERSION};
    showLicenseDialog(this, ProgramInfo("ChessVisualizer", "Simon Ahrenstedt", version.version, license));
}
```

This concludes the brief tutorial of various UI elements that exist in Storm. The code for the
complete program follows below and is also available in the directory `root/tutorials/ui` in the
Storm release as mentioned in the beginning of the tutorial.

```bs
use ui;
use graphics;
use layout;
use core:io;
use core:geometry;
use lang:bs:macro;

frame ChessVisualizer {

	layout Grid {
		expandCol: 0;
		expandCol: 1;
		expandCol: 2;
		expandCol: 3;
		expandCol: 4;
		expandRow: 0;
		border: 10, 10;
		Window chessBoard { colspan: 4; minHeight: 500; minWidth: 500; }
		ListView moveList(["Nr", "White", "Black"]) { col: 4; minHeight: 500; minWidth: 150;}
		nextLine;
		Button a("|<") {}
		Button b("<") {}
		Button c(">") {}
		Button d(">|") {}
	}

	ChessPainter boardPainter;
	Nat currentHighlight;

	init() {
		init("ChessVisualizer", Size(500, 500)) {}
		chessBoard.painter(boardPainter);

		moveList.multiSelect = false;
		moveList.onSelect = &this.onSelect;
		moveList.add(["1." , "e4"	, "c6"]);
		moveList.add(["2." , "d4"	, "d5"]);
		moveList.add(["3." , "Nd2"	, "dxe4"]);
		moveList.add(["4." , "Nxe4" , "Nd7"]);
		moveList.add(["5." , "Ng5"	, "Ngf6"]);
		moveList.add(["6." , "Bc4"	, "e6"]);
		moveList.add(["7." , "Qe2"	, "Nb6"]);
		moveList.add(["8." , "Bb3"	, "h6"]);
		moveList.add(["9." , "N5f3" , "c5"]);
		moveList.add(["10.", "Bf4"	, "Bd6"]);
		moveList.add(["11.", "Bg3"	, "Qe7"]);
		moveList.add(["12.", "dxc5" , "Bxc5"]);
		moveList.add(["13.", "Ne5"	, "Bd7"]);
		moveList.add(["14.", "Ngf3" , "Nh5"]);
		moveList.add(["15.", "O-O-O", "Nxg3"]);
		moveList.add(["16.", "hxg3" , "O-O-O"]);
		moveList.add(["17.", "Rh5"	, "Be8"]);
		moveList.add(["18.", "Rxd8+", "Kxd8"]);
		moveList.add(["19.", "Qd2+" , "Bd6"]);
		moveList.add(["20.", "Nd3"	, "Qc7"]);
		moveList.add(["21.", "g4"	, "Kc8"]);
		moveList.add(["22.", "g5"	, "Bf8"]);
		moveList.add(["23.", "Rh4"	, "Kb8"]);
		moveList.add(["24.", "a4"	, "Be7"]);
		moveList.add(["25.", "a5"	, "Nd5"]);
		moveList.add(["26.", "Kb1"	, "Bd8"]);
		moveList.add(["27.", "a6"	, "Qa5"]);
		moveList.add(["28.", "Qe2"	, "Nb6"]);
		moveList.add(["29.", "axb7" , "Bxg5"]);
		moveList.add(["30.", "Nxg5" , "Qxg5"]);
		moveList.add(["31.", "Rh5"	, "Qf6"]);
		moveList.add(["32.", "Ra5"	, "Bc6"]);
		moveList.add(["33.", "Nc5"	, "Bxb7"]);
		moveList.add(["34.", "Nxb7" , "Kxb7"]);
		moveList.add(["35.", "Qa6+" , "Kc6"]);
		moveList.add(["36.", "Ba4+" , "Kd6"]);
		moveList.add(["37.", "Qd3+" , "Nd5"]);
		moveList.add(["38.", "Qg3+" , "Qe5"]);
		moveList.add(["39.", "Qa3+" , "Kc7"]);
		moveList.add(["40.", "Qc5+" , "Kd8"]);
		moveList.add(["41.", "Rxa7" , "1-0"]);
		currentHighlight = 0;
		highlightFirst();

		a.onClick = &this.highlightFirst;
		b.onClick = &this.highlightPrevious;
		c.onClick = &this.highlightNext;
		d.onClick = &this.highlightLast;

		PopupMenu helpMenu;
		helpMenu << Menu:Text(mnemonic("_About..."), &this.onAbout());

		MenuBar m;
		m << Menu:Submenu(mnemonic("_Help"), helpMenu);

		menu = m;

		create();
	}

	private void onAbout() {
		var license = named{CHESS};
		var version = named{CHESS_VERSION};
		showLicenseDialog(this, ProgramInfo("ChessVisualizer", "Simon Ahrenstedt", version.version, license));
	}

	void onSelect(Nat row, Bool selected) {
		if(selected) {
			currentHighlight = row;
		}
	}

	void highlightFirst() {
		currentHighlight = 0;
		moveList.selection = currentHighlight;
	}

	void highlightLast() {
		currentHighlight = moveList.count - 1;
		moveList.selection = currentHighlight;
	}

    void highlightNext() {
        if (currentHighlight + 1 < moveList.count){
            currentHighlight++;
            moveList.selection = currentHighlight;
        }
    }

    void highlightPrevious(){
        if (currentHighlight > 0) {
            currentHighlight--;
            moveList.selection = currentHighlight;
        }
    }
}

Url resUrl() {
	if(url = named{res}.url) {
		return url;
	} else {
		throw InternalError("Expected the package 'res' to be non-virtual.");
	}
}

class ChessPainter extends Painter {
	SolidBrush blackBrush;
	SolidBrush whiteBrush;
	SolidBrush darkBrush;

	Text[] rowLabels;
	Text[] colLabels;

	Str[] order;

	Str->Bitmap images;

	init() {
		init() {
			blackBrush(black);
			darkBrush(Color(118, 150, 86));
			whiteBrush(white);
			order = ["r", "n", "b", "q", "k", "b", "n", "r"];
		}

		bgColor = Color(238, 238, 210);

		for (row in ["8", "7", "6", "5", "4", "3", "2", "1"]) {
			rowLabels << Text(row, defaultFont);
		}

		for (col in ["a", "b", "c", "d", "e", "f", "g", "h"]) {
			colLabels << Text(col, defaultFont);
		}

		Url imgPath = resUrl();
		for (child in imgPath.children()) {
			print(child.title);
			images.put(child.title, Bitmap(child.loadImage));
		}
	}

	Bool render(Size me, Graphics g) {
		Size squareSz = me / 8;

		for (Nat y = 0; y < 8; y++) {
			for (Nat x = 0; x < 8; x++) {
				if((x + y) % 2 == 1) {
					g.fill(Rect(Point(squareSz.w * x.float, squareSz.h * y.float), squareSz), darkBrush);
				}
			}
		}

		for (i, rowLabel in rowLabels) {
			g.draw(rowLabel, blackBrush, Point(0, squareSz.h * i.float));
		}

		for (i, colLabel in colLabels) {
			g.draw(colLabel, blackBrush, Point(squareSz.w * (i + 1).float - 1, me.h) - colLabel.size);
		}

		for (Nat x = 0; x < 8; x++) {
			Str blackPiece = order[x] + "b";
			g.draw(images.get("pb"), Rect(Point(squareSz.w * x.float, squareSz.h), squareSz));
			g.draw(images.get(blackPiece), Rect(Point(squareSz.w * x.float, squareSz.h * 0), squareSz));

			Str whitePiece = order[x] + "w";
			g.draw(images.get("pw"), Rect(Point(squareSz.w * x.float, squareSz.h * 6), squareSz));
			g.draw(images.get(whitePiece), Rect(Point(squareSz.w * x.float, squareSz.h * 7), squareSz));
		}

		return false;
	}
}

void main() {
	ChessVisualizer cw;
	cw.waitForClose();
}
```
