Inheriting textarea cursor question

Description

I’m creating a widget inheriting from textarea. It is a time/date editing field. As a starting point I took the spinbox class and modified it a bit (now only prints the date instead of a text and min/max val properties removed).

But my cursor looks different in each case:

  • If i make a textarea I have a line cursor (makes sense to me, I can see this happening in the code)
  • the spinbox has a blue box cursor (always visible: makes sense as textarea code is not run), I don’t get what makes the cursor blue+box here.
  • my widget has no cursor (and no border).

I don’t get where the properties of the cursor are set. I looked at textarea and I get how it is blinking the cursor for example. But spinbox doesn’t do anything with the cursor properties at all and yet you get a blue boxed cursor. Spinbox also ‘inherits’ the default border as textarea has it.

Why does the cursor disappear in my widget and why is there no border renderer as well? I think I do understand why I don’t see the blinking line from textarea as I’m not running it’s constructor/draw functions/event handler. But I don’t get why spinbox does achieve this without me being to find any code doing this.

Some hints?

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

win11/simulator/vscode/platformio

What LVGL version are you using?

9.1

What do you want to achieve?

inherited a textarea with the same properties as spinbox but with my extra functionality of editing a date.

What have you tried so far?

copied and modified spinbox code

Code to reproduce

That is a little much. I can include the files somewhere.

I can include some relevant parts:

const lv_obj_class_t lv_timebox_class = {
    .constructor_cb = lv_timebox_constructor,
    .event_cb = lv_timebox_event,
    .width_def = LV_DPI_DEF,
    .instance_size = sizeof(lv_timebox_t),
    .editable = LV_OBJ_CLASS_EDITABLE_TRUE,
    .base_class = &lv_textarea_class,
    .name = "timebox",
};

lv_obj_t * lv_timebox_create(lv_obj_t * parent)
{
    lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
    lv_obj_class_init_obj(obj);
    return obj;
}

static void lv_timebox_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
{
    LV_UNUSED(class_p);
    LV_LOG_TRACE("begin");

    lv_timebox_t * timebox = (lv_timebox_t *)obj;

    /*Initialize the allocated 'ext'*/
    timebox->min                = 0;
    timebox->hour               = 0;
    timebox->pos                = 3;
    timebox->digit_step_dir     = LV_DIR_RIGHT;

    timebox->ta.cursor.show = 1;
    
    lv_textarea_set_one_line(obj, true);
    lv_textarea_set_cursor_click_pos(obj, true);

    LV_LOG_INFO("constructor");
    lv_timebox_updatevalue(obj);


    LV_LOG_TRACE("Timebox constructor finished");
}
static void lv_timebox_event(const lv_obj_class_t * class_p, lv_event_t * e)
{
    LV_UNUSED(class_p);
    LV_LOG_INFO("event");

    /*Call the ancestor's event handler*/
    lv_result_t res = LV_RESULT_OK;
    res = lv_obj_event_base(MY_CLASS, e);
    if(res != LV_RESULT_OK) return;

    LV_LOG_INFO("event2");

    const lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * obj = lv_event_get_current_target(e);
    lv_timebox_t * timebox = (lv_timebox_t *)obj;

    if(code == LV_EVENT_RELEASED) {
        LV_LOG_INFO("event:released");
        /*If released with an ENCODER then move to the next digit*/
        lv_indev_t * indev = lv_indev_active();
        if(lv_indev_get_type(indev) == LV_INDEV_TYPE_ENCODER && lv_group_get_editing(lv_obj_get_group(obj))) {
                if(timebox->digit_step_dir == LV_DIR_RIGHT) 
                {
                    lv_timebox_step_next(obj);
                }else{
                    lv_timebox_step_prev(obj);    
                };
        }
        /*The cursor has been positioned to a digit.
         * Set `step` accordingly*/
        else {
            const char * txt = lv_textarea_get_text(obj);
            // const size_t txt_len = lv_strlen(txt);

            /* Check cursor position */
            /* Cursor is in '.' digit */
            if(txt[timebox->ta.cursor.pos] == ':') {
                lv_textarea_cursor_right(obj);
            }

            timebox->pos = timebox->ta.cursor.pos;
        }
    }
    else if(code == LV_EVENT_KEY) {
        LV_LOG_INFO("event:key");
        lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_active());

        uint32_t c = *((uint32_t *)lv_event_get_param(e)); /*uint32_t because can be UTF-8*/
        switch(c)
        {
            case LV_KEY_RIGHT:
                if(indev_type == LV_INDEV_TYPE_ENCODER)
                    lv_timebox_increment(obj);
                else
                    lv_timebox_step_next(obj);
                break;
            case LV_KEY_LEFT:
                if(indev_type == LV_INDEV_TYPE_ENCODER)
                    lv_timebox_decrement(obj);
                else
                    lv_timebox_step_prev(obj);
                break;
            case LV_KEY_UP:
                lv_timebox_increment(obj);
                break;
            case LV_KEY_DOWN:
                lv_timebox_decrement(obj);
                break;
            default:
                // lv_textarea_add_char(obj, c);
                break;
        }
    }
}

static void lv_timebox_updatevalue(lv_obj_t * obj)
{    
    lv_timebox_t * timebox = (lv_timebox_t *)obj;

    // wrap position
    if(timebox->pos > 3)
        timebox->pos = 0;
    if(timebox->pos < 0)
        timebox->pos = 3;

    // wrap hour:min
    while(timebox->min > 59)
    {
        timebox->hour++;
        timebox->min -= 60;
    };
    while(timebox->min < 0)
    {
        timebox->hour--;
        timebox->min += 60;
    };
    while(timebox->hour > 23)
        timebox->hour -= 24;
    while(timebox->hour < 0)
        timebox->hour += 24;

    /*Convert the numbers to string (the sign is already handled so always convert positive number)*/
    char txt[10] = {0U};
    lv_snprintf(txt, 10, "%02" LV_PRId32 ":%02" LV_PRId32, timebox->hour, timebox->min);

    /*Refresh the text*/
    lv_textarea_set_text(obj, (char *)txt);
    timebox->ta.cursor.show      = 1;

    /*Set the cursor position*/
    uint32_t cur_pos = timebox->pos;
    if(timebox->pos > 1) // skip the ':'
        cur_pos++;

    LV_LOG_INFO("pos %d, cursor @ %d", timebox->pos, cur_pos);

    lv_textarea_set_cursor_pos(obj, cur_pos);
};

Screenshot and/or video

If possible, add screenshots and/or videos about the current state.

I’m trying to pinpoint this and I’ve isolated the problem:

I copied lv_spinbox.c/.h to lv_testbox.c/.h (in lvgl/widgets) and renamed the files and class-/funcion names to testbox. If I then instantiate this testbox I also get no border and no blue cursor. Just a label. So what magic is happening that the lv_spinbox (in the LVGL lib dir) renders border+blue cursor but this exact copy doesn’t?

class.name? I’ve set testbox’ class.name back to ‘spinbox’ and still no border/cursor.

This drivers me nuts, why is this happening?

void timetest()
{
    lv_obj_t* parent = lv_layer_top();

    LV_LOG_INFO("BOX:\n");
    lv_obj_t* box = lv_obj_create(parent);
    lv_obj_set_size(box, 300, 200);
    lv_obj_align(box, LV_ALIGN_CENTER, 0, 0);

// Working spinbox:
    LV_LOG_INFO("SpinBox:\n");
    lv_obj_t* mn = lv_spinbox_create(box);
    {
      // lv_obj_set_style_border_width(mn, 0, 0);
      lv_obj_align_to(mn, tb, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 0);
      lv_obj_set_size(mn, 180, 30);
    };

// Not working spinbox, no cursor, no border, not editable (exact same code as spinbox.c, just renamed):
    LV_LOG_INFO("TestBox:\n");
    lv_obj_t* ts = lv_testbox_create(box);
    {
      // lv_obj_set_style_border_width(mn, 0, 0);
      lv_obj_align_to(ts, mn, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 0);
      lv_obj_set_size(ts, 180, 30);
    };

    return MIND_BLOWN;
};

e: actually moved lv_testbox.c/h in lvgl/widgets dir to eliminate this difference as well.

1 Like

You can search for the macro definition LV_USE_SPINBOX. It will have a default style in lv_theme_default.c. When you create a new custom component, you must set the corresponding style yourself.

Ahhh, thank you very much for this! I have been grep-ing for something like this and overlooked it anyway!

I can set these in my own object, but:

But what would you recommend if I were to add my own third-party widgets (I have a lib for this) and I don’t want to force a style on my widget but still have default? Because it is a separate lib I cannot edit lv_theme_default to add my own widget here.

Because I have never used textarea as a parent class to create a custom control, I don’t know your current situation. If you want to delete all styles, you can use lv_obj_remove_style_all(obj) to delete all styles.

Thank you for your answer. What I meant is that I want to achieve the same effect as adding the style properties to lv_theme_default.c (which works! I get a border and cursor), without actually editing lv_theme_defaults.c, as to not modify the original library.
Problem is that the theme->styles handle is not accesible outside lv_theme_default.c, as it is a cast to a private struct (my_theme_t) from lv_theme_t (which does not have ->styles). So I cannot repeat in my own external-to-library widget file the code that is used to style the widget as spinbox does.

#if LV_USE_SPINBOX
    else if(lv_obj_check_type(obj, &lv_spinbox_class)) {
        lv_obj_add_style(obj, &theme->styles.card, 0);
        lv_obj_add_style(obj, &theme->styles.pad_small, 0);
        lv_obj_add_style(obj, &theme->styles.outline_primary, LV_STATE_FOCUS_KEY);
        lv_obj_add_style(obj, &theme->styles.outline_secondary, LV_STATE_EDITED);
        lv_obj_add_style(obj, &theme->styles.bg_color_primary, LV_PART_CURSOR);
    }
#endif

this code decently sets the style params from the default global theme, which seems like to correct way. I can still hardcode my settings of course. But I’m afraid it wouldn’t respond to theme changes then.

But thanks again! This was the problem and even without default_theme adaptation I can at least fix my widget now.