Inheriting textarea cursor question


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?


What LVGL version are you using?


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),
    .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);
    return obj;

static void lv_timebox_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)

    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-> = 1;
    lv_textarea_set_one_line(obj, true);
    lv_textarea_set_cursor_click_pos(obj, true);


    LV_LOG_TRACE("Timebox constructor finished");
static void lv_timebox_event(const lv_obj_class_t * class_p, lv_event_t * e)

    /*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;


    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) {
        /*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) 
        /*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] == ':') {

            timebox->pos = timebox->ta.cursor.pos;
    else if(code == LV_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*/
            case LV_KEY_RIGHT:
                if(indev_type == LV_INDEV_TYPE_ENCODER)
            case LV_KEY_LEFT:
                if(indev_type == LV_INDEV_TYPE_ENCODER)
            case LV_KEY_UP:
            case LV_KEY_DOWN:
                // lv_textarea_add_char(obj, c);

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->min -= 60;
    while(timebox->min < 0)
        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->      = 1;

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

    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? I’ve set testbox’ 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_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_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_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.

    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);

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.