How to properly manage a layout

Description

I am trying to draw a fixed-size screen(s) that does not require the user to scroll down.

What MCU/Processor/Board and compiler are you using?

Custom TI AM335x processor board using buildroot 2017.08-git-00337-g9dfed2a-dirty gcc compiler (5.4.0).

What do you want to achieve?

I am trying to draw a number of objects (primarily buttons) on a screen in a fixed amount of space. My thoughts are that I can either make the buttons a specific size and let the layout manager handle positiong or I can use absolute positioning myself.

What have you tried so far?

I have created a Window as my primary object, added a container to the Windowā€™s data portion, and added 15 buttons into the container. There is some strange combination of number of buttons + size of buttons that causes the application to fail to render the full screen.

With this code I can see my 15 buttons, but they do not take up the whole screen (pic1).

Referencing the code below, if I force the buttons to be 1 pixel taller (from y = 35 to y = 36) then it fails to draw the full screen (pic2).

I realize that this is probably the wrong approach, and that is where my question really resides - what should I do to get a number of buttons/objects to fit in a fixed-size area?

Code to reproduce

    static lv_style_t win_style;
    static lv_obj_t *win;

    lv_style_copy(&win_style, &lv_style_transp);
    win = lv_win_create(lv_disp_get_scr_act(NULL), NULL);
    lv_win_set_title(win, "Hello!");
    lv_win_set_style(win, LV_WIN_STYLE_CONTENT, &win_style);
    lv_win_add_btn(win, LV_SYMBOL_HOME);

    . . .

    lv_obj_t *obj;
    lv_obj_t *cont;

    cont = lv_cont_create(win, NULL);
    lv_obj_set_auto_realign(cont, false); // Don't resize
    lv_cont_set_fit(cont, LV_FIT_FLOOD); // Widen to fill parent
    lv_cont_set_layout(cont, LV_LAYOUT_GRID);

    obj = lv_btn_create(cont, NULL);
    lv_obj_set_size(obj, 70, 36);
    lv_btn_set_toggle(obj, true);
    obj = lv_label_create(obj, NULL);
    lv_label_set_text(obj, "1");

    obj = lv_btn_create(cont, NULL);
    lv_obj_set_size(obj, 70, 36);
    lv_btn_set_toggle(obj, true);
    obj = lv_label_create(obj, NULL);
    lv_label_set_text(obj, "2");

    obj = lv_btn_create(cont, NULL);
    lv_obj_set_size(obj, 70, 36);
    lv_btn_set_toggle(obj, true);
    obj = lv_label_create(obj, NULL);
    lv_label_set_text(obj, "3");

    obj = lv_btn_create(cont, NULL);
    lv_obj_set_size(obj, 70, 36);
    lv_btn_set_toggle(obj, true);
    obj = lv_label_create(obj, NULL);
    lv_label_set_text(obj, "4");

    obj = lv_btn_create(cont, NULL);
    lv_obj_set_size(obj, 70, 36);
    lv_btn_set_toggle(obj, true);
    obj = lv_label_create(obj, NULL);
    lv_label_set_text(obj, "A");

    obj = lv_btn_create(cont, NULL);
    lv_obj_set_size(obj, 70, 36);
    lv_btn_set_toggle(obj, true);
    obj = lv_label_create(obj, NULL);
    lv_label_set_text(obj, "5");

    obj = lv_btn_create(cont, NULL);
    lv_obj_set_size(obj, 70, 36);
    lv_btn_set_toggle(obj, true);
    obj = lv_label_create(obj, NULL);
    lv_label_set_text(obj, "6");

    obj = lv_btn_create(cont, NULL);
    lv_obj_set_size(obj, 70, 36);
    lv_btn_set_toggle(obj, true);
    obj = lv_label_create(obj, NULL);
    lv_label_set_text(obj, "7");

    obj = lv_btn_create(cont, NULL);
    lv_obj_set_size(obj, 70, 36);
    lv_btn_set_toggle(obj, true);
    obj = lv_label_create(obj, NULL);
    lv_label_set_text(obj, "8");

    obj = lv_btn_create(cont, NULL);
    lv_obj_set_size(obj, 70, 36);
    lv_btn_set_toggle(obj, true);
    obj = lv_label_create(obj, NULL);
    lv_label_set_text(obj, "B");

    obj = lv_btn_create(cont, NULL);
    lv_obj_set_size(obj, 70, 36);
    lv_btn_set_toggle(obj, true);
    obj = lv_label_create(obj, NULL);
    lv_label_set_text(obj, "9");

    obj = lv_btn_create(cont, NULL);
    lv_obj_set_size(obj, 70, 36);
    lv_btn_set_toggle(obj, true);
    obj = lv_label_create(obj, NULL);
    lv_label_set_text(obj, "10");

    obj = lv_btn_create(cont, NULL);
    lv_obj_set_size(obj, 70, 36);
    lv_btn_set_toggle(obj, true);
    obj = lv_label_create(obj, NULL);
    lv_label_set_text(obj, "11");

    obj = lv_btn_create(cont, NULL);
    lv_obj_set_size(obj, 70, 36);
    lv_btn_set_toggle(obj, true);
    obj = lv_label_create(obj, NULL);
    lv_label_set_text(obj, "12");

    obj = lv_btn_create(cont, NULL);
    lv_obj_set_size(obj, 70, 36);
    lv_btn_set_toggle(obj, true);
    obj = lv_label_create(obj, NULL);
    lv_label_set_text(obj, "C");

Screenshot and/or video

The second picture looks like a crash to me. Is that the case?

As far as I can tell the application has not crashed - it does not return me to the Linux console, and I get no prints of any sort to indicate a segfault or anything of that nature.

Layouts are a bit magic.

My first thoughts (As I have done this recently).

If you want to draw a bunch of widgets in an area, and let the layout do its magic there are a few things you can do.

Lets say you want 10 buttons set in a grid, in an area x=10,y=10,width=100, height=100
// This assumes the screen as a parent, you can put anything you want.

lv_obj_t *cont=lv_cont_create(lv_scr_act(), nullptr);
lv_obj_set_pos(cont, 10,10); // x=10, y=10
lv_obj_set_size(cont, 100, 100); // width=100, height =100
lv_cont_set_fit(cont, LV_FIT_NONE); // We want our container to be static. We want to align our buttons
lv_cont_set_layout(cont, LV_LAYOUT_PRETTY); // We want our layout to be a grid, and align our children as such

// LEVEL UP DATA
One thing about layouts that is very powerful, my guess it was done completely on accident :wink:

You can embed containers in containers. Lets say you want a bunch of columns like in a spreadsheet (Contrived example)

lv_obj_t *outterCont=lv_cont_create(win, nullptr)
lv_cont_set_layout(outterCount, LV_LAYOUT_ROW_T);
lv_obj_t *col1=lv_cont_create(outterCont, nullptr)
lv_obj_t *col2=lv_cont_create(outterCont, nullptr)
lv_obj_t *col3=lv_cont_create(outterCont, nullptr)

Now you have 3 column containers that you can in turn set your layout how you like. This is very useful for things like headers and such.

This has always been my experience as well! :wink: The trick is learning how each one behaves.

In my case I was creating the container within the window, but I can try ditching the window for a while. The only reason I made the window was that trying to write directly to the screen wasnā€™t working (probably due to whatever problem(s) Iā€™ve introduced elsewhere). Iā€™ll give this a shot.

I tried this as well, but they appeared to be writing over one another instead of stacking next to one another. Iā€™m going to scrap my layout for a bit and give this approach a shot to try to better understand how the manager works. Thank you for this pointer!

You should probably debug that issue before you try and get a layout working - the lack of drawing to the screen is going to give you other problems.

Iā€™ve simplified my code thanks to @rohmerā€™s suggestion and I have 15 buttons drawn on the screen; however, itā€™s crammed them into 4 rows with the last row about 2/3 out of the container. Hereā€™s how Iā€™ve simplified my code thus far:

    lb_obj_t *cont = lv_cont_create(lv_scr_act(), NULL);
    lv_obj_set_pos(cont, 15, 15); // x.y = 15.15
    lv_obj_set_size(cont, 450, 242); // container is 450x242 - 15px padding
    lv_cont_set_fit(cont, LV_FIT_NONE); // Container is static
    // We want the layout to be a grid and to align its children as such
    lv_cont_set_layout(cont, LV_LAYOUT_PRETTY);

    lv_obj_t *obj;

    obj = lv_btn_create(cont, NULL);
    obj = lv_btn_create(cont, NULL);
    obj = lv_btn_create(cont, NULL);
    obj = lv_btn_create(cont, NULL);
    obj = lv_btn_create(cont, NULL);
    obj = lv_btn_create(cont, NULL);
    obj = lv_btn_create(cont, NULL);
    obj = lv_btn_create(cont, NULL);
    obj = lv_btn_create(cont, NULL);
    obj = lv_btn_create(cont, NULL);
    obj = lv_btn_create(cont, NULL);
    obj = lv_btn_create(cont, NULL);
    obj = lv_btn_create(cont, NULL);
    obj = lv_btn_create(cont, NULL);
    obj = lv_btn_create(cont, NULL);

One thing to note is I had to get rid of the labels per button before it would actually draw to the screen. This may be something I need to debug, but I can shelve the labels for now if that is indeed causing my other issue.

I suppose my next question is what can I do to tell the container I would like these buttons to be in a 3x5 grid or something to that nature? Am I safe in assuming that the container will not ever resize these buttons as they are created?

Again, I suggest debugging that. Does that issue occur when they are outside of the container or only when they are inside?

It wasnā€™t an intentional feature (as far as I know), but itā€™s typical of many layout systems. For example, in HTML5, you can put many ā€œflexboxā€ containers of differing orientations (columns/rows) together to make complex, responsive layouts.

The disadvantage of such a setup, obviously, is that it consumes more RAM as a result of the additional number of container objects.

@embeddedt I was giving you a hard time on my ā€œaccidentā€ comment.

It was a compliment :smiley:

1 Like

Layout and Fit are really a little bit of magic now and doesnā€™t have enough examples and documentation.

@tarvik
Iā€™ve tried your code and it works for me with 35 and 36 px height too. What is the resolution od your screen?

Actually, it was an intentional feature. Iā€™ve tested it a lot and gave me hard times to make it work properly. AFAIK it works well, but (of course) bugs can be everywhere. :smiling_imp:

With LV_LAYOUT_PRETTY you can control the minimum space on the edges and between the objects in a row with style.body.pading.xxx. So the number of rows/cols are resulted according to the padding values. See the docs about layout. Although I was thinking about adding directly 2,3,4,5 column layouts.

It is a 480x272 screen. Strangely enough while I was testing earlier I was able to change them to 36 despite not being able to do that all afternoon yesterday. I could also make them larger, but then when I reduced the size by a small amount (5px each direction) it crashed out again. Iā€™m sure Iā€™m doing something wrong, but Iā€™m still tracking down what it is.

What determines the size of the button? In other GUI packages Iā€™ve been able to asign a size of the layout and the paddings, and it will force the objects to fit within it. Iā€™m confused as to whether or not I can do something similar here.

Iā€™m working on it. Iā€™m still trying to figure out what causes it as it seems to be a strange combination of number of buttons + size of buttons (+ labels?). It feels like a bad pointer, but without the segfault Iā€™m not sure how it would be that. When I was creating the buttons outside of a container they were overlapping one another (no explicit x/y pos?), and I knew the container was most likely the tool I needed so I immediately started adding them there.

Well, Iā€™m still a bit stumped as to why adding labels to the text boxes is causing me issues. The application is definitely not crashing as I created a 1s task to toggle a button. Without labels inside the buttons I can see the button toggle every second, but as soon as I add the labels back into the buttons it no longer draws the full screen. Is there any way that adding a label to a parent object influences the size of that parent object at all?

The reason I ask is that I got down to counting pixels and came up with a size of 85x76 maximum to fit within my container and not overflow. My screen is 480x272, and Iā€™ve put a 15px padding around the container. This leaves me with 450x242 to play with.

My inner padding is 6px, so for a 5x3 grid that takes away 24 horizontal pixels and 12 vertical pixels - 426x230. 5 horizontal buttons goes into 426 85 full times, and 3 vertical buttons goes into 230 76 full times.

Hereā€™s where it gets a little more interesting to me. If I increase the vertical size of the button by 1 to 77, then the app ā€˜soft-crashesā€™ by which I mean itā€™s still running, but it fails to draw the full screen; however, if I instead increase the horizontal size by 1 pixel then it instead draws a 4x4 grid with the last three buttons off the screen.

This makes some sense to me in that most of the wrapping would be done horizontally rather than vertically, and having too many vertical pixels is probably triggering an early return/failure somewhere in the draw routines.

Iā€™ll get back to this tomorrow, but if you guys have any ideas in the meantime Iā€™d love to hear your thoughts.

Thanks so much for all your help so far!

By default there is a white background filling the whole screen. Even if you adjusted the size of the parent container, you would still see the white background behind it.

Your screenshot above shows that the pixels are not reaching the display/framebuffer. The easiest way to check whether this is a bug in your driver (or LittlevGL) or not is to try the same code in the PC simulator configured for 480x272 resolution and see if you get the same result.

That is reasonable. Iā€™ll give this a shot in the morning. Thanks for the tip!

@tarvik Iā€™ve slightly reworked your original code. Iā€™ve tried some button sizes and it worked well for me.

image

    static lv_style_t win_style;
    lv_style_copy(&win_style, &lv_style_transp);
    win_style.body.padding.inner = 20;

    static lv_obj_t *win;
    win = lv_win_create(lv_disp_get_scr_act(NULL), NULL);
    lv_win_set_title(win, "Hello!");
    lv_win_set_style(win, LV_WIN_STYLE_CONTENT, &win_style);
    lv_win_add_btn(win, LV_SYMBOL_HOME);

    /*Set a layout for the window*/
    lv_obj_t *cont;
    cont = lv_win_get_content(win);
    lv_win_set_layout(win, LV_LAYOUT_GRID);

    /*Add buttons*/
    const char * txts[] = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", NULL};
    lv_obj_t *obj;
    uint8_t i;
    for(i = 0; txts[i]; i++) {
        obj = lv_btn_create(cont, NULL);
        lv_obj_set_size(obj, 70, 36);
        lv_btn_set_toggle(obj, true);
        obj = lv_label_create(obj, NULL);
        lv_label_set_text(obj, txts[i]);
    }

This worked as-is, but as soon as I start tweaking the button sizes it starts to die again.

I am working on bringing up the PC simulator per @embeddedtā€™s suggestion so I can verify that this is not something in my driver or my Linux environment.

If youā€™re on Window itā€™s easier to get started with the Code blocks or the Visual Studio project.
Just donā€™t forget to update lvgl as itā€™s updated only on larger releases.

https://docs.littlevgl.com/en/html/get-started/pc-simulator.html

Iā€™ve downloaded Codeblocks and gotten my project building, but I cannot get the simulator to display anything. It looks like Codeblocks is using a 32-bit mingw toolchain that came with the Codeblocks installation - does this seem right?

Are you using the default example that came with it or have you changed something?

I modified the example to add in the menu code weā€™ve been discussing in this thread, but otherwise the build environment is default. I had to point the IDE to the gdb executable because it somehow had the wrong path.

Maybe I should just reinstall the IDE and try again?