How to ignore clicks on Button Matrix "background panel" (which cause NULL button text!)

Description

When “clicking” on the empty space between buttons arranged in a button matrix, I get NULL text in the corresponding handler.
Is there a button matrix property to set, so that clicks always fall onto a button and not on the “background panel”?

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

Odroid N2, Ubuntu18.04, gcc 8.4.0

What do you want to achieve?

Avoid getting NULL text into the button handler, when clicking “outside” a button in a button matrix

What have you tried so far?

Followed the code example for the button matrix

Code to reproduce

#include "lvgl/lvgl.h"
#include "lv_drivers/display/fbdev.h"
#include "lv_drivers/indev/evdev.h"
#include <stdio.h>
#include <unistd.h>


#define DISP_BUF_SIZE 64*1024UL // If smaller than screen memory, draw will occur in chunks; but if the buffer is not too small, performances are still plenty
#define LV_LOOP_INTERVAL_MS 5

#define GUI_DEBUGMODE

static const char* upperButtonMatrix_map[] = {"1", "2", "3", "4", "5", ""};


static void GUIinit(void)
{
    /*LittlevGL init*/
    lv_init();

    /*Linux frame buffer device init*/
    fbdev_init();

    /*Small buffers for LittlevGL to draw the screen's content with double-buffering*/
    static lv_color_t buf1[DISP_BUF_SIZE];
    static lv_color_t buf2[DISP_BUF_SIZE];

    /*Initialize a descriptor for the buffer, with double buffering*/
    static lv_disp_buf_t disp_buf;
    lv_disp_buf_init(&disp_buf, buf1, buf2, DISP_BUF_SIZE);

    /*Initialize and register a display driver*/
    // Here we use the special callback function specific for the Linux Framebuffer Driver
    lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.buffer = &disp_buf;
    disp_drv.flush_cb = fbdev_flush;
    lv_disp_drv_register(&disp_drv);

    // Initialize EVDEV input for the 52pi touchscreen,
    // which luckily is supported as an "event input device" by the kernel
    evdev_init();
    lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = evdev_read;
    lv_indev_drv_register(&indev_drv);
}

static void generalButtonHandler(lv_obj_t * obj, lv_event_t event)
{
    if(event == LV_EVENT_CLICKED) 
    {   
        const char* buttonText = lv_btnm_get_active_btn_text(obj);
        if (buttonText == NULL)
        {
            fprintf(stderr, "generalButtonHandler: NULL button text!\n");
        }
        else
        {
        if (!strcmp(buttonText, "1"))
        {
            #ifdef GUI_DEBUGMODE
            fprintf(stdout, "Button 1 clicked\n");
            #endif
        } else {
        if(!strcmp(buttonText, "2"))
        {
            #ifdef GUI_DEBUGMODE
            fprintf(stdout, "Button 2 clicked\n");
            #endif
        } else {
        if(!strcmp(buttonText, "3"))
        {
            #ifdef GUI_DEBUGMODE
            fprintf(stdout, "Button 3 clicked\n");
            #endif
        } else {
        if(!strcmp(buttonText, "4"))
        {
            #ifdef GUI_DEBUGMODE
            fprintf(stdout, "Button 4 clicked\n");
            #endif
        } else {
        if(!strcmp(buttonText, "5"))
        {
            #ifdef GUI_DEBUGMODE
            fprintf(stdout, "Button 5 clicked\n");
            #endif
        } else {
        // ERROR
            #ifdef GUI_DEBUGMODE
            fprintf(stdout, "ERROR: unknown button %s pressed\n", buttonText);
            #endif
        }
        }        
        }
        }
        }
        }
    }    
}

static void drawUpperButtons(lv_align_t lAlignment, lv_coord_t lCorrectionX, lv_coord_t lCorrectionY)
{
    lv_obj_t* pUpperButtonMatrix = lv_btnm_create(lv_scr_act(), NULL);
    lv_btnm_set_map(pUpperButtonMatrix, upperButtonMatrix_map);
    lv_obj_set_size(pUpperButtonMatrix, 800, 80);
    lv_obj_align(pUpperButtonMatrix, NULL, lAlignment, lCorrectionX, lCorrectionY);
    lv_obj_set_event_cb(pUpperButtonMatrix, generalButtonHandler);    
}


static void drawHomeScreen(void)
{   
    // Upper buttons    
    drawUpperButtons(LV_ALIGN_IN_TOP_MID, 0, 0);
}


int main(void)
{
    // Initializations
    GUIinit();

    // Draw main screen
    drawHomeScreen();

    // Handle LitlevGL tasks (tickless mode)
    // Mandatory, otherwise nothing will be displayed
    // It allows the callback functions to operate (for example to actually transfer bytes to screen framebuffer)
    while(1) 
    {
        lv_tick_inc(LV_LOOP_INTERVAL_MS);
        lv_task_handler();
        usleep(LV_LOOP_INTERVAL_MS * 1000UL);
    }

    return 0;
}


Screenshot and/or video

The framebuffer device was opened successfully.
800x480, 32bpp
The framebuffer device was mapped to memory successfully.
Button 1 clicked
Button 2 clicked
Button 3 clicked
Button 4 clicked
Button 5 clicked
generalButtonHandler: NULL button text!

LV_EVENT_CLICKED is called whenever the user touches any part of the surface of the button matrix, whether a button on it is pressed or not.

Therefore, there are two solutions I see:

  • Ignore the event if NULL gets passed.
  • Try LV_EVENT_VALUE_CHANGED. I think this might fire on both a press and release, though.
1 Like

Wouldn’t lv_btnm_get_active_btn return LV_BTNM_BTN_NONE if no button was “used”? If that were the case, on could:

  1. test if there was an active button
  2. if there was one, retrieve its text.

However, I gather the example uses the text to find the button, so simply using lv_btnm_get_active_btn should do the trick…

Yes, the problem is not so much avoiding the NULL pointer, but avoiding the “bad user feeling” that he clicked on the button, missed it by a bit (landing on the small space between two adjacent buttons) and got no response from the GUI.
It feels like the GUI had a bad behavior, even if he actually was not precise enough, because the buttons are so near, it “seems” strange to miss one.

I mean, something like a “snap to buttons” property for the button matrix would be great!
So the user cannot “miss” a button.

Yes, that would be a rather useful feature. My understanding is that the click position is compared to the clickable areas of the various objects and if it coincindes then thats it. If one wanted near misses, then one could extend the clickable area of every button in the matrix so that they touch. One would have an invisible recangular grid and every button would be drawn in each cell. The cell being the clickable part.

I know how to do that for proper buttons, but here the buttons are created on the fly so I am at a loss.

It’s a good idea to always find the nearest button.

I’ve implemented it in dev-7.0: https://github.com/littlevgl/lvgl/commit/36934dcbbc528b0453862c698f56c5b4eb77566e

1 Like