Skip to content

Commit 33f1349

Browse files
committed
fix keyboard text selection bug where selection always start at 0
1 parent 1ff25c8 commit 33f1349

File tree

4 files changed

+32
-14
lines changed

4 files changed

+32
-14
lines changed

titlebar.c.v

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
module gui
22

3+
// Utilities for controlling the native window titlebar appearance on Windows.
4+
// Provides `titlebar_dark(dark bool)` which toggles the dark/light titlebar
5+
// using the Desktop Window Manager (DWM) API on Windows 10+.
6+
// The code is compiled and active only on Windows via `$if windows`.
7+
//
38
import sokol.sapp
49

510
$if windows {

view_input.v

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module gui
22

33
// view_input.v provides input field functionality for GUI applications.
44
// It handles text input, cursor management, copy/paste operations, and undo/redo functionality.
5-
// The file implements both single-line and multiline input modes with customizable styling
5+
// This file implements both single-line and multiline input modes with customizable styling
66
// and behavior through the InputCfg struct. Key features include:
77
// - Text selection and cursor positioning
88
// - Clipboard operations (copy, cut, paste)
@@ -11,33 +11,45 @@ module gui
1111
// - Placeholder text
1212
// - Custom callbacks for text changes and enter key
1313
// - Themeable appearance
14+
// - Copy/paste operations
15+
// - Icon support
1416
//
1517
import log
1618
import datatypes
1719
import arrays
1820

1921
// The management of focus and input states poses a problem in stateless views
2022
// because...they're stateless. Instead, the window maintains this state in a
21-
// map where the key is the w.view_state.id_focus. This state map is cleared when a new
22-
// view is introduced.
23+
// map where the key is the w.view_state.id_focus. This state map is cleared
24+
// when a new view is introduced.
2325
@[minify]
2426
struct InputState {
2527
pub:
26-
// positions are number of runes relative to start of input text
28+
// number of runes relative to start of input text
2729
cursor_pos int
2830
select_beg u32
2931
select_end u32
3032
undo datatypes.Stack[InputMemento]
3133
redo datatypes.Stack[InputMemento]
34+
// cursor_offset is used to maintain the horizontal offset of the cursor
35+
// when traversing vertically through text. It is reset when a non-vertical
36+
// navigation operation occurs.
37+
cursor_offset f32
3238
}
3339

40+
// InputMemento is admittedly a naive implementation of undo/redo operations.
41+
// Storing all the text instead of the text operation and text fragment is less
42+
// memory efficient, but it is much easier to implement and debug. Most of the time,
43+
// input text fields are small so the actual savings vs. the additional complexity
44+
// is worth the tradeoff. It can always be refactored later if it becomes an issue.
3445
@[minify]
3546
struct InputMemento {
3647
pub:
37-
text string
38-
cursor_pos int
39-
select_beg u32
40-
select_end u32
48+
text string
49+
cursor_pos int
50+
select_beg u32
51+
select_end u32
52+
cursor_offset f32
4153
}
4254

4355
pub enum InputMode as u8 {

view_text.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@ fn (tv &TextView) on_key_down(layout &Layout, mut e Event, mut w Window) {
354354
// If there's no selection, start one from the old cursor position.
355355
if new_select_beg == new_select_end {
356356
new_select_beg = u32(old_cursor_pos)
357+
new_select_end = u32(old_cursor_pos)
357358
}
358359

359360
// Move the selection boundary that was at the old cursor position.
@@ -370,7 +371,6 @@ fn (tv &TextView) on_key_down(layout &Layout, mut e Event, mut w Window) {
370371
new_select_end = u32(new_cursor_pos)
371372
}
372373
}
373-
374374
// Ensure beg is always less than or equal to end
375375
if new_select_beg > new_select_end {
376376
new_select_beg, new_select_end = new_select_end, new_select_beg

xtra_text.v

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ pub fn get_text_width(text string, text_style TextStyle, mut window Window) f32
3030
}
3131
}
3232

33+
// text_width measures the visual width of the shape's lines, mirroring render rules:
34+
// - when in password mode (and not placeholder), measure '*' repeated for visible rune count
3335
fn text_width(shape &Shape, mut window Window) f32 {
34-
// Measure the visual width of the shape's lines, mirroring render rules:
35-
// - when in password mode (and not placeholder), measure '*' repeated for visible rune count
3636
mut max_width := f32(0)
3737
mut text_cfg_set := false
3838
htx := fnv1a.sum32_struct(shape.text_style).str()
@@ -72,6 +72,7 @@ fn line_height(shape &Shape) f32 {
7272
return shape.text_style.size + shape.text_style.line_spacing
7373
}
7474

75+
// text_wrap applies text wrapping logic to a given shape based on its text mode.
7576
fn text_wrap(mut shape Shape, mut window Window) {
7677
if shape.text_mode in [.wrap, .wrap_keep_spaces] && shape.shape_type == .text {
7778
style := shape.text_style
@@ -197,7 +198,7 @@ fn wrap_text_keep_spaces(text string, text_style TextStyle, max_width f32, tab_s
197198
field_index++
198199
}
199200

200-
// We must flush the decided output line even if it's empty (e.g., max_width == 0)
201+
// Flush the decided output line even if it's empty (e.g., max_width == 0)
201202
lines << output_line
202203
current_line = ''
203204
continue
@@ -233,7 +234,7 @@ fn wrap_text_keep_spaces(text string, text_style TextStyle, max_width f32, tab_s
233234
}
234235
}
235236

236-
// 2) If we still can't add a space and line is non-empty & not ending with space,
237+
// 2) If space can't be added and line is non-empty & not ending with space,
237238
// try to wrap earlier at a space.
238239
if !can_add_space && is_line_non_empty && !line_ends_with_space {
239240
mut should_wrap_early := false
@@ -256,7 +257,7 @@ fn wrap_text_keep_spaces(text string, text_style TextStyle, max_width f32, tab_s
256257
current_line = field
257258
}
258259
} else if is_line_non_empty {
259-
// We either added spaces or the line already ended with a space
260+
// Either added spaces or the line already ended with a space
260261
current_line = field
261262
} else {
262263
// Line is empty but field is too wide – place it anyway to avoid infinite loop

0 commit comments

Comments
 (0)