source: mainline/uspace/lib/ui/src/entry.c@ 1215db9

serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 1215db9 was bb14312, checked in by Jiri Svoboda <jiri@…>, 4 years ago

Use hardware cursor in text mode

We extend GC with cursor control operations. This will also allow to
control the HW cursor when running display server in text mode in
the future (provided that we implement the missing bits in the rest
of the stack, i.e. in IPC GC and in the display server).

  • Property mode set to 100644
File size: 11.7 KB
Line 
1/*
2 * Copyright (c) 2021 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup libui
30 * @{
31 */
32/**
33 * @file Text entry.
34 *
35 * Currentry text entry is always read-only. It differs from label mostly
36 * by its looks.
37 */
38
39#include <errno.h>
40#include <gfx/context.h>
41#include <gfx/cursor.h>
42#include <gfx/render.h>
43#include <gfx/text.h>
44#include <stdlib.h>
45#include <str.h>
46#include <ui/control.h>
47#include <ui/paint.h>
48#include <ui/entry.h>
49#include <ui/ui.h>
50#include <ui/window.h>
51#include "../private/entry.h"
52#include "../private/resource.h"
53
54static void ui_entry_ctl_destroy(void *);
55static errno_t ui_entry_ctl_paint(void *);
56static ui_evclaim_t ui_entry_ctl_kbd_event(void *, kbd_event_t *);
57static ui_evclaim_t ui_entry_ctl_pos_event(void *, pos_event_t *);
58
59enum {
60 ui_entry_hpad = 4,
61 ui_entry_vpad = 4,
62 ui_entry_hpad_text = 1,
63 ui_entry_vpad_text = 0,
64 ui_entry_cursor_overshoot = 1,
65 ui_entry_cursor_width = 2
66};
67
68/** Text entry control ops */
69ui_control_ops_t ui_entry_ops = {
70 .destroy = ui_entry_ctl_destroy,
71 .paint = ui_entry_ctl_paint,
72 .kbd_event = ui_entry_ctl_kbd_event,
73 .pos_event = ui_entry_ctl_pos_event
74};
75
76/** Create new text entry.
77 *
78 * @param window UI window
79 * @param text Text
80 * @param rentry Place to store pointer to new text entry
81 * @return EOK on success, ENOMEM if out of memory
82 */
83errno_t ui_entry_create(ui_window_t *window, const char *text,
84 ui_entry_t **rentry)
85{
86 ui_entry_t *entry;
87 errno_t rc;
88
89 entry = calloc(1, sizeof(ui_entry_t));
90 if (entry == NULL)
91 return ENOMEM;
92
93 rc = ui_control_new(&ui_entry_ops, (void *) entry, &entry->control);
94 if (rc != EOK) {
95 free(entry);
96 return rc;
97 }
98
99 entry->text = str_dup(text);
100 if (entry->text == NULL) {
101 ui_control_delete(entry->control);
102 free(entry);
103 return ENOMEM;
104 }
105
106 entry->window = window;
107 entry->halign = gfx_halign_left;
108 *rentry = entry;
109
110 return EOK;
111}
112
113/** Destroy text entry.
114 *
115 * @param entry Text entry or @c NULL
116 */
117void ui_entry_destroy(ui_entry_t *entry)
118{
119 if (entry == NULL)
120 return;
121
122 ui_control_delete(entry->control);
123 free(entry);
124}
125
126/** Get base control from text entry.
127 *
128 * @param entry Text entry
129 * @return Control
130 */
131ui_control_t *ui_entry_ctl(ui_entry_t *entry)
132{
133 return entry->control;
134}
135
136/** Set text entry rectangle.
137 *
138 * @param entry Text entry
139 * @param rect New text entry rectangle
140 */
141void ui_entry_set_rect(ui_entry_t *entry, gfx_rect_t *rect)
142{
143 entry->rect = *rect;
144}
145
146/** Set text entry horizontal text alignment.
147 *
148 * @param entry Text entry
149 * @param halign Horizontal alignment
150 */
151void ui_entry_set_halign(ui_entry_t *entry, gfx_halign_t halign)
152{
153 entry->halign = halign;
154}
155
156/** Set text entry read-only flag.
157 *
158 * @param entry Text entry
159 * @param read_only True iff entry is to be read-only.
160 */
161void ui_entry_set_read_only(ui_entry_t *entry, bool read_only)
162{
163 entry->read_only = read_only;
164}
165
166/** Set entry text.
167 *
168 * @param entry Text entry
169 * @param text New entry text
170 * @return EOK on success, ENOMEM if out of memory
171 */
172errno_t ui_entry_set_text(ui_entry_t *entry, const char *text)
173{
174 char *tcopy;
175
176 tcopy = str_dup(text);
177 if (tcopy == NULL)
178 return ENOMEM;
179
180 free(entry->text);
181 entry->text = tcopy;
182
183 return EOK;
184}
185
186/** Paint cursor.
187 *
188 * @param entry Text entry
189 * @param pos Cursor position (top-left corner of next character)
190 * @return EOK on success or an error code
191 */
192static errno_t ui_entry_paint_cursor(ui_entry_t *entry, gfx_coord2_t *pos)
193{
194 ui_resource_t *res;
195 gfx_rect_t rect;
196 gfx_font_metrics_t metrics;
197 errno_t rc;
198
199 res = ui_window_get_res(entry->window);
200
201 if (res->textmode) {
202 rc = gfx_cursor_set_pos(res->gc, pos);
203 return rc;
204 }
205
206 gfx_font_get_metrics(res->font, &metrics);
207
208 rect.p0.x = pos->x;
209 rect.p0.y = pos->y - ui_entry_cursor_overshoot;
210 rect.p1.x = pos->x + ui_entry_cursor_width;
211 rect.p1.y = pos->y + metrics.ascent + metrics.descent + 1 +
212 ui_entry_cursor_overshoot;
213
214 rc = gfx_set_color(res->gc, res->entry_fg_color);
215 if (rc != EOK)
216 goto error;
217
218 rc = gfx_fill_rect(res->gc, &rect);
219 if (rc != EOK)
220 goto error;
221
222 return EOK;
223error:
224 return rc;
225}
226
227/** Paint text entry.
228 *
229 * @param entry Text entry
230 * @return EOK on success or an error code
231 */
232errno_t ui_entry_paint(ui_entry_t *entry)
233{
234 ui_resource_t *res;
235 gfx_text_fmt_t fmt;
236 gfx_coord2_t pos;
237 gfx_coord_t hpad;
238 gfx_coord_t vpad;
239 gfx_coord_t width;
240 gfx_rect_t inside;
241 errno_t rc;
242
243 res = ui_window_get_res(entry->window);
244
245 if (res->textmode) {
246 hpad = ui_entry_hpad_text;
247 vpad = ui_entry_vpad_text;
248 } else {
249 hpad = ui_entry_hpad;
250 vpad = ui_entry_vpad;
251 }
252
253 if (res->textmode == false) {
254 /* Paint inset frame */
255 rc = ui_paint_inset_frame(res, &entry->rect, &inside);
256 if (rc != EOK)
257 goto error;
258 } else {
259 inside = entry->rect;
260 }
261
262 /* Paint entry background */
263
264 rc = gfx_set_color(res->gc, res->entry_bg_color);
265 if (rc != EOK)
266 goto error;
267
268 rc = gfx_fill_rect(res->gc, &inside);
269 if (rc != EOK)
270 goto error;
271
272 width = gfx_text_width(res->font, entry->text);
273
274 switch (entry->halign) {
275 case gfx_halign_left:
276 case gfx_halign_justify:
277 pos.x = inside.p0.x + hpad;
278 break;
279 case gfx_halign_center:
280 pos.x = (inside.p0.x + inside.p1.x) / 2 - width / 2;
281 break;
282 case gfx_halign_right:
283 pos.x = inside.p1.x - hpad - 1 - width;
284 break;
285 }
286
287 pos.y = inside.p0.y + vpad;
288
289 gfx_text_fmt_init(&fmt);
290 fmt.color = res->entry_fg_color;
291 fmt.halign = gfx_halign_left;
292 fmt.valign = gfx_valign_top;
293
294 rc = gfx_set_clip_rect(res->gc, &inside);
295 if (rc != EOK)
296 goto error;
297
298 rc = gfx_puttext(res->font, &pos, &fmt, entry->text);
299 if (rc != EOK) {
300 (void) gfx_set_clip_rect(res->gc, NULL);
301 goto error;
302 }
303
304 if (entry->active) {
305 /* Cursor */
306 pos.x += width;
307
308 rc = ui_entry_paint_cursor(entry, &pos);
309 if (rc != EOK) {
310 (void) gfx_set_clip_rect(res->gc, NULL);
311 goto error;
312 }
313 }
314
315 rc = gfx_set_clip_rect(res->gc, NULL);
316 if (rc != EOK)
317 goto error;
318
319 rc = gfx_update(res->gc);
320 if (rc != EOK)
321 goto error;
322
323 return EOK;
324error:
325 return rc;
326}
327
328/** Destroy text entry control.
329 *
330 * @param arg Argument (ui_entry_t *)
331 */
332void ui_entry_ctl_destroy(void *arg)
333{
334 ui_entry_t *entry = (ui_entry_t *) arg;
335
336 ui_entry_destroy(entry);
337}
338
339/** Paint text entry control.
340 *
341 * @param arg Argument (ui_entry_t *)
342 * @return EOK on success or an error code
343 */
344errno_t ui_entry_ctl_paint(void *arg)
345{
346 ui_entry_t *entry = (ui_entry_t *) arg;
347
348 return ui_entry_paint(entry);
349}
350
351/** Insert string at cursor position.
352 *
353 * @param entry Text entry
354 * @param str String
355 * @return EOK on success, ENOMEM if out of memory
356 */
357errno_t ui_entry_insert_str(ui_entry_t *entry, const char *str)
358{
359 char *newtext;
360 char *oldtext;
361 int rc;
362
363 rc = asprintf(&newtext, "%s%s", entry->text, str);
364 if (rc < 0)
365 return ENOMEM;
366
367 oldtext = entry->text;
368 entry->text = newtext;
369 free(oldtext);
370 ui_entry_paint(entry);
371
372 return EOK;
373}
374
375/** Delete character before cursor.
376 *
377 * @param entry Text entry
378 */
379void ui_entry_backspace(ui_entry_t *entry)
380{
381 size_t off;
382
383 off = str_size(entry->text);
384 (void) str_decode_reverse(entry->text, &off,
385 str_size(entry->text));
386 entry->text[off] = '\0';
387 ui_entry_paint(entry);
388}
389
390/** Handle text entry key press without modifiers.
391 *
392 * @param entry Text entry
393 * @param kbd_event Keyboard event
394 * @return @c ui_claimed iff the event is claimed
395 */
396ui_evclaim_t ui_entry_key_press_unmod(ui_entry_t *entry, kbd_event_t *event)
397{
398 assert(event->type == KEY_PRESS);
399
400 if (event->key == KC_BACKSPACE)
401 ui_entry_backspace(entry);
402
403 if (event->key == KC_ESCAPE)
404 ui_entry_deactivate(entry);
405
406 return ui_claimed;
407}
408
409/** Handle text entry keyboard event.
410 *
411 * @param entry Text entry
412 * @param kbd_event Keyboard event
413 * @return @c ui_claimed iff the event is claimed
414 */
415ui_evclaim_t ui_entry_kbd_event(ui_entry_t *entry, kbd_event_t *event)
416{
417 char buf[STR_BOUNDS(1) + 1];
418 size_t off;
419 errno_t rc;
420
421 if (!entry->active)
422 return ui_unclaimed;
423
424 if (event->type == KEY_PRESS && event->c >= ' ') {
425 off = 0;
426 rc = chr_encode(event->c, buf, &off, sizeof(buf));
427 if (rc == EOK) {
428 buf[off] = '\0';
429 (void) ui_entry_insert_str(entry, buf);
430 }
431 }
432
433 if (event->type == KEY_PRESS &&
434 (event->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0)
435 return ui_entry_key_press_unmod(entry, event);
436
437 return ui_claimed;
438}
439
440/** Handle text entry position event.
441 *
442 * @param entry Text entry
443 * @param pos_event Position event
444 * @return @c ui_claimed iff the event is claimed
445 */
446ui_evclaim_t ui_entry_pos_event(ui_entry_t *entry, pos_event_t *event)
447{
448 gfx_coord2_t pos;
449
450 if (entry->read_only)
451 return ui_unclaimed;
452
453 if (event->type == POS_UPDATE) {
454 pos.x = event->hpos;
455 pos.y = event->vpos;
456
457 if (gfx_pix_inside_rect(&pos, &entry->rect)) {
458 if (!entry->pointer_inside) {
459 ui_window_set_ctl_cursor(entry->window,
460 ui_curs_ibeam);
461 entry->pointer_inside = true;
462 }
463 } else {
464 if (entry->pointer_inside) {
465 ui_window_set_ctl_cursor(entry->window,
466 ui_curs_arrow);
467 entry->pointer_inside = false;
468 }
469 }
470 }
471
472 if (event->type == POS_PRESS) {
473 pos.x = event->hpos;
474 pos.y = event->vpos;
475
476 if (gfx_pix_inside_rect(&pos, &entry->rect)) {
477 ui_entry_activate(entry);
478
479 return ui_claimed;
480 } else {
481 ui_entry_deactivate(entry);
482 }
483 }
484
485 return ui_unclaimed;
486}
487
488/** Handle text entry control keyboard event.
489 *
490 * @param arg Argument (ui_entry_t *)
491 * @param kbd_event Keyboard event
492 * @return @c ui_claimed iff the event is claimed
493 */
494static ui_evclaim_t ui_entry_ctl_kbd_event(void *arg, kbd_event_t *event)
495{
496 ui_entry_t *entry = (ui_entry_t *) arg;
497
498 return ui_entry_kbd_event(entry, event);
499}
500
501/** Handle text entry control position event.
502 *
503 * @param arg Argument (ui_entry_t *)
504 * @param pos_event Position event
505 * @return @c ui_claimed iff the event is claimed
506 */
507static ui_evclaim_t ui_entry_ctl_pos_event(void *arg, pos_event_t *event)
508{
509 ui_entry_t *entry = (ui_entry_t *) arg;
510
511 return ui_entry_pos_event(entry, event);
512}
513
514/** Activate text entry.
515 *
516 * @param entry Text entry
517 */
518void ui_entry_activate(ui_entry_t *entry)
519{
520 ui_resource_t *res;
521
522 res = ui_window_get_res(entry->window);
523
524 if (entry->active)
525 return;
526
527 entry->active = true;
528 (void) ui_entry_paint(entry);
529
530 if (res->textmode)
531 gfx_cursor_set_visible(res->gc, true);
532}
533
534/** Deactivate text entry.
535 *
536 * @param entry Text entry
537 */
538void ui_entry_deactivate(ui_entry_t *entry)
539{
540 ui_resource_t *res;
541
542 res = ui_window_get_res(entry->window);
543
544 if (!entry->active)
545 return;
546
547 entry->active = false;
548 (void) ui_entry_paint(entry);
549
550 if (res->textmode)
551 gfx_cursor_set_visible(res->gc, false);
552}
553
554/** @}
555 */
Note: See TracBrowser for help on using the repository browser.