1package text_edit_example
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
31import "core:strings"
32import "core:text/edit"
33import "core:unicode/utf8"
34import rl "vendor:raylib"
35
36WINDOW_WIDTH :: 800
37WINDOW_HEIGHT :: 200
38
39FONT_SIZE :: 30
40FONT_SPACING :: 2
41HINT_FONT_SIZE :: 20
42
43FIELD_PADDING :: 10
44FIELD_X :: 40
45FIELD_Y :: 80
46FIELD_W :: WINDOW_WIDTH - 2 * FIELD_X
47FIELD_H :: FONT_SIZE + 2 * FIELD_PADDING
48
49TEXT_X :: FIELD_X + FIELD_PADDING
50TEXT_Y :: FIELD_Y + FIELD_PADDING
51HINT_Y :: 40
52
53CARET_WIDTH :: 2
54CARET_BLINK_RATE :: 0.5
55
56COLOR_BACKGROUND :: rl.Color{ 30, 30, 35, 255}
57COLOR_FIELD :: rl.Color{ 20, 20, 24, 255}
58COLOR_BORDER :: rl.Color{ 90, 90, 100, 255}
59COLOR_SELECTION :: rl.Color{ 60, 90, 150, 255}
60COLOR_HINT :: rl.Color{140, 140, 150, 255}
61
62Key_Binding :: struct {
63 key: rl.KeyboardKey,
64 ctrl: bool,
65 shift: bool,
66 cmd: edit.Command,
67}
68
69
70
71
72KEY_BINDINGS :: []Key_Binding{
73 {.LEFT, false, false, .Left},
74 {.LEFT, false, true, .Select_Left},
75 {.LEFT, true, false, .Word_Left},
76 {.LEFT, true, true, .Select_Word_Left},
77 {.RIGHT, false, false, .Right},
78 {.RIGHT, false, true, .Select_Right},
79 {.RIGHT, true, false, .Word_Right},
80 {.RIGHT, true, true, .Select_Word_Right},
81 {.HOME, false, false, .Start},
82 {.HOME, false, true, .Select_Start},
83 {.END, false, false, .End},
84 {.END, false, true, .Select_End},
85 {.BACKSPACE, false, false, .Backspace},
86 {.BACKSPACE, true, false, .Delete_Word_Left},
87 {.DELETE, false, false, .Delete},
88 {.DELETE, true, false, .Delete_Word_Right},
89 {.A, true, false, .Select_All},
90 {.C, true, false, .Copy},
91 {.X, true, false, .Cut},
92 {.V, true, false, .Paste},
93 {.Z, true, false, .Undo},
94 {.Y, true, false, .Redo},
95}
96
97main :: proc() {
98 rl.InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "core:text/edit text field")
99 defer rl.CloseWindow()
100 rl.SetTargetFPS(60)
101
102 font := rl.GetFontDefault()
103
104
105
106
107 builder := strings.builder_make()
108 defer strings.builder_destroy(&builder)
109 strings.write_string(&builder, "Edit me!")
110
111 state: edit.State
112 edit.init(&state, context.allocator, context.allocator)
113 defer edit.destroy(&state)
114
115
116
117
118 edit.setup_once(&state, &builder)
119
120
121
122 state.translate_by_grapheme = true
123
124 state.set_clipboard = proc(_: rawptr, text: string) -> bool {
125 rl.SetClipboardText(strings.clone_to_cstring(text, context.temp_allocator))
126 return true
127 }
128 state.get_clipboard = proc(_: rawptr) -> (text: string, ok: bool) {
129 cstr := rl.GetClipboardText()
130 return string(cstr), cstr != nil
131 }
132
133 for !rl.WindowShouldClose() {
134 defer free_all(context.temp_allocator)
135
136 handle_input(&state, font)
137
138
139
140
141 text := strings.to_string(builder)
142
143 draw(&state, font, text)
144 }
145}
146
147handle_input :: proc(state: ^edit.State, font: rl.Font) {
148
149
150 edit.update_time(state)
151
152 for r := rl.GetCharPressed(); r != 0; r = rl.GetCharPressed() {
153 edit.input_rune(state, r)
154 }
155
156 ctrl := rl.IsKeyDown(.LEFT_CONTROL) || rl.IsKeyDown(.RIGHT_CONTROL)
157 shift := rl.IsKeyDown(.LEFT_SHIFT) || rl.IsKeyDown(.RIGHT_SHIFT)
158
159 for binding in KEY_BINDINGS {
160
161 pressed := rl.IsKeyPressed(binding.key) || rl.IsKeyPressedRepeat(binding.key)
162 if pressed && binding.ctrl == ctrl && binding.shift == shift {
163 edit.perform_command(state, binding.cmd)
164 }
165 }
166
167 if rl.IsMouseButtonPressed(.LEFT) {
168 caret := caret_at(font, strings.to_string(state.builder^), rl.GetMousePosition().x)
169 state.selection = {caret, caret}
170 }
171}
172
173draw :: proc(state: ^edit.State, font: rl.Font, text: string) {
174 rl.BeginDrawing()
175 defer rl.EndDrawing()
176
177 rl.ClearBackground(COLOR_BACKGROUND)
178
179 field := rl.Rectangle{FIELD_X, FIELD_Y, FIELD_W, FIELD_H}
180 rl.DrawRectangleRec(field, COLOR_FIELD)
181 rl.DrawRectangleLinesEx(field, 2, COLOR_BORDER)
182
183
184
185
186 if lo, hi := edit.sorted_selection(state); lo != hi {
187 lo_x := TEXT_X + text_width(font, text[:lo])
188 hi_x := TEXT_X + text_width(font, text[:hi])
189 rl.DrawRectangleRec({lo_x, TEXT_Y, hi_x - lo_x, FONT_SIZE}, COLOR_SELECTION)
190 }
191
192 rl.DrawTextEx(font, strings.clone_to_cstring(text, context.temp_allocator),
193 {TEXT_X, TEXT_Y}, FONT_SIZE, FONT_SPACING, rl.RAYWHITE)
194
195 if int(rl.GetTime() / CARET_BLINK_RATE) % 2 == 0 {
196 caret_x := TEXT_X + text_width(font, text[:state.selection[0]])
197 rl.DrawRectangleRec({caret_x, TEXT_Y, CARET_WIDTH, FONT_SIZE}, rl.RAYWHITE)
198 }
199
200 rl.DrawTextEx(font, "Type to edit. Arrows move, Shift selects, Ctrl+Z undoes.",
201 {FIELD_X, HINT_Y}, HINT_FONT_SIZE, FONT_SPACING, COLOR_HINT)
202}
203
204text_width :: proc(font: rl.Font, text: string) -> f32 {
205 cstr := strings.clone_to_cstring(text, context.temp_allocator)
206 return rl.MeasureTextEx(font, cstr, FONT_SIZE, FONT_SPACING).x
207}
208
209
210
211
212
213
214
215
216caret_at :: proc(font: rl.Font, text: string, mouse_x: f32) -> (caret: int) {
217 best := abs(TEXT_X - mouse_x)
218
219 it := utf8.decode_grapheme_iterator_make(text)
220 for _, grapheme in utf8.decode_grapheme_iterate(&it) {
221 end := grapheme.byte_index + len(grapheme.text)
222 if distance := abs(TEXT_X + text_width(font, text[:end]) - mouse_x); distance < best {
223 caret, best = end, distance
224 }
225 }
226 return
227}