Create a UI with BJS 2D GUI in 3 simple lessons (New #Lesson2 available)

Hello BJS users and welcome to newcomers,

In BJS, there’s this awesome ability to create a fully integrated UI straight from BJS. It is continueously worked and improved and just recently there is also an all new GUI Editor to help with creating a great and fully integrated GUI with BJS.

However, as expained in the doc, the BJS 2D GUI has some pros and cons and it’s not always easy to get a grasp of it, despite the very good and comprehensive doc that can be found here.

So, following some questions posted on the release of one of my demos that can be found here, I decided to make a quick tutorial that will hopefully help you reproduce this GUI (SS below) and more importantly get a better understanding of the specificities of the BJS 2D GUI to create your own amazing GUI.

We’ll do this in 3 simple lessons and will start with lesson #3 (nah, just joking :wink:… we’ll start with lesson #1 of course and we’ll rush through:

  • The advancedTexture full screen UI layer
  • The grid
  • The stackpanel

So, here goes:

#Lesson 1:
Here is the link to the PG for lesson 1.
Use this PG and the ‘inspector’ to make twists and tests and re-run it to see what it does.

The advancedTexture (FS layer):
To begin with creating a BJS 2D GUI, you will need to create an ‘advancedTexture’ element/layer. advancedTexture(layer) as its name suggests does one thing: It creates a new layer that by default displays on top of all other layers in the scene with just a couple of exceptions (among which the 3D GUI layer and the PP, but we can come back to this later).

There are different ways to use the advancedTexture but for a start, let’s stick with the most common: the full-screen mode. Think of the advancedTexture as a plane in 2D front/fixed view only that covers the entire scene/canvas. Or, if compared to HTML, think of it like a div of a 100% with absolute position and a zero at top left, top aligned, left aligned that covers the entire window/canvas.

Next thing you want to do (unless you have just one or two controls) is to divide this into blocks/sections/containers and gain some control over how this will display on resize.
You can assign controls directly to your advancedTexture but when you want more control and the ability to create areas for say a left menu, a top menu, etc…

For this, you will first want to create a grid.

The grid:
Think of the grid like a table in html or a serie of divs/containers with relative position. So you can set a grid to your advancedTexture but it’s also interesting to mention that you can also set a grid for your containers (i.e the stackpanel) but we’ll see to this later.

Essentially when you create a grid, you will set a number of rows (vertically) and a number of columns (horizontally). Each column and row defined will stack in order starting from the top left.

For controlling the grid/cells size, same as for your containers and controls, you have 2(3) options:

  1. The bjs value, generally ranging from 0 to 1 (with exceptions). A value of 1 is equal to full or 100%. A value of 0.5 is equal to half the size or 50%.

  2. The value in pixels. Self-explanatory, the value in pixels will create a fixed size expressed in pixels. Though it’s important to state and understand that this ‘fixed’ size’ is not necessarly always the size in (true) pixels. Since (by default), the canvas and the advancedTexture layer resizes depending on screen/window size, the size of containers and controls even when expressed in pixels will also adapt to the size of the canvas/window. There are some options to control how the advancedTexture layer and children display on resize (such as idealWidth or idealHeight and a number other but we’ll come back to this later). For now, simply understand that a size expressed in pixels should be used to maintain a fixed/relative size.

  3. For your containers and controls, the value expressed in percentage is similar to what you can do with HTML/css. It will calculate a percentage from the size of your container/parent and will keep this percentage on resize. In other words, this is the way to make it dynamic/responsive and is also basically a replacement for the bjs value from #1 above. So, in short both #1 and #3 will generally work the same.

First thing to understand when creating your grid is that setting a value of 1 or 100% to a row or column WITHOUT the parameter of ‘true’ will simply fill-in the remaining space. Understand by keeping (at least) one column and row of the grid without the parameter of ‘true’ never overlaps cells but simply covers the remaining space between your ‘fixed’ rows and columns.

I.E.: Say you have 3 columns and you want to define a fixed size for the left column (for a left menu) with a size expressed in pixels (200px) and also have a right menu in the 3rd column of a size of 1/5 of the canvas width, with a value of ‘20%’ - then the middle column will be the one with relative sizing (or fill-in size) and can be given a value of 1. In this case, the value of 1 set without the parameter of ‘true’ should be read: 1 (or 100%) MINUS columns 1 and 3 sizes (== the remaining size).

This is how to create a grid:

var grid = new BABYLON.GUI.Grid();

    grid.addColumnDefinition(240,true);
    grid.addColumnDefinition(1);
    grid.addColumnDefinition(240,true);
    grid.addRowDefinition(140,true);
    grid.addRowDefinition(1);
    grid.addRowDefinition(200,true);

advancedTexture.addControl(grid);

The above will create a grid with :

A left column of a fixed size (true) of 240px.
A middle column of the size of the remaining space.
A right (third) column of a size of 240px.

A first row of a fixed size of 140px.
A middle row of the size of the remaining space.
A bottom (third) row of a fixed size of 200px.

Once you have a grid on your advancedTexture you can:

  1. assign control to a cell - or -
  2. assign a stackpanel to a cell, to gain more control or create a second level of imbrication.

The stackpanel:
By using a stackpanel in a grid (or straight in the advancedTexture), you can next:

  1. add a control to your stackpanel that’s in a grid cell - or -
  2. impricate another stackpanel in the stackpanel that’s in a grid cell (and so on)

This is in fact very similar to an imbrication of divs where each div/container imbricated will be relative to the parent.

For adding you grid to your advancedTexture, same as for adding your controls or containers to a grid, a container or the advancedTexture layer itself, you need to do this (edited,sry):

parent_i.addControl(element_i)

Where ‘element_i’ is the element (control or container) and ‘parent_i’ is the parent container/grid or advancedTexture layer.

Note that you cannot interact with an element (control or container) before it has been attached to the advancedTexture layer.

So you now have an advancedTexture full-screen UI with a grid splitting the UI in a number of cells defined by rows and columns.

Next step is to assign/imbricate a control or a container to a cell in your grid. This is done like this:

grid.addControl(element_i,0,1);

Where ‘element_i’ is the control or container to attach to the grid/cell.
Where the first number following is the row of the grid (row number 0).
Where the second number in parameters is the column of the grid (column number 1).

Tip: To make sure your grid is correct and resizes the way you want, add color to the cells or even better add a stackpanel with bg color to each.
Use the inspector to see and check the hierarchy of your gui elements. Also use the inspector to make changes in size or alignment to get a better understanding of how it works.
Take a tour of the comprehensive doc for the BJS 2D GUI and use the PG (with inspector) to try things.

In the next ‘lesson’, we’ll be adding controls to your grid and see to how we can master alignment, resize and imbrication.

Have fun trying things with the BJS GUI, don’t forget to read the doc and use the PG.
I’ll see ya soon,

16 Likes

Super useful!! cc @PirateJC

1 Like

Thanks for this encouraging comment. But you should first wait for the other lessons to come :wink:
Lesson #1 was the easy one and whilst working on #2 and knowing how broad this thing is, I’m wondering if I should have changed my title to ‘learn in five lessons’ :wink:
Anyways, I’m on it…

2 Likes

Call it “learn in n steps” and change n whenever you want :blush:

Awesome, can’t wait to read more

1 Like

Great work on the tutorial!

Great work @mawa. I’m through stage one and eagerly awaiting the next one. I must say, I couldn’t have understood it without the playground :slight_smile:

That’s why we have the PG and the sandbox. One of the greatest assets and advantage of BJS (in my opinion). Never forget to make good use of it. Lesson #2 is underway and will hopefully come tonight.
Meanwhile, I can only only encourage you to continue reading the doc and apply the ‘try, fail and try again’ process :smiley: making good use of BJS’s versatile capabilities and tools. Stay tuned…

1 Like

@mawa have you thought about submitting a PR to the docs of the tutorial, under Guided Learning?

1 Like

Thanks for your comment. I need this kind of encouraging comment while trying to have all this in just 3 lessons :wink: For now, let me just continue ‘struggle’ with it and bring on the next episode, and will see to this later :hourglass_flowing_sand:

2 Likes

Hi Maya, I’ve been struggling to figure out how to get the menu to open and close as opposed to staying open. https://playground.babylonjs.com/#9Z476P#119 I had a look in the other code you have too and can’t work out how to get the hamburger menu to function as it should. Maybe that is something in one of your lessons but I’ve been stuck trying to figure that for a good while.

Yes, It’s part of the upcoming lesson (apologies for delay). If you just wait a couple or a few hours more it will be right there for you to soak-up.
Else, simply look at the isVisible (true or false) parameter. That’s how I did it in this scenario.

Edit: Apologies to you and to all as #lesson2 will only be available from tomorrow. Cause is some domestic disturbance :wink: and also the time needed to make a comprehensive PG and explanation for this broad lesson.
I must have underestimated it a little (as usual) and I’m no good working in the evening :dizzy_face:
But will be back tomorrow (hopefully in the morning)

1 Like

This is so awesome!! I love how you explained the grid, which is by no means an easy control to use. Please continue sharing your knowledge!

1 Like

- - - LESSON #2 - - -

Thank you for following this topic and let’s move on with this.

Lesson #1 (above) was quite easy to write and I hope you did understand it and enjoyed discovering the BJS 2D GUI.

If not (no offense at all), feel free to post your questions and if I explained anything wrongly or badly, also feel free to comment and correct. I will certainly amend this part.

If things have gone right in Lesson #1, you should now have an ‘AdvancedDynamicTexture’ FS layer in your scene along with a ‘grid’ that resizes correctly (except may be for the mobile vertical format) and you should also have some ‘stackpanels’ assigned to your grid cells, yes? Awesome! So, let’s start with it.

TIP: Same as in #Lesson 1, this tutorial comes with a number of PGs. In these demonstrating PGs, look for the commented parts:

//–> For an exercise, for a part to comment or uncomment
///// * * * For a new section of instructions / example
///// * * * Lesson #x - For the start of an example or exercise
///// * * * END OF :: Lesson/exercise - For the end of this part

ADVISE: I also strongly advise that by taking this lesson, you open a couple new tabs to display the 2D GUI doc and the related PG examples.

I will not be going through all details, aspects, options and parameters for each control. In fact, most are already in the doc (and explained better than I ever will) and most also have a simple and comprehensive PG example you can play with. To further investigate a control or container, use the example PG from the doc. Believe me, when stuck, this is the best (and may be only) way to go. Don’t try too hard in my PGs reproducing the ‘Project Chair’ GUI or in your own GUI as it will build-up and become more complex. You will likely loose your time or loose your way :compass:.
With that said, let’s move on…

LESSON #2

In lesson #2, we are going to have a look at some of the properties we can set on a stackpanel, which will be the parent for all your imbricated controls and or panels (children).

We will also have a look at how to manage the display and design of these stackpanels and their children (controls and containers).

We’ll speak about alignment, sizing and panning that will be, in most cases, relative to its parent(s) and, in some cases, relative to its child/children.

But before we do that, I wanted to quickly expose here something that (I believe) is not thoroughly explained in the doc and which I find to be really great. It’s about the handling of colors in the BJS GUI.

If you simply look at the doc and related PGs, you might think that the colors used in the ‘advancedTexture’ 2D GUI are only opaque (Color3). I sometimes like to have my GUI displaying with alpha blending where you can just lightly see the scene in the background and this is how I discovered that the BJS GUI lets you do just that.


Alpha blending vs Colors with alpha:
The first thing that made me think that the BJS 2D GUI will eventually work with colors with alpha is that it already has the ‘alpha’.

You can set an ‘alpha’ on your containers and controls to blend the GUI element (control or container) into the scene. Something like this:

element_i.alpha = 0.5;

However, by doing this (say on a stackpanel), you will also apply this same alpha level to all children in this container. And this might just not be exactly what you want, thus…

Setting colors on your panels and controls:
You have a number of choices to color the background and content of your containers and controls. Let’s have a look at some of’em:

  1. You can enter a value of a pre-defined color i.e. “red”, “blue”, “black”, “green”, “yellow”. Don’t forget the quotes, single or double. Obviously, these preset colors have no alpha. These colors can also be entered straight as color for the bg or content of your GUI element, i.e:
panel.background = "white";
button.color = "black";
  1. You can enter HEX color values, i.e:
panel.background = "#FFFFFF";
button.color = "#0";

Don’t forget the quotes, single or double.

  1. You can enter a value of “transparent” (with quotes, single or double), making your background or content ‘transparent’. Since ‘transparent’ is ‘a color’ it’s not the same as setting it to ‘isVisible’ or ‘visibility’ and is also not the same as setting the ‘alpha’ to zero (element_i.alpha=0). Understand it’s a ‘transparent’ coloring of the element. It still has a 100% alpha yet with ‘a color’ of ‘transparent’. Therefor, all children of this ‘transparent’ container (or control) will still be visible and will also still keep with their own color and alpha.

button.background = ‘transparent’;

  1. You can enter a BJS Color3 value, i.e.: new BABYLON.Color3(0.2, 0.5, 0.3)
  2. You can enter RGB color values converted to BJS values, i.e: new BABYLON.Color3(26/255, 24/255, 20/255). Where the first number is R (red), the second is G (green), the third is B (blue and the divider is the max levels for an RGB color (255). The divider is applied here to return the BJS value that is set between 0 and 1. Although, it appears BJS also accepts negativ values (but that’s just another subject;). Note that there are also tools to convert a color. In the doc, look for ‘tools’.
    We’ll have a look at just the one we need below

For #4 and #5 above, we will first need to convert these colors to a hex string. And this is done like this (using the tool 'toHexString()’):

var mycolorbjs = new BABYLON.Color3(0.2, 0.5, 0.3).toHexString();
var mycolorrgb = new BABYLON.Color3(20/255, 50/255, 30/255).toHexString();

And now comes the interesting part. Rather than making your container with an ‘alpha level’ (alpha blending or ‘alpha’) that will apply to all children, you can enter a Color4 format value (read, with alpha). You can do so both for containers and controls, both for background and content.
I.E., you can enter:

  1. A HEX (HEX(a) or hexadecimal) color with alpha value. The last two numbers are the alpha (Don’t forget the quotes, single or double.), I.E.:

panel.background = “#FFFFFF99”;

  1. A BJS Color4 value, i.e.: new BABYLON.Color4(0.2, 0.5, 0.3, 0.8)
  2. An RGBA color converted to BJS value, i.e.: new BABYLON.Color4(26/255, 24/255, 20/255, 125/255)

Same as for the above #4 and #5 Color3 colors, for #6 and #7 Color4, you will also first need to convert these into a hex string, i.e.

panel.background = new BABYLON.Color4(20/255, 50/255, 30/255, 125/255).toHexString();

A HEX or HEX(a) color value can be entered straight (with quotes, single or double).

The ‘Play with colors PG’ for experiencing the above can be found here:


Experiencing with sizing, alignment and padding:

The first thing that’s important to understand here is just ‘high level’. Before entering the details of why such or such imbrication or instruction will work or not, I believe it’s important to take a time to speak about ‘#consistency’.

Many aspects of alignments, padding and sizing in BJS are very similar to css (read css v1) but then, some are different (else it wouldn’t be fun, would it :wink:

For those of you who where not born or not yet working at the time of the early days of css (lucky you :baby:, css v1 was much less forgiving/versatile towards using multiple methods and approaches (read disregarding #consistency in code/design) than it is now.

In particular, the way you’d apply padding (vs margin) on imbricated and relative to its parent elements (controls and containers) can rapidly make it so that any change made to the size or padding of an element that is above in the hierarchy (or sometimes below) will have a negativ impact on all children (and eventually parent). Without a method and without ‘#consistency’, changing a simple thing like the size of an icon or adding just one line of text to a textBlock can eventually turn into a nightmare and have you reorganize the entire design of your container and children.

My advise here is the same than when coding with HTML/css: Choose a method for how to apply alignment, padding, sizing and imbrication and stick to it. Typically, mixing things using alternatively ‘paddingTop’ and ‘paddingBottom’ on elements to create the spacing between elements is a dangerous approach. Would you have to remove or resize just one of’em, you’d likely be in for changing everything that’s relative to it.

When you build a UI (in either BJS or HTML/css), think first of what I call the 3Cs: ‘Consistency’, ‘Consistency’, ‘Consistency’ (Note: that’s my own version of the 3Cs :wink: The real one can be found on the Internet. In short, choose a method and try to apply it all the way through.

In fact, this aspect of alignment, sizing and padding could cover an entire lesson. Else, rather than reading a lot of text, there’s no better way to learn than experience it. Try, fail and try again.

So, just before starting writting #Lesson 3, I’m strongly thinking of making a side post with some ‘exercising’ PGs to let you twist and tweak sizing (height, width), panning, alignment (horizontalAlignment, verticalAlignment) and the instructions of ‘top’ and ‘left’. Which altogether constitute the main parameters that will have you master your design.

Meanwhile, you can still play along with this PG of an intermediate version reproducing the settings buttons and main panels for settings of my ‘Project Chair’ GUI. In this PG, I have added two footer buttons that are sharing a same stackpanel to display either ‘settings’ or ‘kbinfo’ information. Already have a look at it and feel free to leave your questions and we’ll return to this a bit later.

On a side note, whilst going through this tutorial and slowly building up this GUI, you will eventually notice some odd values given to a control, image or container, for sizing and panning. Such as a textBlock or image of an initial/native size of 48px that’s being given a height of ‘0px’ along with a ‘paddingTop’ of ‘-24px’ and a ‘paddingBottom’ of ‘-24px’ and this textBlock or image would still show with a height of… guess what? 48px.

There’s a small exercise in this PG for you to experience this:

With that said and whilst keeping this in mind for later, let’s just start with something simple…


For what is coming next, let’s first open this PG

and we’ll take a tour of…

Adding a control to your grid or stackpanel:

We are now going to add a top-menu button to our GUI.
It will be always displayed within the top-left cell of our grid (at grid 0,0) and by clicking/toggling it, we will simply show or hide all of the left menu elements (in column 0 underneith, to the far left). Similar to a ‘hamburger menu’ but without a folding/unfolding animation. At least, for now.

Since we only want to display a simple icon for this menu button (the menu icon), we don’t need a stackpanel for it and we’ll simply assign this control of an ‘imageOnlyButton’ straight to our top left grid cell.

Adding a control to a container or the advancedTexture is always done the same (see lesson #1):

parent_i.addControl(element_i);

Adding this control to a grid cell requires two more parameters (see lesson #1)

grid.addControl(element_i,0,1);

Note that there is one case where you do not have to pass these parameters of row and column.
By default, the unspecified row and column of your grid is at 0,0 (the top left row and column)

Since we want this top menu icon ‘imageOnlyButton’ control to cover the entire size of our grid cell (at row#0 and column#0), we don’t have to give it a width.

By default, the child, control or container, will cover 100% of the width of the parent (grid or container). Interestingly, in case of a stackpanel it will also take a height of 0(zero). So, speaking about a stackpanel, a panel with no instruction for its height will first not show. It is there, you can see it in the hierarchy. It covers 100% of the width of its parent (panel or grid cell); it simply has no height.

In fact, by giving no height to your stackpanel, this container will ‘automagically :magic_wand:resize with the height of its child/children (controls and/or panels). Keep this in mind when building your GUI and imbrications of panels and controls, because this can be very useful. But then, this can also cause lots of issues in a context where panels and controls are removed/hidden or changing size. :face_with_open_eyes_and_hand_over_mouth:
You can already experience and exercise this using this PG (at line 161)

Now, back to our menu button:


The SimpleButton, ImageWithCenterTextButton and ImageOnlyButton
Three of the most commonly used and most basic controls. Again, I encourage you to consult the comprehensive doc and try things using the associated PG example. Generally, there’s at least one for each control.

While the ’ SimpleButton’ can display text, the ‘ImageWithCenterTextButton’ can display both text and image, the ‘imageOnlyButton’ will display (as its name suggests)… well, an image or icon.
And this is what we want here for our menu button that will simply display a menu icon.

First thing we want to do is create this control and add it to our top-left grid cell.
Remember from #Lesson 1 that the ‘grid’ itself is already attached/added to the ‘advancedTexture’.

On a side note, you can actually add controls to a container that is not yet attached to its parent. By doing so, you would build what I call ‘a prefab’; By first adding your controls to the container and only next adding this container to the parent and the ‘advancedTexture’ layer. However, note that some actions cannot be taken until the control is added.

Back to our menu button creation:

var button = BABYLON.GUI.Button.CreateImageOnlyButton(“button”, “textures/icons/Open.png”);
grid.addControl(button);

Since this is an ‘ImageOnlyButton’, notice the additional parameter passed on creation.
The first parameter (that is common to all controls is optional but I strongly encourage you to use it), is the name of the control.
Why do I encourage you to give a name to your controls and containers? Open the PG and ‘the inspector’. From ‘the inspector’ unfold all from ‘GUI’ to see the hierarchy of your GUI and notice that a container or control without a name shows as ‘no name’. Amazing!:wink: Say you have a number of panels and even just a dozen of controls in there, it will already become hard to identify them :dizzy_face:

The second parameter that is specific to the ‘*image’ controls is the URL to load your image or icon. This ‘source’ URL can be changed later on, say depending on context/interaction, by calling on the ‘image.source’.

Updating the image source:

button.image.source = "textures/icons/Zoom.png";

Image sizing, padding and alignment

Having loaded our image source from the URL, we can see that our icon (called image) is eventually fitting our control size. And that’s a good thing and is pretty handy, isn’t it? :smiley:

On the other hand, you can possibly notice that your icon/image is looking blury. In which case, the reason is likely that the source size of your image that is here changed to actually fit your container/control is too small. Reducing the size of a bitmap image will not affect its quality. In fact, it can even improve it. However, if your icon or image is too small, it needs to enlarge to fit the size of your container or control. Since I’m essentially a designer, I’d like to mention here that scaling/enlarging a bitmap image (jpg or png) will also have a negativ impact on its quality. Pixels to show at a bigger size are not in the source and the image will have to ‘extrapolate’ to create this bigger size, thus adding blur and loss in the quality of the image. If your image/icon is too small (at native size), I can only advise you would edit it first and rescale it to the initial (or even better, maximum) required size for your UI.

With that said and here again, there are multiple ways (and ways that can be used in combination) to size and constrain your image part, control or container to your design needs. Things are never simple, are they? Else, it wouldn’t be fun :wink: On the other hand, if things were too simple, it would only mean that the BJS GUI is not as versatile. And believe me, it is :nerd_face:

Let’s have a quick look at how we can resize and add constraints for the display of our icon image, but first, let’s state here what we want to achieve:

  1. Obviously, we want our image/icon to show entirely and this, not depending on the size of the container on resize.
  2. Since an icon displayed without any margin/padding wouldn’t be nice, we will want to give it a little bit of a space around it.
  3. Since an icon that is displayed with irregular padding would also look a bit weird, we want to make sure our image always keeps within the center (horizontal and vertical alignment) of our control

This is where ‘padding’, sizing (‘height’ and ‘width’) and alignment (‘horizontalAlignment’ and ‘verticalAlignment’) come in.

For #1 above, the sizing, we are going to define a new size for our icon (either fixed or relative to the control). It will come in replacement of the default ‘fitTo’ (control/container) size of the image on import. This is instructed like this:

button.image.height = "72px";
button.image.width = "72px";

or this:

button.image.height = "80%";
button.image.width = "80%";

or that:

button.image.height = 0.8;
button.image.width = 0.8;

For #2 above, the padding, We are going to create an exclusion zone (a padding) between the edges of our control and the icon (image or thumbnail).
While adding the padding instruction straight to the control would create a limit (exclusion zone) between the grid cell edges and the control, which would be instructed like this:

button.paddingLeft = “10px”;

We actually want to set the limit/padding between the edges of the ‘imageOnlyButton’ control and our part of ‘image’ displayed within, thus offsetting the image inside the control. And this is done like this:

button.image.paddingLeft = “10px”;

This is because in the case of an Image type button, the ‘image’ is in fact a ‘part’ of the control. To access the image-only properties, you have to specify the path to it; Making sure it’s your image getting the padding and not the entire control. Alternatively to ‘image’, ‘thumbnail’ is also used in other controls and we’ll see to it later, but the way to do is the same.

In case of an image:

button.image.paddingLeft = "10px";
button.image.paddingRight = "10px";
button.image.paddingTop = "10px";
button.image.paddingBottom = "10px";

In case of a textBlock:

button.textBlock.paddingBottom = "10px";

So, by doing this, our ‘imageOnlyButton’ control for the top menu still covers the entire area of its assigned grid cell. However, the ‘image’ icon we are displaying in this control now has an offset of 10px on all sides.

In terms of code though, the above doesn’t look very ‘sexy’, does it? At least, not for a :nerd_face: :wink:
Since we want it to be centered and have the same padding (or exclusion zone) on all sides, we could do something like this:

button.image.setPaddingInPixels(10);

Which is equivalent to this:

button.image.setPaddingInPixels(10,10,10,10);

And can also be replaced with BJS values:

button.image.setPadding(10);

Which is equivalent to this:

button.image.setPaddingInPixels(10,10,10,10);

However, now that we have given a size and/or some padding to our icon, it eventually is not centered anymore. This is because of the default setting for alignment on an image part (left instead of centered) and we will just have to pass on a couple more instructions to make sure it remains centered:

button.image.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_CENTER;
button.image.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_CENTER;

which is basically equal to this:

button.image.horizontalAlignment = 2;
button.image.verticalAlignment = 2;

In case of a ‘textBlock’ (notice the change of ‘textHorizontalAlignment’ and ‘textVerticalAlignment’ instead of ‘horizontalAlignment’ and ‘verticalAlignment’):

button.textBlock.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_CENTER;
button.textBlock.textVerticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_CENTER;

Except for this particular case of a part of a control (image or textBlock), the default for control alignment is centered. Both horizontally and vertically.

The other options for alignment are *_LEFT and *_RIGHT for horizontal, and *_TOP and *_BOTTOM for Vertical. Expressed in number, the range of the value is from 0(zero) to 2.

And then of course, there’s a lot more to it and many other options you can try once you know a bit more.

Also, at this point and while experiencing in the PG, you will eventually notice that you do not always need to specify both height and width. Eventually, your icon image gets resized and keeps with its ratio of height and width. Eventually, it’s actually good practice to set only one (or even none) whenever possible (and this is actually also true for HTML/css).


Cleaning the console from the GUI design Warnings

While adding more controls and sizing’em both with percentage and pixels, you will also eventually notice that the console is starting to throw some warnings about mixing sizes expressed both in percentage and pixels for height and width.

I could tell you to just ignore these warnings (that’s what most devs and engs do, right?:wink: but I’m a simple designer and digital PM and I like to have a clean console so, rather than pushing this back for the upcoming #lesson 3, I’m gonna give you the solution right here, right now.

For a start, you can certainly ignore these warning; If the result and result on resize is what you need and want, the warning can just ‘go with the wind’ and this is achieved like this:

On a single control/container:

element_i.ignoreLayoutWarnings = true;

After building your GUI, looking back at all from your ‘advancedTexture’ elements/descendants:

advancedTexture.getDescendants().forEach(function(control) {
control.ignoreLayoutWarnings = true;
});


Adding observables and animations events to a control:

So we now have a top-menu button (displaying or hiding the menu) at the top left corner of our GUI. That’s great, but right now, this button doesn’t do anything yet. :face_with_diagonal_mouth:

Don’t worry, it will awake soon :zombie: :wink:. There are a number of user actions you can use on a control to trigger an event/animation or simply to record/observe. Mainly two types: An ‘observable’ and an ‘animation’ event. Rather than a lot of text, let’s have a look at some code for them:

button.pointerEnterAnimation = () => {
console.log(“starting an animation when user enters the control area”);
};

button.pointerOutAnimation = () => {
console.log(“starting an animation when user exits the control area”);
};

button.onPointerUpObservable.add(function() {
console.log(“observe this button interaction and start or record something when the users releases a mouse/pointer click”);
});

button.pointerUpAnimation = () => {
console.log(“on release, trigger my awesome animation function”);
myAwesomePointerUpAnim();
};

The complete list can be found in ‘observables’ and ‘animation events’, and for each control/class, in the ‘API’.

What we want to achieve with our menu button (show or hide menu) is the following:

  1. When the user first clicks on the menu button (from an initial hidden state), it shall display all menu buttons below.
  2. If the menu buttons are already visible, when the user clicks again, it will hide them all (and return to a hidden state).

Simple, yes? So how can we do this.
Well, again and as often with BJS (one of the reason I luv this framework :smiling_face_with_three_hearts:), there’s nearly always more than just one way.
But, let’s keep it simple and explain the one I used in this scenario.

We’ll start by opening this PG

The ‘isVisible’ parameter/accessor

First thing is that in this GUI repro of ‘Project Chair’, I’m relying on the parameter/accessor of ‘isVisible’ to either show or hide the menu buttons.

Second, (and this is really just my preference), I’m relying on a variable to record the state of the menu (hidden vs shown). Eventually, I also make sure the value is returned after each interaction.
Note that this is all not absolutely necessary. Eventually the value is returned anyway. Eventually you can also just check the state of ‘isVisible’ (using if ‘isVisible’ VS if ‘!isVisible’ on the control or container). For me, not being an ENG or system dev, everything is fine as long as it works over time, can be maintained and doesn’t soak-up too many resources :wink:

One advantage I find with this method of using a var to record the menu state is that later on, i.e. say in a separate animation script, I can call and rely on this variable without accessing the control. This var tells me the state of my menu display and will always do it correctly (no matter if the control is here or ‘removed’) because its value is always returned and easily accessible.

So, for this to work we need to start with creating this variable. It will be a simple ‘bolean’, with a ‘number’ or a ‘string’, recording either 0,1 or ‘true’,‘false’.
I choose the number, so I don’t use quotes.

var showmenu = 0;

If you want to use a string (dont’ forget the quotes, single or double):

var showmenu = ‘false’;

Now that we have a ‘variable’ to record our menu state, we will simply change it on user click/release (pointer up). For that, again, we have a number of ways. Let’s just pick one (and stick to it for the sake of #consistency).

I choose to go with the ‘if’ condition. We check 'if the showmenu is equal (==) to 0, in case we change it to 1. Whereas ‘if’ or ‘else if’ the value is 1, then we change it back to 0.
After the interaction, we also make sure the value for the var named ‘showmenu’ is updated/returned.

So something like this:

button.onPointerUpObservable.add(function() {
	if (showmenu == 0){
		//menu is NOT active - changing to active
		console.log ("menu is active " + showmenu);
		//do anything when the menu is changed to active/visible.
		//showing the panel hosting the menu controls
		panel.isVisible = true;
		//changing the value of the showmenu var to active/visible.
		showmenu = 1;
	}

	else if(showmenu == 1){
		//menu is active - changing to NOT active
		console.log ("menu is NOT active " + showmenu;
		//do something when the menu is changed to invisible.
		//reinitializing buttons display, making them ready to show with their initial display (specific to this project)
		button.background = "transparent";
		button0.background = "#FFFFFF99";
		button0.image.source = "tex/tour.svg";
		button0.image.alpha = 1;

		button1.background = "#FFFFFF99";
		button1.image.source = "tex/layout.svg";
		button1.image.alpha = 1;

		button2.background = "#FFFFFF99";
		button2.image.source = "tex/edit-compare.svg";
		button2.image.alpha = 1;

		//hiding the panel hosting the menu controls
		panel.isVisible = false;

		//changing the value of the showmenu var to invisible.
		showmenu = 0; 
	}

	console.log ("showmenu after state is " + showmenu);
	//returning the new value for the showmenu var (not absolutely necessary in this case)
	return showmenu;
});

Now, having all this (and more) in your control, can rapidly become a bit messy.
So, why not simply trigger a function that will change the menu state?
We would next have all of these functions and animation events elsewhere in our script or, even better, in a separate/external script. Something like this on our control:

button.onPointerUpObservable.add(() => {
    changeMenu();
});

and elsewhere, in a dedicated section of the script or a separate script, our function:

var _menu = 0;

var changeMenu = function() {
    if (_menu == 0){
        //menu is NOT active - changing to active
        console.log ("menu is active " + _menu);
        _menu = 1;
        panel.isVisible = true;
        }

    else if(_menu == 1){
        //menu is active - changing to NOT active
        console.log ("menu is NOT active " +_menu);

            button.background = "transparent";
            button0.background = "#FFFFFF99";
            button0.image.source = "tex/tour.svg";
            button0.image.alpha = 1;

            button1.background = "#FFFFFF99";
            button1.image.source = "tex/layout.svg";
            button1.image.alpha = 1;

            button2.background = "#FFFFFF99";
            button2.image.source = "tex/edit-compare.svg";
            button2.image.alpha = 1;

        panel.isVisible = false;
        _menu = 0; 
        }

    console.log ("changeMenu after state is " + _menu);
    return _menu;
}

Now, there’s one more reason why I like to work these parts using a var (private or public).
Say you want to have a special event when the user first clicks on the menu, that will happen only the very first time the user clicks. Say ‘a tour’ or ‘a tip’. You can set an initial ‘null’ state to your var:

_menu = null;

and add just this one more condition to your pointerUp event or associated function:

if (_menu == null){
    //menu has never been clicked - changing to active
    console.log ("menu is clicked a first time and is now changing to active " + _menu);
    _menu = 1;
    panel.isVisible = true;
    //add an action that will trigger only once
    myAwesomeTourFunction();
    }

Before wrapping-up #Lesson2 (because I also have a social life and my own pro and personal projects to work, sry :hourglass:, we will be closing this episode with just one more aspect…

4 Likes

#LESSON 2 (cont.) While waiting for #Lesson 3

For those of you used to making your UI with HTML/css, one could ask ‘Where are the options for ‘display’ (none or initial)?’ Don’t worry, it’s here (somehow). It’s not ALL here but enough to manage and handle the display of your controls and containers.

Managing panels and controls depending on context:

For just now, you can use this PG to test and experience with this (and 'will come back to the details in #Lesson3).

There are at least three major ways to manage how the GUI elements display or hide depending on context. At least, three I know of (there may be more):

  1. Adding/removing a control:
    In lesson #1, we have learned that we need to attach/add a control or container to the advancedTexture in order to see it and eventually, in order to be able to pass on parameters/styling to it. If we can add it, we sure also can remove it.
    Adding and removing controls is one way to replace the ‘display=none’/“display=initial” we are eventually using with HTML/css.

  2. Making a control or container ‘visible’ or ‘invisible’:
    The other way I know of, the way I personally like to use, is using ‘isVisible’ (true or false).
    This is the method we have seen above and you should now have a better understanding of how this works. Again, if not, feel free to question, comment or challenge.

  3. Changing the height of a container or control to zero:
    Self-explanatory. If the height is 0 (or “0px”) then the control or container is still here but cannot be seen (because the height is 0)

In our PG example above reproducing the GUI from my ‘Project Chair’, let’s have a closer look at the ‘settings’ panel (called ‘panelset*’), showing to the mid-right bottom of the grid and which is changing content depending on the footer icons clicked. How is it done?

Basically, this one is done with an imbrication of stackpanels (similar to an imbrication of divs/blocks), some being visible, others not, depending on context. The panel called ‘panelset’ is the parent (or frame) for all of ‘settings’ panels. It basically sets an area and a constraint for displaying all from settings. Inside this ‘panelset’ (main parent) panel, a first panel is added/imbricated. It will be used to display the content from ‘settings/change monitor’ interaction. But then, in this same ‘panelset’ container (frame or parent), a second and a third panel are added. They all stack-up vertically (since they all have a width of 1 or 100%). The two reasons why you don’t see them all at once are because:

  1. The first imbricated panel (the active panel) covers the entire size (height and width) of the parent container. So, all other panels are here (just below) but the limits set to the parent makes it so you cannot see them.

  2. All others than the ‘active’ panel have been given a parameter for ‘isVisible’ set to ‘false’ (which is the equivalent of display=‘none’). Understand they are ‘invisible’ at this point (or better said, ‘inexistant for display’). Which is also (at least, visually) basically the same as when using removeControl(); Except i.e. ‘panel.removeControl(button);’ makes the button ‘inactive’ or even better said, removed.

So, why do I generally prefer using ‘isVisible’ VS ‘removeControl()’? Eventually, in my script and interactions, I have parts checking back on a control. It’s much easier to do that if the control is just invisible rather than removed. Also, I (personally) don’t really fancy seeing my controls automagically disappear :dotted_line_face:, replace and re-appear :sunrise: depending on context.

From my understanding, the true counterpart for ‘display=none’ would be ‘isVisible=false’. And you know what, this works beautifully in BJS. :smile:

And then there’s just this one more thing. When a control or container is removed, the children are also removed. Whereas, when a container is set to be ‘invisible’, then the children are… well, also invisible :wink: :thinking: Ok, true, but the point is: I can fix this by making ‘invisible’ only this control I want to be hidden and this is done like this:

control.notRenderable = true;

In fine, really, no matter which approach you choose from the above (or even others). All methods and approaches are OK to use as long as they work for your needs (else they wouldn’t be here :smiley:.

At this point, in terms of design, it’s just important to understand that by making your panel (parent of controls and/or other panels) ‘disappear’ (either invisible or removed), you are also removing everything from the instructions of panning, sizing and alignement you have given to this panel or control. And, since most or many are relative, you are likely going to impact everything that’s below it (and sometimes, above it).

Well, I guess this is where #Consistency (or the 3Cs :wink: ) kick-in and where you start to realize of the multiple methods and approaches for design that will eventually work or break in your GUI and for your project needs. I shall leave you with this thought and the content, examples and exercises from #Lesson 2 above for this WE,… making us ready for #Lesson 3.


In #Lesson 3, we will have a closer look at some more controls and containers, such as the ‘textBlock’, the ‘Image’, the ‘Rectangle’, the ‘Slider’, the ‘Scrollviewer’ and the ‘Line’ system.
We will also add all these containers and controls and finalize our repro of my ‘Project Chair’ GUI (at least, visually). Finally, we will have a quick look at some more advanced controls and parameters.

After all that, rest assured that your learning will still be incomplete :wink: But then, hopefully enough for you to start create your own GUI and with the will and eagerness to continue discovering by yourself (and with the help of the doc and PGs) all of the amazing features offered by the BJS 2D GUI.

Meanwhile, take care, never stop BJSying and we’ll see us soon,

3 Likes

Thanks for taking the time to add this level of detail. Looking forward to the next chapter :slight_smile: Breaking it down this way helps me understand the interactions between the code, although I would struggle to write it from scratch with my js knowledge.

The good thing is: You don’t have to :smiley: Just how do you thing I learned this? :wink:
Meanwhile, have a great WE :sunglasses:

1 Like

@mawa : Looking forward to the rest of your tutorial(s) on this topic.

Stay Safe, gryff :slight_smile:

Hey @mawa how’s lesson 3 coming along :slight_smile: have you got a playground I could take a look at in the meantime to understand the basics of collapsing the whole structure? I see the last one just collapses the first structure and not the sub box.

Sry, was kind of busy with the launch of v5.5. :rocket: :rocket: Updating older demos (had some issues). :sweat:
Will come back on it this WE. Will certainly not leave it unfinished.
Meanwhile, take care,

2 Likes