Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • uspace/lib/ui/src/entry.c

    r03145ee rd0dfbba  
    11/*
    2  * Copyright (c) 2020 Jiri Svoboda
     2 * Copyright (c) 2026 Jiri Svoboda
    33 * All rights reserved.
    44 *
     
    3737 */
    3838
     39#include <assert.h>
     40#include <clipboard.h>
    3941#include <errno.h>
    4042#include <gfx/context.h>
     43#include <gfx/cursor.h>
    4144#include <gfx/render.h>
    4245#include <gfx/text.h>
     46#include <macros.h>
     47#include <stdio.h>
    4348#include <stdlib.h>
    4449#include <str.h>
     
    4651#include <ui/paint.h>
    4752#include <ui/entry.h>
     53#include <ui/ui.h>
     54#include <ui/window.h>
    4855#include "../private/entry.h"
    4956#include "../private/resource.h"
     
    5158static void ui_entry_ctl_destroy(void *);
    5259static errno_t ui_entry_ctl_paint(void *);
     60static ui_evclaim_t ui_entry_ctl_kbd_event(void *, kbd_event_t *);
    5361static ui_evclaim_t ui_entry_ctl_pos_event(void *, pos_event_t *);
    5462
    5563enum {
    5664        ui_entry_hpad = 4,
    57         ui_entry_vpad = 4
     65        ui_entry_vpad = 4,
     66        ui_entry_hpad_text = 1,
     67        ui_entry_vpad_text = 0,
     68        ui_entry_cursor_overshoot = 1,
     69        ui_entry_cursor_width = 2,
     70        ui_entry_sel_hpad = 0,
     71        ui_entry_sel_vpad = 2,
     72        /** Additional amount to scroll to the left after revealing cursor */
     73        ui_entry_left_scroll_margin = 30
    5874};
    5975
     
    6278        .destroy = ui_entry_ctl_destroy,
    6379        .paint = ui_entry_ctl_paint,
     80        .kbd_event = ui_entry_ctl_kbd_event,
    6481        .pos_event = ui_entry_ctl_pos_event
    6582};
     
    6784/** Create new text entry.
    6885 *
    69  * @param resource UI resource
     86 * @param window UI window
    7087 * @param text Text
    7188 * @param rentry Place to store pointer to new text entry
    7289 * @return EOK on success, ENOMEM if out of memory
    7390 */
    74 errno_t ui_entry_create(ui_resource_t *resource, const char *text,
     91errno_t ui_entry_create(ui_window_t *window, const char *text,
    7592    ui_entry_t **rentry)
    7693{
     
    95112        }
    96113
    97         entry->res = resource;
     114        entry->window = window;
    98115        entry->halign = gfx_halign_left;
    99116        *rentry = entry;
     117
    100118        return EOK;
    101119}
     
    142160{
    143161        entry->halign = halign;
     162        ui_entry_scroll_update(entry, true);
     163        ui_entry_paint(entry);
     164}
     165
     166/** Set text entry read-only flag.
     167 *
     168 * @param entry Text entry
     169 * @param read_only True iff entry is to be read-only.
     170 */
     171void ui_entry_set_read_only(ui_entry_t *entry, bool read_only)
     172{
     173        entry->read_only = read_only;
    144174}
    145175
     
    160190        free(entry->text);
    161191        entry->text = tcopy;
     192        entry->pos = str_size(text);
     193        entry->sel_start = entry->pos;
     194
     195        ui_entry_scroll_update(entry, false);
     196        ui_entry_paint(entry);
    162197
    163198        return EOK;
    164199}
    165200
     201/** Get entry text.
     202 *
     203 * @return Pointer to entry text.
     204 */
     205const char *ui_entry_get_text(ui_entry_t *entry)
     206{
     207        return entry->text;
     208}
     209
     210/** Paint cursor.
     211 *
     212 * @param entry Text entry
     213 * @param pos Cursor position (top-left corner of next character)
     214 * @return EOK on success or an error code
     215 */
     216static errno_t ui_entry_paint_cursor(ui_entry_t *entry, gfx_coord2_t *pos)
     217{
     218        ui_resource_t *res;
     219        gfx_rect_t rect;
     220        gfx_font_metrics_t metrics;
     221        errno_t rc;
     222
     223        res = ui_window_get_res(entry->window);
     224
     225        if (res->textmode) {
     226                rc = gfx_cursor_set_pos(res->gc, pos);
     227                return rc;
     228        }
     229
     230        gfx_font_get_metrics(res->font, &metrics);
     231
     232        rect.p0.x = pos->x;
     233        rect.p0.y = pos->y - ui_entry_cursor_overshoot;
     234        rect.p1.x = pos->x + ui_entry_cursor_width;
     235        rect.p1.y = pos->y + metrics.ascent + metrics.descent + 1 +
     236            ui_entry_cursor_overshoot;
     237
     238        rc = gfx_set_color(res->gc, res->entry_fg_color);
     239        if (rc != EOK)
     240                goto error;
     241
     242        rc = gfx_fill_rect(res->gc, &rect);
     243        if (rc != EOK)
     244                goto error;
     245
     246        return EOK;
     247error:
     248        return rc;
     249}
     250
     251/** Return width of text before cursor.
     252 *
     253 * @param entry Text entry
     254 * @return Widht of text before cursor
     255 */
     256static gfx_coord_t ui_entry_lwidth(ui_entry_t *entry)
     257{
     258        ui_resource_t *res;
     259        uint8_t tmp;
     260        gfx_coord_t width;
     261
     262        res = ui_window_get_res(entry->window);
     263
     264        tmp = entry->text[entry->pos];
     265
     266        entry->text[entry->pos] = '\0';
     267        width = gfx_text_width(res->font, entry->text);
     268        entry->text[entry->pos] = tmp;
     269
     270        return width;
     271}
     272
    166273/** Paint text entry.
    167274 *
     
    171278errno_t ui_entry_paint(ui_entry_t *entry)
    172279{
     280        ui_resource_t *res;
     281        ui_entry_geom_t geom;
    173282        gfx_text_fmt_t fmt;
    174283        gfx_coord2_t pos;
    175         gfx_rect_t frame;
     284        gfx_text_fmt_t cfmt;
     285        gfx_coord2_t cpos;
    176286        gfx_rect_t inside;
     287        unsigned off1, off2;
     288        gfx_rect_t sel;
     289        char c;
    177290        errno_t rc;
    178291
    179         /* Paint inset frame */
    180 
    181         rc = ui_paint_bevel(entry->res->gc, &entry->rect,
    182             entry->res->wnd_shadow_color, entry->res->wnd_highlight_color,
    183             1, &frame);
     292        res = ui_window_get_res(entry->window);
     293
     294        ui_entry_get_geom(entry, &geom);
     295
     296        if (res->textmode == false) {
     297                /* Paint inset frame */
     298                rc = ui_paint_inset_frame(res, &entry->rect, &inside);
     299                if (rc != EOK)
     300                        goto error;
     301        } else {
     302                inside = entry->rect;
     303        }
     304
     305        /* Paint entry background */
     306
     307        rc = gfx_set_color(res->gc, res->entry_bg_color);
    184308        if (rc != EOK)
    185309                goto error;
    186310
    187         rc = ui_paint_bevel(entry->res->gc, &frame,
    188             entry->res->wnd_frame_sh_color, entry->res->wnd_frame_hi_color,
    189             1, &inside);
     311        rc = gfx_fill_rect(res->gc, &inside);
    190312        if (rc != EOK)
    191313                goto error;
    192314
    193         /* Paint entry background */
    194 
    195         rc = gfx_set_color(entry->res->gc, entry->res->entry_bg_color);
     315        pos = geom.text_pos;
     316
     317        gfx_text_fmt_init(&fmt);
     318        fmt.font = res->font;
     319        fmt.color = res->entry_fg_color;
     320        fmt.halign = gfx_halign_left;
     321        fmt.valign = gfx_valign_top;
     322
     323        rc = gfx_set_clip_rect(res->gc, &inside);
    196324        if (rc != EOK)
    197325                goto error;
    198326
    199         rc = gfx_fill_rect(entry->res->gc, &inside);
     327        off1 = min(entry->pos, entry->sel_start);
     328        off2 = max(entry->pos, entry->sel_start);
     329
     330        /* Render initial segment before start of selection */
     331        c = entry->text[off1];
     332        entry->text[off1] = '\0';
     333
     334        rc = gfx_puttext(&pos, &fmt, entry->text);
     335        if (rc != EOK) {
     336                (void) gfx_set_clip_rect(res->gc, NULL);
     337                goto error;
     338        }
     339
     340        gfx_text_cont(&pos, &fmt, entry->text, &cpos, &cfmt);
     341        entry->text[off1] = c;
     342
     343        /* Render selected text */
     344
     345        if (off1 != off2) {
     346                c = entry->text[off2];
     347                entry->text[off2] = '\0';
     348                cfmt.color = res->entry_sel_text_fg_color;
     349
     350                gfx_text_rect(&cpos, &cfmt, entry->text + off1, &sel);
     351                sel.p0.x -= ui_entry_sel_hpad;
     352                sel.p0.y -= ui_entry_sel_vpad;
     353                sel.p1.x += ui_entry_sel_hpad;
     354                sel.p1.y += ui_entry_sel_vpad;
     355
     356                rc = gfx_set_color(res->gc, res->entry_sel_text_bg_color);
     357                if (rc != EOK)
     358                        goto error;
     359
     360                rc = gfx_fill_rect(res->gc, &sel);
     361                if (rc != EOK)
     362                        goto error;
     363
     364                rc = gfx_puttext(&cpos, &cfmt, entry->text + off1);
     365                if (rc != EOK) {
     366                        (void) gfx_set_clip_rect(res->gc, NULL);
     367                        goto error;
     368                }
     369
     370                gfx_text_cont(&cpos, &cfmt, entry->text + off1, &cpos, &cfmt);
     371
     372                entry->text[off2] = c;
     373        }
     374
     375        /* Render trailing, non-selected text */
     376        cfmt.color = res->entry_fg_color;
     377
     378        rc = gfx_puttext(&cpos, &cfmt, entry->text + off2);
     379        if (rc != EOK) {
     380                (void) gfx_set_clip_rect(res->gc, NULL);
     381                goto error;
     382        }
     383
     384        if (entry->active) {
     385                /* Cursor */
     386                pos.x += ui_entry_lwidth(entry);
     387
     388                rc = ui_entry_paint_cursor(entry, &pos);
     389                if (rc != EOK) {
     390                        (void) gfx_set_clip_rect(res->gc, NULL);
     391                        goto error;
     392                }
     393        }
     394
     395        rc = gfx_set_clip_rect(res->gc, NULL);
    200396        if (rc != EOK)
    201397                goto error;
     398
     399        rc = gfx_update(res->gc);
     400        if (rc != EOK)
     401                goto error;
     402
     403        return EOK;
     404error:
     405        return rc;
     406}
     407
     408/** Find position in text entry.
     409 *
     410 * @param entry Text entry
     411 * @param fpos Position for which we need to find text offset
     412 * @return Corresponding byte offset in entry text
     413 */
     414size_t ui_entry_find_pos(ui_entry_t *entry, gfx_coord2_t *fpos)
     415{
     416        ui_resource_t *res;
     417        ui_entry_geom_t geom;
     418        gfx_text_fmt_t fmt;
     419
     420        res = ui_window_get_res(entry->window);
     421
     422        ui_entry_get_geom(entry, &geom);
     423
     424        gfx_text_fmt_init(&fmt);
     425        fmt.font = res->font;
     426        fmt.halign = gfx_halign_left;
     427        fmt.valign = gfx_valign_top;
     428
     429        return gfx_text_find_pos(&geom.text_pos, &fmt, entry->text, fpos);
     430}
     431
     432/** Destroy text entry control.
     433 *
     434 * @param arg Argument (ui_entry_t *)
     435 */
     436void ui_entry_ctl_destroy(void *arg)
     437{
     438        ui_entry_t *entry = (ui_entry_t *) arg;
     439
     440        ui_entry_destroy(entry);
     441}
     442
     443/** Paint text entry control.
     444 *
     445 * @param arg Argument (ui_entry_t *)
     446 * @return EOK on success or an error code
     447 */
     448errno_t ui_entry_ctl_paint(void *arg)
     449{
     450        ui_entry_t *entry = (ui_entry_t *) arg;
     451
     452        return ui_entry_paint(entry);
     453}
     454
     455/** Delete selected text.
     456 *
     457 * @param entry Text entry
     458 */
     459void ui_entry_delete_sel(ui_entry_t *entry)
     460{
     461        size_t off1;
     462        size_t off2;
     463
     464        off1 = min(entry->sel_start, entry->pos);
     465        off2 = max(entry->sel_start, entry->pos);
     466
     467        memmove(entry->text + off1, entry->text + off2,
     468            str_size(entry->text + off2) + 1);
     469
     470        entry->pos = off1;
     471        entry->sel_start = off1;
     472        ui_entry_scroll_update(entry, false);
     473        ui_entry_paint(entry);
     474}
     475
     476/** Insert string at cursor position.
     477 *
     478 * @param entry Text entry
     479 * @param str String
     480 * @return EOK on success, ENOMEM if out of memory
     481 */
     482errno_t ui_entry_insert_str(ui_entry_t *entry, const char *str)
     483{
     484        uint8_t tmp;
     485        char *ltext = NULL;
     486        char *newtext;
     487        char *oldtext;
     488        int rc;
     489
     490        /* Do we have a selection? */
     491        if (entry->sel_start != entry->pos)
     492                ui_entry_delete_sel(entry);
     493
     494        tmp = entry->text[entry->pos];
     495        entry->text[entry->pos] = '\0';
     496        ltext = str_dup(entry->text);
     497        if (ltext == NULL)
     498                return ENOMEM;
     499
     500        entry->text[entry->pos] = tmp;
     501
     502        rc = asprintf(&newtext, "%s%s%s", ltext, str, entry->text + entry->pos);
     503        if (rc < 0) {
     504                free(ltext);
     505                return ENOMEM;
     506        }
     507
     508        oldtext = entry->text;
     509        entry->text = newtext;
     510        entry->pos += str_size(str);
     511        free(oldtext);
     512        free(ltext);
     513
     514        entry->sel_start = entry->pos;
     515        ui_entry_scroll_update(entry, false);
     516        ui_entry_paint(entry);
     517
     518        return EOK;
     519}
     520
     521/** Delete character before cursor.
     522 *
     523 * @param entry Text entry
     524 */
     525void ui_entry_backspace(ui_entry_t *entry)
     526{
     527        size_t off;
     528
     529        /* Do we have a selection? */
     530        if (entry->sel_start != entry->pos) {
     531                ui_entry_delete_sel(entry);
     532                return;
     533        }
     534
     535        if (entry->pos == 0)
     536                return;
     537
     538        /* Find offset where character before cursor starts */
     539        off = entry->pos;
     540        (void) str_decode_reverse(entry->text, &off,
     541            str_size(entry->text));
     542
     543        memmove(entry->text + off, entry->text + entry->pos,
     544            str_size(entry->text + entry->pos) + 1);
     545        entry->pos = off;
     546        entry->sel_start = off;
     547
     548        ui_entry_scroll_update(entry, false);
     549        ui_entry_paint(entry);
     550}
     551
     552/** Delete character after cursor.
     553 *
     554 * @param entry Text entry
     555 */
     556void ui_entry_delete(ui_entry_t *entry)
     557{
     558        size_t off;
     559
     560        /* Do we have a selection? */
     561        if (entry->sel_start != entry->pos) {
     562                ui_entry_delete_sel(entry);
     563                return;
     564        }
     565
     566        /* Find offset where character after cursor ends */
     567        off = entry->pos;
     568        (void) str_decode(entry->text, &off,
     569            str_size(entry->text));
     570
     571        memmove(entry->text + entry->pos, entry->text + off,
     572            str_size(entry->text + off) + 1);
     573
     574        ui_entry_scroll_update(entry, false);
     575        ui_entry_paint(entry);
     576}
     577
     578/** Copy selected text to clipboard.
     579 *
     580 * @param entry Text entry
     581 */
     582void ui_entry_copy(ui_entry_t *entry)
     583{
     584        unsigned off1;
     585        unsigned off2;
     586        char c;
     587
     588        off1 = min(entry->pos, entry->sel_start);
     589        off2 = max(entry->pos, entry->sel_start);
     590
     591        c = entry->text[off2];
     592        entry->text[off2] = '\0';
     593
     594        (void) clipboard_put_str(entry->text + off1);
     595
     596        entry->text[off2] = c;
     597}
     598
     599/** Cut selected text to clipboard.
     600 *
     601 * @param entry Text entry
     602 */
     603void ui_entry_cut(ui_entry_t *entry)
     604{
     605        ui_entry_copy(entry);
     606        ui_entry_delete_sel(entry);
     607}
     608
     609/** Paste text from clipboard.
     610 *
     611 * @param entry Text entry
     612 */
     613void ui_entry_paste(ui_entry_t *entry)
     614{
     615        char *str;
     616        errno_t rc;
     617
     618        rc = clipboard_get_str(&str);
     619        if (rc != EOK)
     620                return;
     621
     622        ui_entry_insert_str(entry, str);
     623        free(str);
     624}
     625
     626/** Handle text entry key press without modifiers.
     627 *
     628 * @param entry Text entry
     629 * @param kbd_event Keyboard event
     630 * @return @c ui_claimed iff the event is claimed
     631 */
     632ui_evclaim_t ui_entry_key_press_unmod(ui_entry_t *entry, kbd_event_t *event)
     633{
     634        assert(event->type == KEY_PRESS);
     635
     636        switch (event->key) {
     637        case KC_BACKSPACE:
     638                ui_entry_backspace(entry);
     639                break;
     640
     641        case KC_DELETE:
     642                ui_entry_delete(entry);
     643                break;
     644
     645        case KC_ESCAPE:
     646                ui_entry_deactivate(entry);
     647                break;
     648
     649        case KC_HOME:
     650                ui_entry_seek_start(entry, false);
     651                break;
     652
     653        case KC_END:
     654                ui_entry_seek_end(entry, false);
     655                break;
     656
     657        case KC_LEFT:
     658                ui_entry_seek_prev_char(entry, false);
     659                break;
     660
     661        case KC_RIGHT:
     662                ui_entry_seek_next_char(entry, false);
     663                break;
     664
     665        default:
     666                break;
     667        }
     668        return ui_claimed;
     669}
     670
     671/** Handle text entry key press with shift modifier.
     672 *
     673 * @param entry Text entry
     674 * @param kbd_event Keyboard event
     675 * @return @c ui_claimed iff the event is claimed
     676 */
     677ui_evclaim_t ui_entry_key_press_shift(ui_entry_t *entry, kbd_event_t *event)
     678{
     679        assert(event->type == KEY_PRESS);
     680
     681        switch (event->key) {
     682        case KC_HOME:
     683                ui_entry_seek_start(entry, true);
     684                break;
     685
     686        case KC_END:
     687                ui_entry_seek_end(entry, true);
     688                break;
     689
     690        case KC_LEFT:
     691                ui_entry_seek_prev_char(entry, true);
     692                break;
     693
     694        case KC_RIGHT:
     695                ui_entry_seek_next_char(entry, true);
     696                break;
     697
     698        default:
     699                break;
     700        }
     701
     702        return ui_claimed;
     703}
     704
     705/** Handle text entry key press with control modifier.
     706 *
     707 * @param entry Text entry
     708 * @param kbd_event Keyboard event
     709 * @return @c ui_claimed iff the event is claimed
     710 */
     711ui_evclaim_t ui_entry_key_press_ctrl(ui_entry_t *entry, kbd_event_t *event)
     712{
     713        assert(event->type == KEY_PRESS);
     714
     715        switch (event->key) {
     716        case KC_C:
     717                ui_entry_copy(entry);
     718                break;
     719        case KC_V:
     720                ui_entry_paste(entry);
     721                break;
     722        case KC_X:
     723                ui_entry_cut(entry);
     724                break;
     725        default:
     726                break;
     727        }
     728
     729        return ui_claimed;
     730}
     731
     732/** Handle text entry keyboard event.
     733 *
     734 * @param entry Text entry
     735 * @param kbd_event Keyboard event
     736 * @return @c ui_claimed iff the event is claimed
     737 */
     738ui_evclaim_t ui_entry_kbd_event(ui_entry_t *entry, kbd_event_t *event)
     739{
     740        char buf[STR_BOUNDS(1) + 1];
     741        size_t off;
     742        errno_t rc;
     743
     744        if (!entry->active)
     745                return ui_unclaimed;
     746
     747        /*
     748         * Need to keep track if any shift is held for the case
     749         * of selecting by shift-click. This could be simplified
     750         * if position events were decorated with modifier
     751         * state.
     752         */
     753
     754        if (event->type == KEY_PRESS && event->key == KC_LSHIFT)
     755                entry->lshift_held = true;
     756        if (event->type == KEY_RELEASE && event->key == KC_LSHIFT)
     757                entry->lshift_held = false;
     758        if (event->type == KEY_PRESS && event->key == KC_RSHIFT)
     759                entry->rshift_held = true;
     760        if (event->type == KEY_RELEASE && event->key == KC_RSHIFT)
     761                entry->rshift_held = false;
     762
     763        if (event->type == KEY_PRESS &&
     764            (event->mods & (KM_CTRL | KM_ALT)) == 0 && event->c >= ' ') {
     765                off = 0;
     766                rc = chr_encode(event->c, buf, &off, sizeof(buf));
     767                if (rc == EOK) {
     768                        buf[off] = '\0';
     769                        (void) ui_entry_insert_str(entry, buf);
     770                }
     771        }
     772
     773        if (event->type == KEY_PRESS &&
     774            (event->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0)
     775                return ui_entry_key_press_unmod(entry, event);
     776
     777        if (event->type == KEY_PRESS &&
     778            (event->mods & KM_SHIFT) != 0 &&
     779            (event->mods & (KM_CTRL | KM_ALT)) == 0)
     780                return ui_entry_key_press_shift(entry, event);
     781
     782        if (event->type == KEY_PRESS &&
     783            (event->mods & KM_CTRL) != 0 &&
     784            (event->mods & (KM_ALT | KM_SHIFT)) == 0)
     785                return ui_entry_key_press_ctrl(entry, event);
     786
     787        return ui_claimed;
     788}
     789
     790/** Handle text entry position event.
     791 *
     792 * @param entry Text entry
     793 * @param pos_event Position event
     794 * @return @c ui_claimed iff the event is claimed
     795 */
     796ui_evclaim_t ui_entry_pos_event(ui_entry_t *entry, pos_event_t *event)
     797{
     798        gfx_coord2_t pos;
     799
     800        if (entry->read_only)
     801                return ui_unclaimed;
     802
     803        if (event->type == POS_UPDATE) {
     804                pos.x = event->hpos;
     805                pos.y = event->vpos;
     806
     807                /* Change cursor shape when pointer is entering/leaving */
     808                if (gfx_pix_inside_rect(&pos, &entry->rect)) {
     809                        if (!entry->pointer_inside) {
     810                                ui_window_set_ctl_cursor(entry->window,
     811                                    ui_curs_ibeam);
     812                                entry->pointer_inside = true;
     813                        }
     814                } else {
     815                        if (entry->pointer_inside) {
     816                                ui_window_set_ctl_cursor(entry->window,
     817                                    ui_curs_arrow);
     818                                entry->pointer_inside = false;
     819                        }
     820                }
     821
     822                if (entry->held) {
     823                        /*
     824                         * Selecting using mouse drag: Change pos,
     825                         * keep sel_start
     826                         */
     827                        entry->pos = ui_entry_find_pos(entry, &pos);
     828                        ui_entry_paint(entry);
     829                }
     830        }
     831
     832        if (event->type == POS_PRESS) {
     833                pos.x = event->hpos;
     834                pos.y = event->vpos;
     835
     836                if (gfx_pix_inside_rect(&pos, &entry->rect)) {
     837                        /* Clicked inside - activate, set position */
     838                        entry->held = true;
     839                        entry->pos = ui_entry_find_pos(entry, &pos);
     840
     841                        /* Clear selection if no shift key is held */
     842                        if (!entry->lshift_held && !entry->rshift_held)
     843                                entry->sel_start = entry->pos;
     844
     845                        if (entry->active)
     846                                ui_entry_paint(entry);
     847                        else
     848                                ui_entry_activate(entry);
     849
     850                        return ui_claimed;
     851                } else {
     852                        /* Clicked outside - deactivate */
     853                        ui_entry_deactivate(entry);
     854                }
     855        }
     856
     857        if (event->type == POS_RELEASE) {
     858                entry->held = false;
     859        }
     860
     861        return ui_unclaimed;
     862}
     863
     864/** Handle text entry control keyboard event.
     865 *
     866 * @param arg Argument (ui_entry_t *)
     867 * @param kbd_event Keyboard event
     868 * @return @c ui_claimed iff the event is claimed
     869 */
     870static ui_evclaim_t ui_entry_ctl_kbd_event(void *arg, kbd_event_t *event)
     871{
     872        ui_entry_t *entry = (ui_entry_t *) arg;
     873
     874        return ui_entry_kbd_event(entry, event);
     875}
     876
     877/** Handle text entry control position event.
     878 *
     879 * @param arg Argument (ui_entry_t *)
     880 * @param pos_event Position event
     881 * @return @c ui_claimed iff the event is claimed
     882 */
     883static ui_evclaim_t ui_entry_ctl_pos_event(void *arg, pos_event_t *event)
     884{
     885        ui_entry_t *entry = (ui_entry_t *) arg;
     886
     887        return ui_entry_pos_event(entry, event);
     888}
     889
     890/** Get text entry geometry.
     891 *
     892 * @param entry Text entry
     893 * @param geom Structure to fill in with computed geometry
     894 */
     895void ui_entry_get_geom(ui_entry_t *entry, ui_entry_geom_t *geom)
     896{
     897        gfx_coord_t hpad;
     898        gfx_coord_t vpad;
     899        ui_resource_t *res;
     900
     901        res = ui_window_get_res(entry->window);
     902
     903        if (res->textmode) {
     904                hpad = ui_entry_hpad_text;
     905                vpad = ui_entry_vpad_text;
     906        } else {
     907                hpad = ui_entry_hpad;
     908                vpad = ui_entry_vpad;
     909        }
     910
     911        if (res->textmode == false) {
     912                ui_paint_get_inset_frame_inside(res, &entry->rect,
     913                    &geom->interior_rect);
     914        } else {
     915                geom->interior_rect = entry->rect;
     916        }
     917
     918        geom->text_rect.p0.x = geom->interior_rect.p0.x + hpad;
     919        geom->text_rect.p0.y = geom->interior_rect.p0.y + vpad;
     920        geom->text_rect.p1.x = geom->interior_rect.p1.x - hpad;
     921        geom->text_rect.p1.y = geom->interior_rect.p1.y - vpad;
     922
     923        geom->text_pos.x = geom->interior_rect.p0.x + hpad +
     924            entry->scroll_pos;
     925        geom->text_pos.y = geom->interior_rect.p0.y + vpad;
    202926
    203927        switch (entry->halign) {
    204928        case gfx_halign_left:
    205929        case gfx_halign_justify:
    206                 pos.x = inside.p0.x + ui_entry_hpad;
     930                geom->anchor_x = geom->text_rect.p0.x;
    207931                break;
    208932        case gfx_halign_center:
    209                 pos.x = (inside.p0.x + inside.p1.x) / 2;
     933                geom->anchor_x = (geom->text_rect.p0.x +
     934                    geom->text_rect.p1.x) / 2;
    210935                break;
    211936        case gfx_halign_right:
    212                 pos.x = inside.p1.x - ui_entry_hpad;
    213                 break;
    214         }
    215 
    216         pos.y = inside.p0.y + ui_entry_vpad;
    217 
    218         gfx_text_fmt_init(&fmt);
    219         fmt.halign = entry->halign;
    220         fmt.valign = gfx_valign_top;
    221 
    222         rc = gfx_set_color(entry->res->gc, entry->res->entry_fg_color);
    223         if (rc != EOK)
    224                 goto error;
    225 
    226         rc = gfx_puttext(entry->res->font, &pos, &fmt, entry->text);
    227         if (rc != EOK)
    228                 goto error;
    229 
    230         return EOK;
    231 error:
    232         return rc;
    233 }
    234 
    235 /** Destroy text entry control.
    236  *
    237  * @param arg Argument (ui_entry_t *)
    238  */
    239 void ui_entry_ctl_destroy(void *arg)
    240 {
    241         ui_entry_t *entry = (ui_entry_t *) arg;
    242 
    243         ui_entry_destroy(entry);
    244 }
    245 
    246 /** Paint text entry control.
    247  *
    248  * @param arg Argument (ui_entry_t *)
    249  * @return EOK on success or an error code
    250  */
    251 errno_t ui_entry_ctl_paint(void *arg)
    252 {
    253         ui_entry_t *entry = (ui_entry_t *) arg;
    254 
    255         return ui_entry_paint(entry);
    256 }
    257 
    258 /** Handle text entry control position event.
    259  *
    260  * @param arg Argument (ui_entry_t *)
    261  * @param pos_event Position event
    262  * @return @c ui_claimed iff the event is claimed
    263  */
    264 ui_evclaim_t ui_entry_ctl_pos_event(void *arg, pos_event_t *event)
    265 {
    266         ui_entry_t *entry = (ui_entry_t *) arg;
    267 
    268         (void) entry;
    269         return ui_unclaimed;
     937                geom->anchor_x = geom->text_rect.p1.x;
     938                break;
     939        }
     940}
     941
     942/** Activate text entry.
     943 *
     944 * @param entry Text entry
     945 */
     946void ui_entry_activate(ui_entry_t *entry)
     947{
     948        ui_resource_t *res;
     949
     950        res = ui_window_get_res(entry->window);
     951
     952        if (entry->active)
     953                return;
     954
     955        entry->active = true;
     956        (void) ui_entry_paint(entry);
     957
     958        if (res->textmode)
     959                gfx_cursor_set_visible(res->gc, true);
     960}
     961
     962/** Move text cursor to the beginning of text.
     963 *
     964 * @param entry Text entry
     965 * @param shift @c true iff shift key is pressed
     966 */
     967void ui_entry_seek_start(ui_entry_t *entry, bool shift)
     968{
     969        entry->pos = 0;
     970
     971        if (!shift)
     972                entry->sel_start = entry->pos;
     973
     974        ui_entry_scroll_update(entry, false);
     975        (void) ui_entry_paint(entry);
     976}
     977
     978/** Move text cursor to the end of text.
     979 *
     980 * @param entry Text entry
     981 * @param shift @c true iff shift key is pressed
     982 */
     983void ui_entry_seek_end(ui_entry_t *entry, bool shift)
     984{
     985        entry->pos = str_size(entry->text);
     986
     987        if (!shift)
     988                entry->sel_start = entry->pos;
     989
     990        ui_entry_scroll_update(entry, false);
     991        (void) ui_entry_paint(entry);
     992}
     993
     994/** Move text cursor one character backward.
     995 *
     996 * @param entry Text entry
     997 * @param shift @c true iff shift key is pressed
     998 */
     999void ui_entry_seek_prev_char(ui_entry_t *entry, bool shift)
     1000{
     1001        size_t off;
     1002
     1003        off = entry->pos;
     1004        (void) str_decode_reverse(entry->text, &off,
     1005            str_size(entry->text));
     1006        entry->pos = off;
     1007
     1008        if (!shift)
     1009                entry->sel_start = entry->pos;
     1010
     1011        ui_entry_scroll_update(entry, false);
     1012        (void) ui_entry_paint(entry);
     1013}
     1014
     1015/** Move text cursor one character forward.
     1016 *
     1017 * @param entry Text entry
     1018 * @param shift @c true iff shift key is pressed
     1019 */
     1020void ui_entry_seek_next_char(ui_entry_t *entry, bool shift)
     1021{
     1022        size_t off;
     1023
     1024        off = entry->pos;
     1025        (void) str_decode(entry->text, &off,
     1026            str_size(entry->text));
     1027        entry->pos = off;
     1028
     1029        if (!shift)
     1030                entry->sel_start = entry->pos;
     1031
     1032        ui_entry_scroll_update(entry, false);
     1033        (void) ui_entry_paint(entry);
     1034}
     1035
     1036/** Deactivate text entry.
     1037 *
     1038 * @param entry Text entry
     1039 */
     1040void ui_entry_deactivate(ui_entry_t *entry)
     1041{
     1042        ui_resource_t *res;
     1043
     1044        res = ui_window_get_res(entry->window);
     1045
     1046        if (!entry->active)
     1047                return;
     1048
     1049        entry->active = false;
     1050        entry->sel_start = entry->pos;
     1051        (void) ui_entry_paint(entry);
     1052
     1053        if (res->textmode)
     1054                gfx_cursor_set_visible(res->gc, false);
     1055}
     1056
     1057/** Update text entry scroll position.
     1058 *
     1059 * @param entry Text entry
     1060 * @param realign @c true iff we should left-align short text.
     1061 *                This should be only used when changing text alignment,
     1062 *                because left-aligned text entries should not realign
     1063 *                the text to the left side under normal circumstances.
     1064 */
     1065void ui_entry_scroll_update(ui_entry_t *entry, bool realign)
     1066{
     1067        ui_entry_geom_t geom;
     1068        gfx_coord_t x;
     1069        gfx_coord_t width;
     1070        gfx_coord2_t tpos;
     1071        gfx_coord2_t anchor;
     1072        gfx_text_fmt_t fmt;
     1073        ui_resource_t *res;
     1074
     1075        res = ui_window_get_res(entry->window);
     1076
     1077        ui_entry_get_geom(entry, &geom);
     1078
     1079        /* Compute position where cursor is currently displayed at */
     1080        x = geom.text_pos.x + ui_entry_lwidth(entry);
     1081
     1082        /* Is cursor off to the left? */
     1083        if (x < geom.text_rect.p0.x) {
     1084                /*
     1085                 * Scroll to make cursor visible and put some space between it
     1086                 * and the left edge of the text rectangle.
     1087                 */
     1088                entry->scroll_pos += geom.text_rect.p0.x - x +
     1089                    ui_entry_left_scroll_margin;
     1090
     1091                /*
     1092                 * We don't want to scroll further than what's needed
     1093                 * to reveal the beginning of the text.
     1094                 */
     1095                if (entry->scroll_pos > 0)
     1096                        entry->scroll_pos = 0;
     1097        }
     1098
     1099        /*
     1100         * Is cursor off to the right? Note that the width of the cursor
     1101         * is deliberately not taken into account (i.e. we only care
     1102         * about the left edge of the cursor).
     1103         */
     1104        if (x > geom.text_rect.p1.x)
     1105                entry->scroll_pos -= x - geom.text_rect.p1.x;
     1106
     1107        width = gfx_text_width(res->font, entry->text);
     1108
     1109        if (width < geom.text_rect.p1.x - geom.text_rect.p0.x &&
     1110            (realign || entry->halign != gfx_halign_left)) {
     1111                /* Text fits inside entry, so we need to align it */
     1112                anchor.x = geom.anchor_x;
     1113                anchor.y = 0;
     1114                gfx_text_fmt_init(&fmt);
     1115                fmt.font = res->font;
     1116                fmt.halign = entry->halign;
     1117                gfx_text_start_pos(&anchor, &fmt, entry->text, &tpos);
     1118                entry->scroll_pos = tpos.x - geom.text_rect.p0.x;
     1119        } else if (geom.text_pos.x + width < geom.text_rect.p1.x &&
     1120            entry->halign != gfx_halign_left) {
     1121                /* Text is long, unused space on the right */
     1122                entry->scroll_pos += geom.text_rect.p1.x -
     1123                    geom.text_pos.x - width;
     1124        }
    2701125}
    2711126
Note: See TracChangeset for help on using the changeset viewer.