Browse Source

microui: textbox selection

Håkon Stormo 1 year ago
parent
commit
043ddd83a9
1 changed files with 110 additions and 19 deletions
  1. 110 19
      vendor/microui/microui.odin

+ 110 - 19
vendor/microui/microui.odin

@@ -29,6 +29,8 @@ import "core:sort"
 import "core:strings"
 import "core:strings"
 import "core:strconv"
 import "core:strconv"
 import "core:math"
 import "core:math"
+import "core:mem"
+import textedit "core:text/edit"
 
 
 COMMAND_LIST_SIZE    :: #config(MICROUI_COMMAND_LIST_SIZE,    256 * 1024)
 COMMAND_LIST_SIZE    :: #config(MICROUI_COMMAND_LIST_SIZE,    256 * 1024)
 ROOT_LIST_SIZE       :: #config(MICROUI_ROOT_LIST_SIZE,       32)
 ROOT_LIST_SIZE       :: #config(MICROUI_ROOT_LIST_SIZE,       32)
@@ -51,6 +53,7 @@ Clip :: enum u32 {
 
 
 Color_Type :: enum u32 {
 Color_Type :: enum u32 {
 	TEXT,
 	TEXT,
+	SELECTION_BG,
 	BORDER,
 	BORDER,
 	WINDOW_BG,
 	WINDOW_BG,
 	TITLE_BG,
 	TITLE_BG,
@@ -111,7 +114,13 @@ Key :: enum u32 {
 	CTRL,
 	CTRL,
 	ALT,
 	ALT,
 	BACKSPACE,
 	BACKSPACE,
+	DELETE,
 	RETURN,
 	RETURN,
+	LEFT,
+	RIGHT,
+	HOME,
+	END,
+	A,
 }
 }
 Key_Set :: distinct bit_set[Key; u32]
 Key_Set :: distinct bit_set[Key; u32]
 
 
@@ -235,6 +244,8 @@ Context :: struct {
 	key_down_bits, key_pressed_bits:     Key_Set,
 	key_down_bits, key_pressed_bits:     Key_Set,
 	_text_store:                         [MAX_TEXT_STORE]u8,
 	_text_store:                         [MAX_TEXT_STORE]u8,
 	text_input:                          strings.Builder, // uses `_text_store` as backing store with nil_allocator.
 	text_input:                          strings.Builder, // uses `_text_store` as backing store with nil_allocator.
+	textbox_state:                       textedit.State,
+	textbox_offset:                      i32,
 }
 }
 
 
 Stack :: struct($T: typeid, $N: int) {
 Stack :: struct($T: typeid, $N: int) {
@@ -260,6 +271,7 @@ default_style := Style{
 	scrollbar_size = 12, thumb_size = 8,
 	scrollbar_size = 12, thumb_size = 8,
 	colors = {
 	colors = {
 		.TEXT         = {230, 230, 230, 255},
 		.TEXT         = {230, 230, 230, 255},
+		.SELECTION_BG = {90,  90,  90,  255},
 		.BORDER       = {25,  25,  25,  255},
 		.BORDER       = {25,  25,  25,  255},
 		.WINDOW_BG    = {50,  50,  50,  255},
 		.WINDOW_BG    = {50,  50,  50,  255},
 		.TITLE_BG     = {25,  25,  25,  255},
 		.TITLE_BG     = {25,  25,  25,  255},
@@ -967,23 +979,78 @@ checkbox :: proc(ctx: ^Context, label: string, state: ^bool) -> (res: Result_Set
 textbox_raw :: proc(ctx: ^Context, textbuf: []u8, textlen: ^int, id: Id, r: Rect, opt := Options{}) -> (res: Result_Set) {
 textbox_raw :: proc(ctx: ^Context, textbuf: []u8, textlen: ^int, id: Id, r: Rect, opt := Options{}) -> (res: Result_Set) {
 	update_control(ctx, id, r, opt | {.HOLD_FOCUS})
 	update_control(ctx, id, r, opt | {.HOLD_FOCUS})
 
 
+	font := ctx.style.font
+
 	if ctx.focus_id == id {
 	if ctx.focus_id == id {
+		/* create a builder backed by the user's buffer */
+		builder := strings.builder_from_bytes(textbuf)
+		non_zero_resize(&builder.buf, textlen^)
+		ctx.textbox_state.builder = &builder
+		if ctx.textbox_state.id != u64(id) {
+			ctx.textbox_state.id = u64(id)
+			ctx.textbox_state.selection = {}
+		}
+
+		/* check selection bounds */
+		if ctx.textbox_state.selection[0] > textlen^ || ctx.textbox_state.selection[1] > textlen^ {
+			ctx.textbox_state.selection = {}
+		}
+
 		/* handle text input */
 		/* handle text input */
 		n := min(len(textbuf) - textlen^, strings.builder_len(ctx.text_input))
 		n := min(len(textbuf) - textlen^, strings.builder_len(ctx.text_input))
 		if n > 0 {
 		if n > 0 {
-			copy(textbuf[textlen^:], strings.to_string(ctx.text_input)[:n])
-			textlen^ += n
+			s := strings.to_string(ctx.text_input)[:n]
+			textedit.input_text(&ctx.textbox_state, s)
+			textlen^ = strings.builder_len(builder)
 			res += {.CHANGE}
 			res += {.CHANGE}
 		}
 		}
-		/* handle backspace */
-		if .BACKSPACE in ctx.key_pressed_bits && textlen^ > 0 {
-			/* skip utf-8 continuation bytes */
-			for textlen^ > 0 {
-				textlen^ -= 1
-				if textbuf[textlen^] & 0xc0 != 0x80 {
-					break
-				}
+		/* handle ctrl+a */
+		if .A in ctx.key_pressed_bits && .CTRL in ctx.key_down_bits && .ALT not_in ctx.key_down_bits {
+			ctx.textbox_state.selection = {textlen^, 0}
+		}
+		/* handle left/right */
+		if .LEFT in ctx.key_pressed_bits {
+			move: textedit.Translation = .Word_Left if .CTRL in ctx.key_down_bits else .Left
+			if .SHIFT in ctx.key_down_bits {
+				textedit.select_to(&ctx.textbox_state, move)
+			} else {
+				textedit.move_to(&ctx.textbox_state, move)
 			}
 			}
+		}
+		if .RIGHT in ctx.key_pressed_bits {
+			move: textedit.Translation = .Word_Right if .CTRL in ctx.key_down_bits else .Right
+			if .SHIFT in ctx.key_down_bits {
+				textedit.select_to(&ctx.textbox_state, move)
+			} else {
+				textedit.move_to(&ctx.textbox_state, move)
+			}
+		}
+		/* handle home/end */
+		if .HOME in ctx.key_pressed_bits {
+			if .SHIFT in ctx.key_down_bits {
+				textedit.select_to(&ctx.textbox_state, .Start)
+			} else {
+				textedit.move_to(&ctx.textbox_state, .Start)
+			}
+		}
+		if .END in ctx.key_pressed_bits {
+			if .SHIFT in ctx.key_down_bits {
+				textedit.select_to(&ctx.textbox_state, .End)
+			} else {
+				textedit.move_to(&ctx.textbox_state, .End)
+			}
+		}
+		/* handle backspace/delete */
+		if .BACKSPACE in ctx.key_pressed_bits && textlen^ > 0 {
+			move: textedit.Translation = .Word_Left if .CTRL in ctx.key_down_bits else .Left
+			textedit.delete_to(&ctx.textbox_state, move)
+			textlen^ = strings.builder_len(builder)
+			res += {.CHANGE}
+		}
+		if .DELETE in ctx.key_pressed_bits && textlen^ > 0 {
+			move: textedit.Translation = .Word_Right if .CTRL in ctx.key_down_bits else .Right
+			textedit.delete_to(&ctx.textbox_state, move)
+			textlen^ = strings.builder_len(builder)
 			res += {.CHANGE}
 			res += {.CHANGE}
 		}
 		}
 		/* handle return */
 		/* handle return */
@@ -991,6 +1058,25 @@ textbox_raw :: proc(ctx: ^Context, textbuf: []u8, textlen: ^int, id: Id, r: Rect
 			set_focus(ctx, 0)
 			set_focus(ctx, 0)
 			res += {.SUBMIT}
 			res += {.SUBMIT}
 		}
 		}
+
+		/* handle click/drag */
+		if .LEFT in ctx.mouse_down_bits {
+			idx := textlen^
+			for i in 0..<textlen^ {
+				/* skip continuation bytes */
+				if textbuf[i] >= 0x80 && textbuf[i] < 0xc0 {
+					continue
+				}
+				if ctx.mouse_pos.x < r.x + ctx.textbox_offset + ctx.text_width(font, string(textbuf[:i])) {
+					idx = i
+					break
+				}
+			}
+			ctx.textbox_state.selection[0] = idx
+			if .LEFT in ctx.mouse_pressed_bits && .SHIFT not_in ctx.key_down_bits {
+				ctx.textbox_state.selection[1] = idx
+			}
+		}
 	}
 	}
 
 
 	textstr := string(textbuf[:textlen^])
 	textstr := string(textbuf[:textlen^])
@@ -998,16 +1084,21 @@ textbox_raw :: proc(ctx: ^Context, textbuf: []u8, textlen: ^int, id: Id, r: Rect
 	/* draw */
 	/* draw */
 	draw_control_frame(ctx, id, r, .BASE, opt)
 	draw_control_frame(ctx, id, r, .BASE, opt)
 	if ctx.focus_id == id {
 	if ctx.focus_id == id {
-		color := ctx.style.colors[.TEXT]
-		font  := ctx.style.font
-		textw := ctx.text_width(font, textstr)
-		texth := ctx.text_height(font)
-		ofx   := r.w - ctx.style.padding - textw - 1
-		textx := r.x + min(ofx, ctx.style.padding)
-		texty := r.y + (r.h - texth) / 2
+		text_color := ctx.style.colors[.TEXT]
+		sel_color  := ctx.style.colors[.SELECTION_BG]
+		textw      := ctx.text_width(font, textstr)
+		texth      := ctx.text_height(font)
+		headx      := ctx.text_width(font, textstr[:ctx.textbox_state.selection[0]])
+		tailx      := ctx.text_width(font, textstr[:ctx.textbox_state.selection[1]])
+		ofmin      := max(ctx.style.padding - headx, r.w - textw - ctx.style.padding)
+		ofmax      := min(r.w - headx - ctx.style.padding, ctx.style.padding)
+		ctx.textbox_offset = clamp(ctx.textbox_offset, ofmin, ofmax)
+		textx      := r.x + ctx.textbox_offset
+		texty      := r.y + (r.h - texth) / 2
 		push_clip_rect(ctx, r)
 		push_clip_rect(ctx, r)
-		draw_text(ctx, font, textstr, Vec2{textx, texty}, color)
-		draw_rect(ctx, Rect{textx + textw, texty, 1, texth}, color)
+		draw_rect(ctx, Rect{textx + min(headx, tailx), texty, abs(headx - tailx), texth}, sel_color)
+		draw_text(ctx, font, textstr, Vec2{textx, texty}, text_color)
+		draw_rect(ctx, Rect{textx + headx, texty, 1, texth}, text_color)
 		pop_clip_rect(ctx)
 		pop_clip_rect(ctx)
 	} else {
 	} else {
 		draw_control_text(ctx, textstr, r, .TEXT, opt)
 		draw_control_text(ctx, textstr, r, .TEXT, opt)