guido 0.7.0

A reactive Rust GUI library using wgpu for Wayland layer shell widgets
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
# Text Input

The TextInput widget provides single-line text editing with support for selection, clipboard operations, undo/redo, and password masking.

## Basic Usage

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
let username = create_signal(String::new());

text_input(username)
# ;
# }
```

TextInput uses **two-way binding** with signals:
- When the user types, the signal is automatically updated
- When the signal changes programmatically, the input reflects the new value

No manual synchronization is needed - just pass a `Signal<String>` and the binding works automatically.

## Styling

### Text Color

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let value = create_signal(String::new());
container().child(text_input(value).color(Color::WHITE))
# ;
# }
```

### Cursor Color

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let value = create_signal(String::new());
text_input(value).cursor_color(Color::rgb(0.4, 0.8, 1.0))
# ;
# }
```

### Selection Color

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let value = create_signal(String::new());
text_input(value).selection_color(Color::rgba(0.4, 0.6, 1.0, 0.4))
# ;
# }
```

### Font Size

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let value = create_signal(String::new());
container().child(text_input(value).font_size(16.0))
# ;
# }
```

### Font Family

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let value = create_signal(String::new());
// Predefined families
container().child(text_input(value).font_family(FontFamily::Monospace));

// Shorthand for monospace
container().child(text_input(value));

// Custom font
container().child(text_input(value).font_family(FontFamily::name("JetBrains Mono")))
# ;
# }
```

### Font Weight

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let value = create_signal(String::new());
// Using constants
container().child(text_input(value).font_weight(FontWeight::BOLD));

// Shorthand for bold
container().child(text_input(value))
# ;
# }
```

## Password Mode

Hide text input for sensitive data like passwords:

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let password = create_signal(String::new());
text_input(password)
    .password(true)
# ;
# }
```

Masking is a declared value, so it takes a signal — which is what an eye icon
beside the field needs. Declaring it rather than rebuilding the input is what
keeps the caret, the selection and the focus where they were:

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let password = create_signal(String::new());
# let hidden = create_signal(true);
text_input(password).password(hidden)
# ;
# }
```

By default, characters are masked with `•`. Customize the mask character:

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let password = create_signal(String::new());
text_input(password)
    .password(true)
    .mask_char('*')
# ;
# }
```

## Initial Focus

A screen that exists to be typed into should not make the user click first — and
on a surface with no pointer there may be nothing to click *with*:

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let password = create_signal(String::new());
text_input(password)
    .password(true)
    .autofocus()
# ;
# }
```

The input takes the keyboard at its first layout, and only if no widget already
holds focus. That second condition is what makes it safe: an input appearing
later never pulls focus off the one being typed into, two autofocusing inputs do
not fight (the first laid out wins), and the same view built once per output — a
lock screen with two monitors — ends up with one focused field rather than
whichever was laid out last.

The offer is made once. A relayout does not ask again, so a resize or a scale
change cannot drag focus back from wherever the user has since put it.

## Losing Focus

A press that no widget claimed takes the keyboard off the field. Clicking the
surface behind it, or a decorative panel, or empty space in a row: nothing there
wanted the press, so there is nothing left for the focus to belong to, and the
caret stops.

A press that something *did* claim leaves the focus alone. A container with an
`on_click`, `on_mouse_down` or `on_mouse_up` claims the left presses that land
on it, so a toolbar button that acts on the field it sits beside does not blur
it, and neither does the field's own scrollbar. The other two buttons are
claimed by the handlers that name them, `on_right_click` and `on_middle_click`:
a container with only an `on_click` does not claim a right press, and a right
press it did not claim is a press on nothing like any other.

A press inside the focused field is always the field's, whichever button it
was — and it keeps the keyboard without consuming the press, so a row wrapping
the field still gets its own click or context menu.

The box drawn around a field is the third case, and it is the one worth knowing:
a container that declares `when_focused` and currently holds that focus keeps
that focus against a press inside itself. Clicking its padding, its border, the
space beside the caret, is clicking the field it draws.

The press still travels. The box keeps the keyboard and consumes nothing, so a
clickable ancestor wrapping the field is clicked as it would be anywhere else.

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let value = create_signal(String::new());
container()
    .padding(8.0)
    .border(1.0, Color::rgb(0.3, 0.3, 0.4))
    // Lights up for the focus, and keeps it: a click on the padding is a
    // click on the field.
    .when_focused(|s| s.border(2.0, Color::rgb(0.4, 0.8, 1.0)))
    .child(text_input(value))
# ;
# }
```

The focus also leaves when the whole surface does — the compositor moving the
keyboard elsewhere — and when the field is taken out of the tree.

## Placeholder

What the field says while it is empty:

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let password = create_signal(String::new());
text_input(password)
    .password(true)
    .placeholder("Password")
# ;
# }
```

It is a **label, not a value**: never masked, so a password field shows the word
rather than eight bullets, and it stands *in place of* the text rather than beside
it — there is nothing to overlap, because it is only drawn when the value is empty.

Reactive, so a prompt that changes changes the empty field with it:

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let answer = create_signal(String::new());
# let prompt = create_signal(String::from("Type here"));
text_input(answer).placeholder(move || prompt.get())
# ;
# }
```

The colour is the field's own text colour at reduced alpha — a placeholder is the
same text, quieter. Declare `placeholder_color` on the field itself: only a
field draws a placeholder, so only a field says what colour it is.

```rust
# extern crate guido;
# use guido::prelude::*;
# #[derive(Clone, Copy)]
# struct Theme { text: Color, text_weak: Color, weak: Color, strong: Color, line: Color, accent: Color, error: Color, danger: Color, surface: Color }
# impl Default for Theme { fn default() -> Self { let c = Color::WHITE; Self { text: c, text_weak: c, weak: c, strong: c, line: c, accent: c, error: c, danger: c, surface: c } } }
# fn main() {
# let theme = Theme::default();
# let value = create_signal(String::new());
container()
    .child(text_input(value).placeholder_color(theme.text_weak).color(theme.text).placeholder("Search"))
# ;
# }
```

## No Caret

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let password = create_signal(String::new());
text_input(password)
    .password(true)
    .no_caret()
# ;
# }
```

Keeps the focus and everything it carries — click to position, drag to select, the
keyboard — and draws no caret. For a field where the caret says nothing: a masked
one, where every character looks the same and you are always at the end. swaylock
draws none for that reason.

It is also the cheapest field there is. A blinking caret is the one thing a still
screen redraws on its own, twice a second, for as long as it is focused; without
one an idle surface asks the loop for nothing at all.

## Pointer Shape

A field shows the I-beam under the pointer unless it says otherwise, and it takes
the same `cursor` a container does — a value, a closure or a signal:

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let password = create_signal(String::new());
text_input(password)
    .password(true)
    .no_caret()
    .cursor(CursorIcon::Hidden)
# ;
# }
```

A masked field with no caret has nothing a click could place, so on a lock screen
whose root hides the pointer the I-beam would promise editing that is not there.
The field is the innermost widget under the pointer, so a shape declared around it
never reaches it: this is the one place to say it.

## Callbacks

### On Change

Called whenever the text content changes:

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let value = create_signal(String::new());
text_input(value)
    .on_change(|new_text| {
        println!("Text changed: {}", new_text);
    })
# ;
# }
```

### On Submit

Called when the user presses Enter:

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let value = create_signal(String::new());
text_input(value)
    .on_submit(|text| {
        println!("Submitted: {}", text);
    })
# ;
# }
```

## Keyboard Shortcuts

The TextInput widget supports standard text editing shortcuts:

| Shortcut | Action |
|----------|--------|
| `Ctrl+A` | Select all |
| `Ctrl+C` | Copy selection |
| `Ctrl+X` | Cut selection |
| `Ctrl+V` | Paste |
| `Ctrl+Z` | Undo |
| `Ctrl+Shift+Z` or `Ctrl+Y` | Redo |
| `Left/Right` | Move cursor |
| `Ctrl+Left/Right` | Move by word |
| `Shift+Left/Right` | Extend selection |
| `Home/End` | Move to start/end |
| `Backspace` | Delete before cursor |
| `Delete` | Delete after cursor |

## Styling with Container

TextInput handles text editing but not visual styling like backgrounds and borders. Wrap it in a Container for full styling:

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let value = create_signal(String::new());
container()
    .padding([8.0, 12.0])
    .background(Color::rgb(0.15, 0.15, 0.2))
    .border(1.0, Color::rgb(0.3, 0.3, 0.4))
    .corners(4.0)
    .child(
        container().child(text_input(value).font_size(14.0).color(Color::WHITE))
    )
# ;
# }
```

### With Focus State

Add visual feedback when the input is focused:

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let value = create_signal(String::new());
container()
    .padding([8.0, 12.0])
    .background(Color::rgb(0.15, 0.15, 0.2))
    .border(1.0, Color::rgb(0.3, 0.3, 0.4))
    .corners(4.0)
    .when_focused(|s| s.border(1.0, Color::rgb(0.4, 0.6, 1.0)))
    .child(
        container().child(text_input(value).color(Color::WHITE))
    )
# ;
# }
```

## Complete Example

A login form with username and password fields:

```rust
# extern crate guido;
# use guido::prelude::*;
fn login_form() -> Container {
    let username = create_signal(String::new());
    let password = create_signal(String::new());

    container()
        .padding(24.0)
        .background(Color::rgb(0.1, 0.1, 0.15))
        .corners(12.0)
        .layout(Flex::column().spacing(16.0))
        .children([
            // Username field
            container()
                .layout(Flex::column().spacing(4.0))
                .children([
                    container().child(text("Username").font_size(12.0).color(Color::rgb(0.6, 0.6, 0.7))),
                    container()
                        .padding([8.0, 12.0])
                        .background(Color::rgb(0.15, 0.15, 0.2))
                        .border(1.0, Color::rgb(0.3, 0.3, 0.4))
                        .corners(4.0)
                        .when_focused(|s| s.border(1.0, Color::rgb(0.4, 0.6, 1.0)))
                        .child(
                            container().child(text_input(username).font_size(14.0).color(Color::WHITE))
                        ),
                ]),
            // Password field
            container()
                .layout(Flex::column().spacing(4.0))
                .children([
                    container().child(text("Password").font_size(12.0).color(Color::rgb(0.6, 0.6, 0.7))),
                    container()
                        .padding([8.0, 12.0])
                        .background(Color::rgb(0.15, 0.15, 0.2))
                        .border(1.0, Color::rgb(0.3, 0.3, 0.4))
                        .corners(4.0)
                        .when_focused(|s| s.border(1.0, Color::rgb(0.4, 0.6, 1.0)))
                        .child(
                            container().child(text_input(password).font_size(14.0).color(Color::WHITE).password(true))
                        ),
                ]),
            // Submit button
            container()
                .padding([10.0, 16.0])
                .background(Color::rgb(0.3, 0.5, 0.9))
                .corners(6.0)
                .when_hovered(|s| s.lighter(0.1))
                .when_pressed(|s| s.darker(0.1))
                .on_click(move || {
                    println!("Login: {} / {}", username.get(), password.get());
                })
                .child(
                    container().child(text("Sign In").font_size(14.0).color(Color::WHITE))
                ),
        ])
}
# fn main() {}
```

## Features

- **Selection**: Click and drag to select text, or use Shift+Arrow keys
- **Clipboard**: Full copy/cut/paste support via Ctrl+C/X/V. System
  clipboard contents are prefetched asynchronously whenever another app
  copies, so paste is instant and never blocks the UI
- **Primary selection**: selecting text with the mouse sets the primary
  selection (paste it elsewhere with middle click); middle-clicking the
  input pastes the primary selection at the click position
- **Undo/Redo**: History with intelligent coalescing of rapid edits
- **Scrolling**: Long text scrolls horizontally to keep cursor visible
- **Cursor Blinking**: Standard blinking cursor when focused. It asks the loop to
  wake at its next toggle rather than animating, so a focused field costs two
  wakeups a second instead of a frame at the compositor's rate; `no_caret()` drops
  it entirely and costs nothing
- **Key Repeat**: Hold keys for continuous input, at the rate and delay the
  compositor reports — the same settings every other application on the
  session obeys

## API Reference

```rust,ignore
# // not compiled: a signature listing — these declarations have no bodies.
text_input(signal: Signal<String>) -> TextInput

impl TextInput {
    pub fn password<M>(self, enabled: impl IntoSignal<bool, M>) -> Self;
    pub fn mask_char<M>(self, c: impl IntoSignal<char, M>) -> Self;
    pub fn autofocus(self) -> Self;
    pub fn caret<M>(self, caret: impl IntoSignal<bool, M>) -> Self;
    pub fn no_caret(self) -> Self;  // Shorthand for caret(false)
    pub fn cursor<M>(self, cursor: impl IntoSignal<CursorIcon, M>) -> Self;  // Text by default
    pub fn placeholder<M>(self, text: impl IntoSignal<String, M>) -> Self;
    pub fn on_change<F: Fn(&str) + 'static>(self, callback: F) -> Self;
    pub fn on_submit<F: Fn(&str) + 'static>(self, callback: F) -> Self;
}
```

**Note:** The `on_change` callback is optional and is called *in addition* to the automatic signal update. Use it for side effects like validation or logging, not for updating the signal (that happens automatically).