nappgui_src icon indicating copy to clipboard operation
nappgui_src copied to clipboard

Dynamically unloading GUI elements

Open kicsyromy opened this issue 2 years ago • 2 comments

Hello,

I'm working on a application that requires some dynamic UI behavior. In the context of this application this means adding and removing (not hiding but outright destroying) arbitrary GUI elements to a existing (and visible) window.

Looking at the documentation and going through the examples I haven't found a way to achieve this.

Is this something that is doable?

Thank you!

kicsyromy avatar Jul 02 '22 09:07 kicsyromy

Hi @kicsyromy! Yes you can do it with panels. The layout_panel() function "cleanly" destroys the previously assigned panel. The rest of the elements of a layout (buttons, labels, slider, view, etc) cannot be changed dynamically. Here a demo (source + exe). I will add same similiar example to documentation.

DynPanel0

DynPanel1

DynPanel2

DynamicPanelsApp.zip

When you close the App, you can see that there is no MemoryLeaks

[16:42:39] [OK] Heap Memory Staticstics
[16:42:39] ============================
[16:42:39] Total a/dellocations: 654, 654
[16:42:39] Total bytes a/dellocated: 72366, 72366
[16:42:39] Max bytes allocated: 26198
[16:42:39] Effective reallocations: (0/30)
[16:42:39] Real allocations: 2 pages of 65536 bytes
[16:42:39] ============================
[16:42:39] Config: Debug
/* NAppGUI Hello World */

#include "nappgui.h"

typedef struct _app_t App;

struct _app_t
{
    Window *window;
    Layout *layout;
    uint32_t panel_id;
};

/*---------------------------------------------------------------------------*/

// Dynamic Panel 0 --> Just a label
static Panel *i_panel0(void)
{
    Panel *panel = panel_create();
    Layout *layout = layout_create(1, 1);
    Label *label = label_create();
    label_text(label, "This is a very small panel");
    layout_label(layout, label, 0, 0);
    panel_layout(panel, layout);
    return panel;
}

/*---------------------------------------------------------------------------*/

// Dynamic Panel 1 --> 'n' pairs Label:Slider
static Panel *i_panel1(void)
{
    uint32_t i, n = 5;
    Panel *panel = panel_create();
    Layout *layout = layout_create(2, 5);
    
    for (i = 0; i < n; ++i)
    {
        Label *label = label_create();
        Slider *slider = slider_create();
        char_t text[128];
        bstd_sprintf(text, sizeof(text), "Dynamic variable %d", i + 1);
        label_text(label, text);
        layout_label(layout, label, 0, i);
        layout_slider(layout, slider, 1, i);
        layout_hsize(layout, 1, 150);

        if (i < n - 1)
            layout_vmargin(layout, i, 5);
    }
    
    layout_hmargin(layout, 0, 5);
    panel_layout(panel, layout);
    return panel;
}

/*---------------------------------------------------------------------------*/

static void i_OnDraw1(App *app, Event *e)
{
    const EvDraw *p = event_params(e, EvDraw);
    draw_clear(p->ctx, kCOLOR_RED);
    unref(app);
}

/*---------------------------------------------------------------------------*/

static void i_OnDraw2(App *app, Event *e)
{
    const EvDraw *p = event_params(e, EvDraw);
    draw_clear(p->ctx, kCOLOR_BLUE);
    unref(app);
}

/*---------------------------------------------------------------------------*/

// Dynamic Panel 2 --> Two dynamic drawings
static Panel *i_panel2(App *app)
{
    Panel *panel = panel_create();
    Layout *layout = layout_create(2, 1);
    View *view1 = view_create();
    View *view2 = view_create();
    view_OnDraw(view1, listener(app, i_OnDraw1, App));
    view_OnDraw(view2, listener(app, i_OnDraw2, App));
    view_size(view1, s2df(256, 256));
    view_size(view2, s2df(256, 256));
    layout_view(layout, view1, 0, 0);
    layout_view(layout, view2, 1, 0);    
    layout_hmargin(layout, 0, 5);
    panel_layout(panel, layout);
    return panel;
}

/*---------------------------------------------------------------------------*/

static Panel *i_dynamic_panel(App *app, const uint32_t id)
{
    switch (id) {
    case 0:
        return i_panel0();

    case 1:
        return i_panel1();

    case 2:
        return i_panel2(app);

    cassert_default();
    }

    return NULL;
}

/*---------------------------------------------------------------------------*/

static void i_OnRadio(App *app, Event *e)
{
    const EvButton *p = event_params(e, EvButton);
    cassert_no_null(app);
    if (app->panel_id != p->index)
    {
        Panel *panel = i_dynamic_panel(app, p->index);

        //
        // Layout panel will correctly change the current panel
        // with the new panel. Current panel will be correctly destroyed.
        //
        layout_panel(app->layout, panel, 0, 1);
        
        //
        // Force the current window to recompute/resize
        //
        layout_update(app->layout);

        app->panel_id = p->index;
    }
}

/*---------------------------------------------------------------------------*/

static Layout *i_layout(App *app)
{
    Layout *layout1 = layout_create(1, 2);
    Layout *layout2 = layout_create(3, 1);
    Button *radio0 = button_radio();
    Button *radio1 = button_radio();
    Button *radio2 = button_radio();
    Panel *panel = i_dynamic_panel(app, app->panel_id);
    button_text(radio0, "Panel 0");
    button_text(radio1, "Panel 1");
    button_text(radio2, "Panel 2");
    button_state(radio0, app->panel_id == 0 ? ekON : ekOFF);
    button_state(radio1, app->panel_id == 1 ? ekON : ekOFF);
    button_state(radio2, app->panel_id == 2 ? ekON : ekOFF);
    button_OnClick(radio0, listener(app, i_OnRadio, App));
    layout_hmargin(layout2, 0, 5);
    layout_hmargin(layout2, 1, 5);
    layout_vmargin(layout1, 0, 10);
    layout_margin(layout1, 10);
    layout_halign(layout1, 0, 0, ekLEFT);
    layout_button(layout2, radio0, 0, 0);
    layout_button(layout2, radio1, 1, 0);
    layout_button(layout2, radio2, 2, 0);
    layout_layout(layout1, layout2, 0, 0);
    layout_panel(layout1, panel, 0, 1);
    return layout1;
}

/*---------------------------------------------------------------------------*/

static Panel *i_panel(App *app)
{
    Panel *panel = panel_create();
    Layout *layout = i_layout(app);
    panel_layout(panel, layout);

    //
    // The keep the main layout for futher editions in 'i_OnRadio'
    //
    app->layout = layout;

    return panel;
}

/*---------------------------------------------------------------------------*/

static void i_OnClose(App *app, Event *e)
{
    osapp_finish();
    unref(app);
    unref(e);
}

/*---------------------------------------------------------------------------*/

static App *i_create(void)
{
    App *app = heap_new0(App);
    Panel *panel = i_panel(app);
    app->window = window_create(ekWNSTD);
    window_panel(app->window, panel);
    window_title(app->window, "Dynamic Panels");
    window_origin(app->window, v2df(500, 200));
    window_OnClose(app->window, listener(app, i_OnClose, App));
    window_show(app->window);
    return app;
}

/*---------------------------------------------------------------------------*/

static void i_destroy(App **app)
{
    window_destroy(&(*app)->window);
    heap_delete(app, App);
}

/*---------------------------------------------------------------------------*/

#include "osmain.h"
osmain(i_create, i_destroy, "", App)

frang75 avatar Jul 02 '22 14:07 frang75

Hi @frang75,

This is amazing! Thank you so much for the very detailed response and example.

From my POV the issue can be closed after a documentation update.

Keep up the great work! 🙂

kicsyromy avatar Jul 04 '22 04:07 kicsyromy

Related with https://github.com/frang75/nappgui_src/issues/74

WIP

frang75 avatar Jan 05 '24 05:01 frang75

This feature is officially supported in this commit: https://github.com/frang75/nappgui_src/commit/1b6d902b8b3e19ddb185154ae3488a5c6637b02b

Using this new function: layout_panel_replace()

Documentation: https://nappgui.com/en/gui/layout.html#h6.1

frang75 avatar Feb 20 '24 14:02 frang75