Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Textbox improvements! #57

Open
ryuukk opened this issue Dec 23, 2021 · 3 comments
Open

Textbox improvements! #57

ryuukk opened this issue Dec 23, 2021 · 3 comments

Comments

@ryuukk
Copy link

ryuukk commented Dec 23, 2021

Hi, thanks for this awesome lib!

One thing that i felt was missing was proper textbox navigation, being able to correct a character without having to delete the entire thing

So i spent some time and managed to come up with something:

Peek.2021-12-23.05-28.mp4

Please let me know if you want to see more improvement to the code, so i can submit a PR!

Changes:

struct mu_Context {
  //. ..
  /* input state */
  // ...
  char input_text[32];
+  int input_text_written;
};
 /* reset input state */
  ctx->key_pressed = 0;
  ctx->input_text[0] = '\0';
+  ctx->input_text_written = 0;
void mu_input_text(mu_Context *ctx, const char *text) {
  int len = strlen(ctx->input_text);
  int size = strlen(text) + 1;
  expect(len + size <= (int) sizeof(ctx->input_text));
  memcpy(ctx->input_text + len, text, size);
+  ctx.input_text_written += (size - 1);
}

And here is how the new function looks like:

We basically introduce a new argument to the textbox_raw function
A pointer to an int so we can track the index, and user can specific where the index should start

int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, int *index, mu_Id id, mu_Rect r,
  int opt)
{
  int res = 0;
  mu_update_control(ctx, id, r, opt | MU_OPT_HOLDFOCUS);

  if (ctx->focus == id) {
    /* handle text input */
    int len = strlen(buf);
    int n = mu_min(bufsz - len - 1, (int) strlen(ctx->input_text));
    int i = *index;
    int needMove = len - i;
    if (n > 0) {
      if (needMove > 0) {
        memmove(buf + i + n, buf + i, needMove);
      }
      memcpy(buf + i, ctx->input_text, n);
      len += n;
      buf[len] = '\0';
      res |= MU_RES_CHANGE;
    }
    /* handle backspace */
    if (ctx->key_pressed & MU_KEY_BACKSPACE && len > 0) {
      /* skip utf-8 continuation bytes */
      while ((buf[--len] & 0xc0) == 0x80 && len > 0);
      
      (*index)--;
      memmove(buf + (*index), buf + (*index) + 1, len - (*index) + 1);
      buf[len] = '\0';
      res |= MU_RES_CHANGE;
    }
    /* handle return */
    if (ctx->key_pressed & MU_KEY_RETURN) {
      mu_set_focus(ctx, 0);
      res |= MU_RES_SUBMIT;
    }
    if (ctx.key_pressed & KEY_LEFT)
    {
      (*index)--;
    }
    else if (ctx.key_pressed & KEY_RIGHT)
    {
      (*index)++;
    }
    if ((*index) < 0) (*index) = 0;
    if (*index >= len) (*index) = len;
  } else {
    int len = strlen(buf);
    (*index) = len;
  }

  /* draw */
  mu_draw_control_frame(ctx, id, r, MU_COLOR_BASE, opt);
  if (ctx->focus == id) {
    mu_Color color = ctx->style->colors[MU_COLOR_TEXT];
    mu_Font font = ctx->style->font;
    int textw = ctx->text_width(font, buf, (*index));
    int texth = ctx->text_height(font);
    int ofx = r.w - ctx->style->padding - textw - 1;
    int textx = r.x + mu_min(ofx, ctx->style->padding);
    int texty = r.y + (r.h - texth) / 2;
    mu_push_clip_rect(ctx, r);
    mu_draw_text(ctx, font, buf, -1, mu_vec2(textx, texty), color);
    mu_draw_rect(ctx, mu_rect(textx + textw, texty, 1, texth), color);
    mu_pop_clip_rect(ctx);
  } else {
    mu_draw_control_text(ctx, buf, r, MU_COLOR_TEXT, opt);
  }
  
  (*index)+=ctx->input_text_written;
  return res;
}
@ryuukk
Copy link
Author

ryuukk commented Dec 23, 2021

I have a version without changing the function arguments, isntead i just have a new field in the Context
I also have some basic mouse functionality

That's in D, it's a WIP, i'll port it to C when the PR is ready:

Peek.2021-12-23.21-14.mp4

TODO:

  • fix mouse selection when text has scrolled
  • implement proper selection support
  • add new style for selection colors
  • add new key shortcuts for
    • SHIFT + LEFT/RIGHT move selection
    • CTRL + LEFT/RIGHT for jumping to next block (word/delimiter)
    • CTRL + DEL/BACKSPACE for deleting a block

BONUS:

  • ideally this should support multi line text edits? or it should be a new control: textedit?
  • new copy/paste/cut command?
int textbox_raw(Context *ctx, char *buf, int bufsz, Id id, Rect r, int opt)
{
    Id lastFocus = ctx.focus;

    int res = 0;
    update_control(ctx, id, r, OPT_HOLDFOCUS);

    if (ctx.focus != lastFocus && ctx.focus == id)
    {
        int len = cast(int) str_len(buf);
        ctx.textbox_index = len;
        ctx.textbox_select_min = 0;
        ctx.textbox_select_max = 0;
    }

    if (ctx.focus == id)
    {
        scope(exit) (ctx.textbox_index)+=ctx.input_text_written;

        /* handle text input */
        int len = cast(int) str_len(buf);
        int n = min(bufsz - len - 1, cast(int) str_len(ctx.input_text.ptr));
        int i = ctx.textbox_index;
        int needMove = len - i;

        if (n > 0)
        {
            if (needMove > 0)
            {
                memmove(buf + i + n, buf + i, needMove);
            }
            memcpy(buf + i, ctx.input_text.ptr, n);
            len += n;
            buf[len] = '\0';
            res |= RES_CHANGE;
        }
        /* handle backspace */
        if ((ctx.key_pressed & KEY_BACKSPACE ||  ctx.key_repeated & KEY_BACKSPACE) && len > 0)
        {
            /* skip utf-8 continuation bytes */
            while ((buf[--len] & 0xc0) == 0x80 && len > 0)
            {
            }

            if (i > 0)
            {
                ctx.textbox_index --;
                memmove(buf + (ctx.textbox_index), buf + (ctx.textbox_index) + 1, len - (ctx.textbox_index) + 1);
                buf[len] = '\0';
                res |= RES_CHANGE;
            }
        }
        /* handle backspace */
        if ((ctx.key_pressed & KEY_DELETE ||  ctx.key_repeated & KEY_DELETE) && len > 0)
        {
            if (len == 0 || i == (len+1)) goto skip;

            memmove(buf + (ctx.textbox_index), buf + (ctx.textbox_index) + 1, len - (ctx.textbox_index) + 1);
            buf[len] = '\0';
            res |= RES_CHANGE;
        }

        skip:{}

        if (ctx.mouse_pressed & MOUSE_LEFT && len > 0)
        {
            Font font = ctx.style.font;
            int offsetX = ctx.mouse_pos.x - r.x;
            int textw = ctx.text_width(font, buf, len);
            int textwIndex = ctx.text_width(font, &buf[ctx.textbox_index], len);
            int w = max(textw, textwIndex);

            // click too far, set index to len
            if (offsetX > textw)
            {
                LINFO("sup {} {} {}", offsetX, w, len);
                ctx.textbox_index = len;
            }
            else
            {
                // TODO: doesn't work when text has scrolled..
                // need to figure out a better way to handle this
                // for now i guess that'll do...

                //   aaaaa[aaaaaaaaaaa]aaaaaa
                int num = cast(int) (offsetX / cast(float) w * len);

                // LINFO("les {} {} {}", offsetX, textw, num);
                ctx.textbox_index = num;
            }
        }

        /* handle return */
        if (ctx.key_pressed & KEY_RETURN)
        {
            set_focus(ctx, 0);
            res |= RES_SUBMIT;
        }
        else if (ctx.key_pressed & KEY_LEFT)
        {
            ctx.textbox_index--;
        }
        else if (ctx.key_pressed & KEY_RIGHT)
        {
            ctx.textbox_index ++;
        }
        if ((ctx.textbox_index) < 0) ctx.textbox_index = 0;
        if (ctx.textbox_index >= len) ctx.textbox_index = len;
    }

    /* draw */
    draw_control_frame(ctx, id, r, COLOR_BASE, 0);
    if (ctx.focus == id)
    {
        Color color = ctx.style.colors[COLOR_TEXT];
        Font font = ctx.style.font;
        int textw = ctx.text_width(font, buf, ctx.textbox_index);
        int texth = ctx.text_height(font);
        int ofx = r.w - ctx.style.padding - textw - 1;
        int textx = r.x + min(ofx, ctx.style.padding);
        int texty = r.y + (r.h - texth) / 2;
        push_clip_rect(ctx, r);

        // text
        draw_text(ctx, font, buf, -1, vec2(textx, texty), color);

        // cursor
        draw_rect(ctx, rect(textx + textw, texty, 1, texth), color);

        // draw selection
        if (ctx.textbox_select_min != ctx.textbox_select_max)
        {
            
        }
        

        pop_clip_rect(ctx);
    }
    else
    {
        draw_control_text(ctx, buf, r, COLOR_TEXT, 0);
    }

    return res;
}

@abnercoimbre
Copy link

Hi @ryuukk did you ever finish this work to submit a PR? It would be very useful to have a standard solution for all microui users.

@ericoporto
Copy link

@ryuukk , I am curious about this as well, since I could then port these changes to my port :P

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants