diff options
Diffstat (limited to 'v_windows/v/old/vlib/term')
| -rw-r--r-- | v_windows/v/old/vlib/term/README.md | 84 | ||||
| -rw-r--r-- | v_windows/v/old/vlib/term/colors.v | 199 | ||||
| -rw-r--r-- | v_windows/v/old/vlib/term/control.v | 108 | ||||
| -rw-r--r-- | v_windows/v/old/vlib/term/term.js.v | 8 | ||||
| -rw-r--r-- | v_windows/v/old/vlib/term/term.v | 196 | ||||
| -rw-r--r-- | v_windows/v/old/vlib/term/term_nix.c.v | 105 | ||||
| -rw-r--r-- | v_windows/v/old/vlib/term/term_test.v | 115 | ||||
| -rw-r--r-- | v_windows/v/old/vlib/term/term_windows.c.v | 125 | ||||
| -rw-r--r-- | v_windows/v/old/vlib/term/ui/README.md | 99 | ||||
| -rw-r--r-- | v_windows/v/old/vlib/term/ui/color.v | 88 | ||||
| -rw-r--r-- | v_windows/v/old/vlib/term/ui/consoleapi_windows.c.v | 82 | ||||
| -rw-r--r-- | v_windows/v/old/vlib/term/ui/input.v | 241 | ||||
| -rw-r--r-- | v_windows/v/old/vlib/term/ui/input_nix.c.v | 70 | ||||
| -rw-r--r-- | v_windows/v/old/vlib/term/ui/input_windows.c.v | 326 | ||||
| -rw-r--r-- | v_windows/v/old/vlib/term/ui/termios_nix.c.v | 530 | ||||
| -rw-r--r-- | v_windows/v/old/vlib/term/ui/ui.v | 256 | 
16 files changed, 2632 insertions, 0 deletions
diff --git a/v_windows/v/old/vlib/term/README.md b/v_windows/v/old/vlib/term/README.md new file mode 100644 index 0000000..8708226 --- /dev/null +++ b/v_windows/v/old/vlib/term/README.md @@ -0,0 +1,84 @@ +# Quickstart + +The V `term` module is a module designed to provide the building blocks +for building very simple TUI apps. +For more complex apps, you should really look at the `term.input` module, +as it includes terminal events, is easier to use and is much more performant for large draws. + +# Use + +You can use the `term` module to either color the output on a terminal +or to decide on where to put the output in your terminal. + +For example let's make a simple program which prints colored text in the middle of the terminal. + +```v +import term +import os + +fn main() { +	term.clear() // clears the content in the terminal +	width, height := term.get_terminal_size() // get the size of the terminal +	term.set_cursor_position(x: width / 2, y: height / 2) // now we point the cursor to the middle of  the terminal +	println(term.strikethrough(term.bright_green('hello world'))) // Print green text +	term.set_cursor_position(x: 0, y: height) // Sets the position of the cursor to the bottom of the terminal +	// Keep prompting until the user presses the q key +	for { +		if var := os.input_opt('press q to quit: ') { +			if var != 'q' { +				continue +			} +			break +		} +		println('') +		break +	} +	println('Goodbye.') +} +``` + +This simple program covers many of the principal aspects of the `term ` module. + +# API + +Here are some functions you should be aware of in the `term `module: + +```v oksyntax +import term + +// returns the height and the width of the terminal +width, height := term.get_terminal_size() +// returns the string as green text to be printed on stdout +term.ok_message('cool') +// returns the string as red text to be printed on stdout +term.fail_message('oh, no') +// returns the string as yellow text to be printed on stdout +term.warning_message('be warned') +// clears the entire terminal and leaves a blank one +term.clear() +// colors the output of the output, the available colors are: black,blue,yellow,green,cyan,gray,bright_blue,bright_green,bright_red,bright_black,bright_cyan +term.yellow('submarine') +// transforms the given string into bold text +term.bold('and beautiful') +// puts a strikethrough into the given string +term.strikethrough('the core of the problem') +// underlines the given string +term.underline('important') +// colors the background of the output following the given color +// the available colors are: black, blue, yellow, green, cyan, gray +term.bg_green('field') +// sets the position of the cursor at a given place in the terminal +term.set_cursor_position(x: 5, y: 10) +// moves the cursor up +term.cursor_up() +// moves the cursor down +term.cursor_down() +// moves the cursor to the right +term.cursor_forward() +// moves the cursor to the left +term.cursor_back() +// shows the cursor +term.show_cursor() +// hides the cursor +term.hide_cursor() +``` diff --git a/v_windows/v/old/vlib/term/colors.v b/v_windows/v/old/vlib/term/colors.v new file mode 100644 index 0000000..f7662ee --- /dev/null +++ b/v_windows/v/old/vlib/term/colors.v @@ -0,0 +1,199 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module term + +pub fn format(msg string, open string, close string) string { +	return '\x1b[' + open + 'm' + msg + '\x1b[' + close + 'm' +} + +pub fn format_rgb(r int, g int, b int, msg string, open string, close string) string { +	return '\x1b[' + open + ';2;' + r.str() + ';' + g.str() + ';' + b.str() + 'm' + msg + '\x1b[' + +		close + 'm' +} + +pub fn rgb(r int, g int, b int, msg string) string { +	return format_rgb(r, g, b, msg, '38', '39') +} + +pub fn bg_rgb(r int, g int, b int, msg string) string { +	return format_rgb(r, g, b, msg, '48', '49') +} + +pub fn hex(hex int, msg string) string { +	return format_rgb(hex >> 16, hex >> 8 & 0xFF, hex & 0xFF, msg, '38', '39') +} + +pub fn bg_hex(hex int, msg string) string { +	return format_rgb(hex >> 16, hex >> 8 & 0xFF, hex & 0xFF, msg, '48', '49') +} + +pub fn bg_black(msg string) string { +	return format(msg, '40', '49') +} + +pub fn bright_bg_black(msg string) string { +	return format(msg, '100', '49') +} + +pub fn bg_blue(msg string) string { +	return format(msg, '44', '49') +} + +pub fn bright_bg_blue(msg string) string { +	return format(msg, '104', '49') +} + +pub fn bg_cyan(msg string) string { +	return format(msg, '46', '49') +} + +pub fn bright_bg_cyan(msg string) string { +	return format(msg, '106', '49') +} + +pub fn bg_green(msg string) string { +	return format(msg, '42', '49') +} + +pub fn bright_bg_green(msg string) string { +	return format(msg, '102', '49') +} + +pub fn bg_magenta(msg string) string { +	return format(msg, '45', '49') +} + +pub fn bright_bg_magenta(msg string) string { +	return format(msg, '105', '49') +} + +pub fn bg_red(msg string) string { +	return format(msg, '41', '49') +} + +pub fn bright_bg_red(msg string) string { +	return format(msg, '101', '49') +} + +pub fn bg_white(msg string) string { +	return format(msg, '47', '49') +} + +pub fn bright_bg_white(msg string) string { +	return format(msg, '107', '49') +} + +pub fn bg_yellow(msg string) string { +	return format(msg, '43', '49') +} + +pub fn bright_bg_yellow(msg string) string { +	return format(msg, '103', '49') +} + +pub fn black(msg string) string { +	return format(msg, '30', '39') +} + +pub fn bright_black(msg string) string { +	return format(msg, '90', '39') +} + +pub fn blue(msg string) string { +	return format(msg, '34', '39') +} + +pub fn bright_blue(msg string) string { +	return format(msg, '94', '39') +} + +pub fn bold(msg string) string { +	return format(msg, '1', '22') +} + +pub fn cyan(msg string) string { +	return format(msg, '36', '39') +} + +pub fn bright_cyan(msg string) string { +	return format(msg, '96', '39') +} + +pub fn dim(msg string) string { +	return format(msg, '2', '22') +} + +pub fn green(msg string) string { +	return format(msg, '32', '39') +} + +pub fn bright_green(msg string) string { +	return format(msg, '92', '39') +} + +pub fn gray(msg string) string { +	return bright_black(msg) +} + +pub fn hidden(msg string) string { +	return format(msg, '8', '28') +} + +pub fn italic(msg string) string { +	return format(msg, '3', '23') +} + +pub fn inverse(msg string) string { +	return format(msg, '7', '27') +} + +pub fn magenta(msg string) string { +	return format(msg, '35', '39') +} + +pub fn bright_magenta(msg string) string { +	return format(msg, '95', '39') +} + +pub fn reset(msg string) string { +	return format(msg, '0', '0') +} + +pub fn red(msg string) string { +	return format(msg, '31', '39') +} + +pub fn bright_red(msg string) string { +	return format(msg, '91', '39') +} + +pub fn strikethrough(msg string) string { +	return format(msg, '9', '29') +} + +pub fn underline(msg string) string { +	return format(msg, '4', '24') +} + +pub fn white(msg string) string { +	return format(msg, '37', '39') +} + +pub fn bright_white(msg string) string { +	return format(msg, '97', '39') +} + +pub fn yellow(msg string) string { +	return format(msg, '33', '39') +} + +pub fn bright_yellow(msg string) string { +	return format(msg, '93', '39') +} + +// highlight_command highlights the command with an on-brand background +// to make CLI commands immediately recognizable. +pub fn highlight_command(command string) string { +	return bright_white(bg_cyan(' $command ')) +} diff --git a/v_windows/v/old/vlib/term/control.v b/v_windows/v/old/vlib/term/control.v new file mode 100644 index 0000000..377cc42 --- /dev/null +++ b/v_windows/v/old/vlib/term/control.v @@ -0,0 +1,108 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module term + +// Sources for ANSI Control Sequences +// https://github.com/RajeshPatkarInstitute/Panim +// https://www.gnu.org/software/screen/manual/html_node/Control-Sequences.html +// https://en.wikipedia.org/wiki/ANSI_escape_code +// Support for Windows +// https://en.wikipedia.org/wiki/ANSI.SYS +// #include <windows.h> +// C.SetConsoleMode(C.ENABLE_VIRTUAL_TERMINAL_INPUT) +// Setting cursor to the given position +// x is the x coordinate +// y is the y coordinate +pub fn set_cursor_position(c Coord) { +	print('\x1b[$c.y;$c.x' + 'H') +} + +// n is number of cells +// direction: A is up / North +// direction: B is down / South +// direction: C is forward / East +// direction: D is backward / West +pub fn move(n int, direction string) { +	print('\x1b[$n$direction') +} + +pub fn cursor_up(n int) { +	move(n, 'A') +} + +pub fn cursor_down(n int) { +	move(n, 'B') +} + +pub fn cursor_forward(n int) { +	move(n, 'C') +} + +pub fn cursor_back(n int) { +	move(n, 'D') +} + +// type: 0 -> current cursor position to end of the screen +// type: 1 -> current cursor position to beginning of the screen +// type: 2 -> clears entire screen +// type: 3 -> clears entire screen and also delete scrollback buffer + +pub fn erase_display(t string) { +	print('\x1b[' + t + 'J') +} + +pub fn erase_toend() { +	erase_display('0') +} + +pub fn erase_tobeg() { +	erase_display('1') +} + +// clears entire screen and returns cursor to top left-corner +pub fn erase_clear() { +	print('\033[H\033[J') +} + +pub fn erase_del_clear() { +	erase_display('3') +} + +// type: 0 -> current cursor position to end of the line +// type: 1 -> current cursor position to beginning of the line +// type: 2 -> clears entire line +// Note: Cursor position does not change +pub fn erase_line(t string) { +	print('\x1b[' + t + 'K') +} + +pub fn erase_line_toend() { +	erase_line('0') +} + +pub fn erase_line_tobeg() { +	erase_line('1') +} + +pub fn erase_line_clear() { +	erase_line('2') +} + +// Will make cursor appear if not visible +pub fn show_cursor() { +	print('\x1b[?25h') +} + +// Will make cursor invisible +pub fn hide_cursor() { +	print('\x1b[?25l') +} + +// clear_previous_line - useful for progressbars. +// It moves the cursor to start of line, then 1 line above, +// then erases the line. In effect the next println will overwrite +// the previous content. +pub fn clear_previous_line() { +	print('\r\x1b[1A\x1b[2K') +} diff --git a/v_windows/v/old/vlib/term/term.js.v b/v_windows/v/old/vlib/term/term.js.v new file mode 100644 index 0000000..be85eca --- /dev/null +++ b/v_windows/v/old/vlib/term/term.js.v @@ -0,0 +1,8 @@ +module term + +// get_terminal_size returns a number of colums and rows of terminal window. +pub fn get_terminal_size() (int, int) { +	// TODO Find a way to get proper width & height of the terminal +	// on a Javascript environment +	return default_columns_size, default_rows_size +} diff --git a/v_windows/v/old/vlib/term/term.v b/v_windows/v/old/vlib/term/term.v new file mode 100644 index 0000000..390b3be --- /dev/null +++ b/v_windows/v/old/vlib/term/term.v @@ -0,0 +1,196 @@ +module term + +import os +import strings.textscanner + +const ( +	default_columns_size = 80 +	default_rows_size    = 25 +) + +// Coord - used by term.get_cursor_position and term.set_cursor_position +pub struct Coord { +pub mut: +	x int +	y int +} + +// can_show_color_on_stdout returns true if colors are allowed in stdout; +// returns false otherwise. +pub fn can_show_color_on_stdout() bool { +	return supports_escape_sequences(1) +} + +// can_show_color_on_stderr returns true if colors are allowed in stderr; +// returns false otherwise. +pub fn can_show_color_on_stderr() bool { +	return supports_escape_sequences(2) +} + +// failed returns a bold white on red version of the string `s` +// If colors are not allowed, returns the string `s` +pub fn failed(s string) string { +	if can_show_color_on_stdout() { +		return bg_red(bold(white(s))) +	} +	return s +} + +// ok_message returns a colored string with green color. +// If colors are not allowed, returns a given string. +pub fn ok_message(s string) string { +	if can_show_color_on_stdout() { +		return green(' $s ') +	} +	return s +} + +// fail_message returns a colored string with red color. +// If colors are not allowed, returns a given string. +pub fn fail_message(s string) string { +	return failed(' $s ') +} + +// warn_message returns a colored string with yellow color. +// If colors are not allowed, returns a given string. +pub fn warn_message(s string) string { +	if can_show_color_on_stdout() { +		return bright_yellow(' $s ') +	} +	return s +} + +// colorize returns a colored string by running the specified `cfn` over +// the message `s`, only if colored output is supported by the terminal. +// Example: term.colorize(term.yellow, 'the message') +pub fn colorize(cfn fn (string) string, s string) string { +	if can_show_color_on_stdout() { +		return cfn(s) +	} +	return s +} + +// strip_ansi removes any ANSI sequences in the `text` +pub fn strip_ansi(text string) string { +	// This is a port of https://github.com/kilobyte/colorized-logs/blob/master/ansi2txt.c +	// \e, [, 1, m, a, b, c, \e, [, 2, 2, m => abc +	mut input := textscanner.new(text) +	mut output := []byte{cap: text.len} +	mut ch := 0 +	for ch != -1 { +		ch = input.next() +		if ch == 27 { +			ch = input.next() +			if ch == `[` { +				for { +					ch = input.next() +					if ch in [`;`, `?`] || (ch >= `0` && ch <= `9`) { +						continue +					} +					break +				} +			} else if ch == `]` { +				ch = input.next() +				if ch >= `0` && ch <= `9` { +					for { +						ch = input.next() +						if ch == -1 || ch == 7 { +							break +						} +						if ch == 27 { +							ch = input.next() +							break +						} +					} +				} +			} else if ch == `%` { +				ch = input.next() +			} +		} else if ch != -1 { +			output << byte(ch) +		} +	} +	return output.bytestr() +} + +// h_divider returns a horizontal divider line with a dynamic width, +// that depends on the current terminal settings. +// If an empty string is passed in, print enough spaces to make a new line +pub fn h_divider(divider string) string { +	cols, _ := get_terminal_size() +	mut result := '' +	if divider.len > 0 { +		result = divider.repeat(1 + (cols / divider.len)) +	} else { +		result = ' '.repeat(1 + cols) +	} +	return result[0..cols] +} + +// header_left returns a horizontal divider line with a title text on the left. +// e.g: term.header_left('TITLE', '=') +// ==== TITLE ========================= +pub fn header_left(text string, divider string) string { +	plain_text := strip_ansi(text) +	xcols, _ := get_terminal_size() +	cols := imax(1, xcols) +	relement := if divider.len > 0 { divider } else { ' ' } +	hstart := relement.repeat(4)[0..4] +	remaining_cols := (cols - (hstart.len + 1 + plain_text.len + 1)) +	hend := relement.repeat((remaining_cols + 1) / relement.len)[0..remaining_cols] +	return '$hstart $text $hend' +} + +// header returns a horizontal divider line with a centered text in the middle. +// e.g: term.header('TEXT', '=') +// =============== TEXT =============== +pub fn header(text string, divider string) string { +	if text.len == 0 { +		return h_divider(divider) +	} +	xcols, _ := get_terminal_size() +	cols := imax(1, xcols) +	tlimit := imax(1, if cols > text.len + 2 + 2 * divider.len { +		text.len +	} else { +		cols - 3 - 2 * divider.len +	}) +	tlimit_alligned := if (tlimit % 2) != (cols % 2) { tlimit + 1 } else { tlimit } +	tstart := imax(0, (cols - tlimit_alligned) / 2) +	mut ln := '' +	if divider.len > 0 { +		ln = divider.repeat(1 + cols / divider.len)[0..cols] +	} else { +		ln = ' '.repeat(1 + cols) +	} +	if ln.len == 1 { +		return ln + ' ' + text[0..tlimit] + ' ' + ln +	} +	return ln[0..tstart] + ' ' + text[0..tlimit] + ' ' + ln[tstart + tlimit + 2..cols] +} + +fn imax(x int, y int) int { +	return if x > y { x } else { y } +} + +fn supports_escape_sequences(fd int) bool { +	vcolors_override := os.getenv('VCOLORS') +	if vcolors_override == 'always' { +		return true +	} +	if vcolors_override == 'never' { +		return false +	} +	if os.getenv('TERM') == 'dumb' { +		return false +	} +	$if windows { +		if os.getenv('ConEmuANSI') == 'ON' { +			return true +		} +		// 4 is enable_virtual_terminal_processing +		return (os.is_atty(fd) & 0x0004) > 0 +	} $else { +		return os.is_atty(fd) > 0 +	} +} diff --git a/v_windows/v/old/vlib/term/term_nix.c.v b/v_windows/v/old/vlib/term/term_nix.c.v new file mode 100644 index 0000000..45a0a9b --- /dev/null +++ b/v_windows/v/old/vlib/term/term_nix.c.v @@ -0,0 +1,105 @@ +module term + +import os + +#include <sys/ioctl.h> +#include <termios.h> // TIOCGWINSZ + +pub struct C.winsize { +pub: +	ws_row    u16 +	ws_col    u16 +	ws_xpixel u16 +	ws_ypixel u16 +} + +fn C.ioctl(fd int, request u64, arg voidptr) int + +// get_terminal_size returns a number of colums and rows of terminal window. +pub fn get_terminal_size() (int, int) { +	if os.is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' { +		return default_columns_size, default_rows_size +	} +	w := C.winsize{} +	C.ioctl(1, u64(C.TIOCGWINSZ), &w) +	return int(w.ws_col), int(w.ws_row) +} + +// get_cursor_position returns a Coord containing the current cursor position +pub fn get_cursor_position() Coord { +	if os.is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' { +		return Coord{ +			x: 0 +			y: 0 +		} +	} +	// TODO: use termios.h, C.tcgetattr & C.tcsetattr directly, +	// instead of using `stty` +	mut oldsettings := os.execute('stty -g') +	if oldsettings.exit_code < 0 { +		oldsettings = os.Result{} +	} +	os.system('stty -echo -icanon time 0') +	print('\033[6n') +	mut ch := int(0) +	mut i := 0 +	// ESC [ YYY `;` XXX `R` +	mut reading_x := false +	mut reading_y := false +	mut x := 0 +	mut y := 0 +	for { +		ch = C.getchar() +		b := byte(ch) +		i++ +		if i >= 15 { +			panic('C.getchar() called too many times') +		} +		// state management: +		if b == `R` { +			break +		} +		if b == `[` { +			reading_y = true +			reading_x = false +			continue +		} +		if b == `;` { +			reading_y = false +			reading_x = true +			continue +		} +		// converting string vals to ints: +		if reading_x { +			x *= 10 +			x += (b - byte(`0`)) +		} +		if reading_y { +			y *= 10 +			y += (b - byte(`0`)) +		} +	} +	// restore the old terminal settings: +	os.system('stty $oldsettings.output') +	return Coord{ +		x: x +		y: y +	} +} + +// set_terminal_title change the terminal title +pub fn set_terminal_title(title string) bool { +	if os.is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' { +		return true +	} +	print('\033]0') +	print(title) +	print('\007') +	return true +} + +// clear clears current terminal screen. +pub fn clear() { +	print('\x1b[2J') +	print('\x1b[H') +} diff --git a/v_windows/v/old/vlib/term/term_test.v b/v_windows/v/old/vlib/term/term_test.v new file mode 100644 index 0000000..00f9293 --- /dev/null +++ b/v_windows/v/old/vlib/term/term_test.v @@ -0,0 +1,115 @@ +import os +import term + +fn test_get_terminal_size() { +	cols, _ := term.get_terminal_size() +	assert cols > 0 +} + +fn test_h_divider() { +	divider := term.h_divider('-') +	assert divider.len > 0 +	assert divider[0] == `-` +	assert divider[divider.len - 1] == `-` +} + +fn test_h_divider_multiple_characters() { +	xdivider := term.h_divider('abc') +	assert xdivider.len > 0 +	assert xdivider.contains('abcabc') +} + +fn test_header() { +	divider := term.h_divider('-') +	term_width := divider.len +	assert term_width > 0 +	empty_header := term.header('', '-') +	short_header := term.header('reasonable header', '-') +	very_long_header := term.header(['abc'].repeat(500).join(' '), '-') +	very_long_header_2 := term.header(['abcd'].repeat(500).join(' '), '-') +	/* +	eprintln(divider) +	eprintln(empty_header) +	eprintln(short_header) +	eprintln(term.header('another longer header', '_-/\\')) +	eprintln(term.header('another longer header', '-')) +	eprintln(term.header('short', '-')) +	eprintln(term.header('12345', '-')) +	eprintln(term.header('1234', '-')) +	eprintln(term.header('123', '-')) +	eprintln(term.header('12', '-')) +	eprintln(term.header('1', '-')) +	eprintln(very_long_header) +	eprintln(divider) +	eprintln(very_long_header_2) +	eprintln(term.header(['abcd'].repeat(500).join(' '), '_-/\\')) +	eprintln(term.header(['abcd'].repeat(500).join(' '), '_-//')) +	eprintln(term.header('1', '_-/\\\/')) +	eprintln(term.header('12', '_-/\\\/')) +	eprintln(term.header('123', '_-/\\\/')) +	eprintln(term.header('1234', '_-/\\/\\')) +	eprintln(term.header('', '-')) +	*/ +	assert term_width == empty_header.len +	assert term_width == short_header.len +	assert term_width == very_long_header.len +	assert term_width == very_long_header_2.len +	assert term_width == term.header('1234', '_-/\\/\\').len +} + +fn test_get_cursor_position() { +	original_position := term.get_cursor_position() +	cursor_position_1 := term.get_cursor_position() +	assert original_position.x == cursor_position_1.x +	assert original_position.y == cursor_position_1.y +	// +	term.set_cursor_position( +		x: 10 +		y: 11 +	) +	cursor_position_2 := term.get_cursor_position() +	// +	term.set_cursor_position( +		x: 5 +		y: 6 +	) +	cursor_position_3 := term.get_cursor_position() +	// +	term.set_cursor_position(original_position) +	eprintln('original_position: $original_position') +	eprintln('cursor_position_2: $cursor_position_2') +	eprintln('cursor_position_3: $cursor_position_3') +	// 0,0 is returned on dumb terminals +	if cursor_position_2.x == 0 && cursor_position_2.y == 0 { +		return +	} +	if cursor_position_3.x == 0 && cursor_position_3.y == 0 { +		return +	} +	assert cursor_position_2.x == 10 +	assert cursor_position_2.y == 11 +	assert cursor_position_3.x == 5 +	assert cursor_position_3.y == 6 +} + +fn test_set_terminal_title() { +	// do not change the current terminal title outside of CI: +	if os.getenv('CI') != 'true' { +		return +	} +	title_change := term.set_terminal_title('v is awesome!') +	assert title_change == true +} + +fn test_strip_ansi() { +	strings := [ +		'abc', +		term.bold('abc'), +		term.yellow('abc'), +		term.bold(term.red('abc')), +		term.strikethrough(term.inverse(term.dim(term.bold(term.bright_bg_blue('abc'))))), +	] +	for s in strings { +		assert term.strip_ansi(s) == 'abc' +	} +} diff --git a/v_windows/v/old/vlib/term/term_windows.c.v b/v_windows/v/old/vlib/term/term_windows.c.v new file mode 100644 index 0000000..1d2f032 --- /dev/null +++ b/v_windows/v/old/vlib/term/term_windows.c.v @@ -0,0 +1,125 @@ +module term + +import os + +[typedef] +struct C.COORD { +mut: +	X i16 +	Y i16 +} + +[typedef] +struct C.SMALL_RECT { +mut: +	Left   u16 +	Top    u16 +	Right  u16 +	Bottom u16 +} + +// win: CONSOLE_SCREEN_BUFFER_INFO +// https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str +[typedef] +struct C.CONSOLE_SCREEN_BUFFER_INFO { +mut: +	dwSize              C.COORD +	dwCursorPosition    C.COORD +	wAttributes         u16 +	srWindow            C.SMALL_RECT +	dwMaximumWindowSize C.COORD +} + +union C.uChar { +mut: +	UnicodeChar rune +	AsciiChar   byte +} + +[typedef] +struct C.CHAR_INFO { +mut: +	Char       C.uChar +	Attributes u16 +} + +// ref - https://docs.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo +fn C.GetConsoleScreenBufferInfo(handle C.HANDLE, info &C.CONSOLE_SCREEN_BUFFER_INFO) bool + +// ref - https://docs.microsoft.com/en-us/windows/console/setconsoletitle +fn C.SetConsoleTitle(title &u16) bool + +// ref - https://docs.microsoft.com/en-us/windows/console/setconsolecursorposition +fn C.SetConsoleCursorPosition(handle C.HANDLE, coord C.COORD) bool + +// ref - https://docs.microsoft.com/en-us/windows/console/scrollconsolescreenbuffer +fn C.ScrollConsoleScreenBuffer(output C.HANDLE, scroll_rect &C.SMALL_RECT, clip_rect &C.SMALL_RECT, des C.COORD, fill &C.CHAR_INFO) bool + +// get_terminal_size returns a number of colums and rows of terminal window. +pub fn get_terminal_size() (int, int) { +	if os.is_atty(1) > 0 && os.getenv('TERM') != 'dumb' { +		info := C.CONSOLE_SCREEN_BUFFER_INFO{} +		if C.GetConsoleScreenBufferInfo(C.GetStdHandle(C.STD_OUTPUT_HANDLE), &info) { +			columns := int(info.srWindow.Right - info.srWindow.Left + 1) +			rows := int(info.srWindow.Bottom - info.srWindow.Top + 1) +			return columns, rows +		} +	} +	return default_columns_size, default_rows_size +} + +// get_cursor_position returns a Coord containing the current cursor position +pub fn get_cursor_position() Coord { +	mut res := Coord{} +	if os.is_atty(1) > 0 && os.getenv('TERM') != 'dumb' { +		info := C.CONSOLE_SCREEN_BUFFER_INFO{} +		if C.GetConsoleScreenBufferInfo(C.GetStdHandle(C.STD_OUTPUT_HANDLE), &info) { +			res.x = info.dwCursorPosition.X +			res.y = info.dwCursorPosition.Y +		} +	} +	return res +} + +// set_terminal_title change the terminal title +pub fn set_terminal_title(title string) bool { +	title_change := C.SetConsoleTitle(title.to_wide()) +	return title_change +} + +// clear clears current terminal screen. +// Implementation taken from https://docs.microsoft.com/en-us/windows/console/clearing-the-screen#example-2. +pub fn clear() { +	hconsole := C.GetStdHandle(C.STD_OUTPUT_HANDLE) +	mut csbi := C.CONSOLE_SCREEN_BUFFER_INFO{} +	mut scrollrect := C.SMALL_RECT{} +	mut scrolltarget := C.COORD{} +	mut fill := C.CHAR_INFO{} + +	// Get the number of character cells in the current buffer. +	if !C.GetConsoleScreenBufferInfo(hconsole, &csbi) { +		return +	} +	// Scroll the rectangle of the entire buffer. +	scrollrect.Left = 0 +	scrollrect.Top = 0 +	scrollrect.Right = u16(csbi.dwSize.X) +	scrollrect.Bottom = u16(csbi.dwSize.Y) + +	// Scroll it upwards off the top of the buffer with a magnitude of the entire height. +	scrolltarget.X = 0 +	scrolltarget.Y = (0 - csbi.dwSize.Y) + +	// Fill with empty spaces with the buffer's default text attribute. +	fill.Char.UnicodeChar = rune(` `) +	fill.Attributes = csbi.wAttributes + +	// Do the scroll +	C.ScrollConsoleScreenBuffer(hconsole, &scrollrect, C.NULL, scrolltarget, &fill) + +	// Move the cursor to the top left corner too. +	csbi.dwCursorPosition.X = 0 +	csbi.dwCursorPosition.Y = 0 + +	C.SetConsoleCursorPosition(hconsole, csbi.dwCursorPosition) +} diff --git a/v_windows/v/old/vlib/term/ui/README.md b/v_windows/v/old/vlib/term/ui/README.md new file mode 100644 index 0000000..6bce054 --- /dev/null +++ b/v_windows/v/old/vlib/term/ui/README.md @@ -0,0 +1,99 @@ +## `term.ui` + +A V module for designing terminal UI apps + +#### Quickstart + +```v +import term.ui as tui + +struct App { +mut: +	tui &tui.Context = 0 +} + +fn event(e &tui.Event, x voidptr) { +	mut app := &App(x) +	println(e) +	if e.typ == .key_down && e.code == .escape { +		exit(0) +	} +} + +fn frame(x voidptr) { +	mut app := &App(x) + +	app.tui.clear() +	app.tui.set_bg_color(r: 63, g: 81, b: 181) +	app.tui.draw_rect(20, 6, 41, 10) +	app.tui.draw_text(24, 8, 'Hello from V!') +	app.tui.set_cursor_position(0, 0) + +	app.tui.reset() +	app.tui.flush() +} + +mut app := &App{} +app.tui = tui.init( +	user_data: app +	event_fn: event +	frame_fn: frame +	hide_cursor: true +) +app.tui.run() ? +``` + +See the `/examples/term.ui/` folder for more usage examples. + +#### Configuration + +- `user_data voidptr` - a pointer to any `user_data`, it will be passed as the last argument to +    each callback. Used for accessing your app context from the different callbacks. +- `init_fn fn(voidptr)` - a callback that will be called after initialization +    and before the first event / frame. Useful for initializing any user data. +- `frame_fn fn(voidptr)` - a callback that will be fired on each frame, +    at a rate of `frame_rate` frames per second. +`event_fn fn(&Event, voidptr)` - a callback that will be fired for every event received. +- `cleanup_fn fn(voidptr)` - a callback that will be fired once, before the application exits. +- `fail_fn  fn(string)` - a callback that will be fired +    if a fatal error occurs during app initialization. +- `buffer_size int = 256` - the internal size of the read buffer. +    Increasing it may help in case you're missing events, but you probably shouldn't lower +    this value unless you make sure you're still receiving all events. In general, +    higher frame rates work better with lower buffer sizes, and vice versa. +- `frame_rate int = 30` - the number of times per second that the `frame` callback will be fired. +    30fps is a nice balance between smoothness and performance, +    but you can increase or lower it as you wish. +- `hide_cursor bool` - whether to hide the mouse cursor. Useful if you want to use your own. +- `capture_events bool` - sets the terminal into raw mode, which makes it intercept some +    escape codes such as `ctrl + c` and `ctrl + z`. +    Useful if you want to use those key combinations in your app. +- `window_title string` - sets the title of the terminal window. +    This may be changed later, by calling the `set_window_title()` method. +- `reset []int = [1, 2, 3, 4, 6, 7, 8, 9, 11, 13, 14, 15, 19]` - a list of reset signals, +    to setup handlers to cleanup the terminal state when they're received. +    You should not need to change this, unless you know what you're doing. + +All of these fields may be omitted, in which case, the default value will be used. +In the case of the various callbacks, they will not be fired if a handler has not been specified. + + +#### FAQ + +Q: My terminal (doesn't receive events / doesn't print anything / prints gibberish characters), +what's up with that? +A: Please check if your terminal. The module has been tested with `xterm`-based terminals on Linux +(like `gnome-terminal` and `konsole`), and `Terminal.app` and `iterm2` on macOS. +If your terminal does not work, open an issue with the output of `echo $TERM`. + +Q: There are screen tearing issues when doing large prints +A: This is an issue with how terminals render frames, +as they may decide to do so in the middle of receiving a frame, +and cannot be fully fixed unless your console implements the [synchronized updates spec](https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec). +It can be reduced *drastically*, though, by using the rendering methods built in to the module, +and by only painting frames when your app's content has actually changed. + +Q: Why does the module only emit `keydown` events, and not `keyup` like `sokol`/`gg`? +A: It's because of the way terminals emit events. Every key event is received as a keypress, +and there isn't a way of telling terminals to send keyboard events differently, +nor a reliable way of converting these into `keydown` / `keyup` events. diff --git a/v_windows/v/old/vlib/term/ui/color.v b/v_windows/v/old/vlib/term/ui/color.v new file mode 100644 index 0000000..3e0a0bb --- /dev/null +++ b/v_windows/v/old/vlib/term/ui/color.v @@ -0,0 +1,88 @@ +// radare - LGPL - Copyright 2013-2020 - pancake, xarkes +// ansi 256 color extension for r_cons +// https://en.wikipedia.org/wiki/ANSI_color + +module ui + +const ( +	value_range = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]! +	color_table = init_color_table() +) + +[direct_array_access] +fn init_color_table() []int { +	mut color_table_ := []int{len: 256} +	// ansi colors +	color_table_[0] = 0x000000 +	color_table_[1] = 0x800000 +	color_table_[2] = 0x008000 +	color_table_[3] = 0x808000 +	color_table_[4] = 0x000080 +	color_table_[5] = 0x800080 +	color_table_[6] = 0x008080 +	color_table_[7] = 0xc0c0c0 +	color_table_[8] = 0x808080 +	color_table_[9] = 0xff0000 +	color_table_[10] = 0x00ff00 +	color_table_[11] = 0xffff00 +	color_table_[12] = 0x0000ff +	color_table_[13] = 0xff00ff +	color_table_[14] = 0x00ffff +	color_table_[15] = 0xffffff +	// color palette +	for i in 0 .. 216 { +		r := ui.value_range[(i / 36) % 6] +		g := ui.value_range[(i / 6) % 6] +		b := ui.value_range[i % 6] +		color_table_[i + 16] = ((r << 16) & 0xffffff) + ((g << 8) & 0xffff) + (b & 0xff) +	} +	// grayscale +	for i in 0 .. 24 { +		r := 8 + (i * 10) +		color_table_[i + 232] = ((r << 16) & 0xffffff) + ((r << 8) & 0xffff) + (r & 0xff) +	} +	return color_table_ +} + +fn clamp(x int, y int, z int) int { +	if x < y { +		return y +	} +	if x > z { +		return z +	} +	return x +} + +fn approximate_rgb(r int, g int, b int) int { +	grey := r > 0 && r < 255 && r == g && r == b +	if grey { +		return 232 + int(f64(r) / (255 / 24.1)) +	} +	k := int(256.0 / 6) +	r2 := clamp(r / k, 0, 5) +	g2 := clamp(g / k, 0, 5) +	b2 := clamp(b / k, 0, 5) +	return 16 + (r2 * 36) + (g2 * 6) + b2 +} + +fn lookup_rgb(r int, g int, b int) int { +	color := (r << 16) + (g << 8) + b +	// lookup extended colors only, coz non-extended can be changed by users. +	for i in 16 .. 256 { +		if ui.color_table[i] == color { +			return i +		} +	} +	return -1 +} + +// converts an RGB color to an ANSI 256-color, approximating it to the nearest available color +// if an exact match is not found +fn rgb2ansi(r int, g int, b int) int { +	c := lookup_rgb(r, g, b) +	if c == -1 { +		return approximate_rgb(r, g, b) +	} +	return c +} diff --git a/v_windows/v/old/vlib/term/ui/consoleapi_windows.c.v b/v_windows/v/old/vlib/term/ui/consoleapi_windows.c.v new file mode 100644 index 0000000..a6002a6 --- /dev/null +++ b/v_windows/v/old/vlib/term/ui/consoleapi_windows.c.v @@ -0,0 +1,82 @@ +module ui + +union C.Event { +	KeyEvent              C.KEY_EVENT_RECORD +	MouseEvent            C.MOUSE_EVENT_RECORD +	WindowBufferSizeEvent C.WINDOW_BUFFER_SIZE_RECORD +	MenuEvent             C.MENU_EVENT_RECORD +	FocusEvent            C.FOCUS_EVENT_RECORD +} + +[typedef] +struct C.INPUT_RECORD { +	EventType u16 +	Event     C.Event +} + +union C.uChar { +	UnicodeChar rune +	AsciiChar   byte +} + +[typedef] +struct C.KEY_EVENT_RECORD { +	bKeyDown          int +	wRepeatCount      u16 +	wVirtualKeyCode   u16 +	wVirtualScanCode  u16 +	uChar             C.uChar +	dwControlKeyState u32 +} + +[typedef] +struct C.MOUSE_EVENT_RECORD { +	dwMousePosition   C.COORD +	dwButtonState     u32 +	dwControlKeyState u32 +	dwEventFlags      u32 +} + +[typedef] +struct C.WINDOW_BUFFER_SIZE_RECORD { +	dwSize C.COORD +} + +[typedef] +struct C.MENU_EVENT_RECORD { +	dwCommandId u32 +} + +[typedef] +struct C.FOCUS_EVENT_RECORD { +	bSetFocus int +} + +[typedef] +struct C.COORD { +	X i16 +	Y i16 +} + +[typedef] +struct C.SMALL_RECT { +	Left   u16 +	Top    u16 +	Right  u16 +	Bottom u16 +} + +[typedef] +struct C.CONSOLE_SCREEN_BUFFER_INFO { +	dwSize              C.COORD +	dwCursorPosition    C.COORD +	wAttributes         u16 +	srWindow            C.SMALL_RECT +	dwMaximumWindowSize C.COORD +} + +fn C.ReadConsoleInput(hConsoleInput C.HANDLE, lpBuffer &C.INPUT_RECORD, nLength u32, lpNumberOfEventsRead &u32) bool + +fn C.GetNumberOfConsoleInputEvents(hConsoleInput C.HANDLE, lpcNumberOfEvents &u32) bool + +fn C.GetConsoleScreenBufferInfo(handle C.HANDLE, info &C.CONSOLE_SCREEN_BUFFER_INFO) bool diff --git a/v_windows/v/old/vlib/term/ui/input.v b/v_windows/v/old/vlib/term/ui/input.v new file mode 100644 index 0000000..0532b39 --- /dev/null +++ b/v_windows/v/old/vlib/term/ui/input.v @@ -0,0 +1,241 @@ +// Copyright (c) 2020-2021 Raúl Hernández. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module ui + +import os + +pub enum KeyCode { +	null = 0 +	tab = 9 +	enter = 10 +	escape = 27 +	space = 32 +	backspace = 127 +	exclamation = 33 +	double_quote = 34 +	hashtag = 35 +	dollar = 36 +	percent = 37 +	ampersand = 38 +	single_quote = 39 +	left_paren = 40 +	right_paren = 41 +	asterisk = 42 +	plus = 43 +	comma = 44 +	minus = 45 +	period = 46 +	slash = 47 +	_0 = 48 +	_1 = 49 +	_2 = 50 +	_3 = 51 +	_4 = 52 +	_5 = 53 +	_6 = 54 +	_7 = 55 +	_8 = 56 +	_9 = 57 +	colon = 58 +	semicolon = 59 +	less_than = 60 +	equal = 61 +	greater_than = 62 +	question_mark = 63 +	at = 64 +	a = 97 +	b = 98 +	c = 99 +	d = 100 +	e = 101 +	f = 102 +	g = 103 +	h = 104 +	i = 105 +	j = 106 +	k = 107 +	l = 108 +	m = 109 +	n = 110 +	o = 111 +	p = 112 +	q = 113 +	r = 114 +	s = 115 +	t = 116 +	u = 117 +	v = 118 +	w = 119 +	x = 120 +	y = 121 +	z = 122 +	left_square_bracket = 91 +	backslash = 92 +	right_square_bracket = 93 +	caret = 94 +	underscore = 95 +	backtick = 96 +	left_curly_bracket = 123 +	vertical_bar = 124 +	right_curly_bracket = 125 +	tilde = 126 +	insert = 260 +	delete = 261 +	up = 262 +	down = 263 +	right = 264 +	left = 265 +	page_up = 266 +	page_down = 267 +	home = 268 +	end = 269 +	f1 = 290 +	f2 = 291 +	f3 = 292 +	f4 = 293 +	f5 = 294 +	f6 = 295 +	f7 = 296 +	f8 = 297 +	f9 = 298 +	f10 = 299 +	f11 = 300 +	f12 = 301 +	f13 = 302 +	f14 = 303 +	f15 = 304 +	f16 = 305 +	f17 = 306 +	f18 = 307 +	f19 = 308 +	f20 = 309 +	f21 = 310 +	f22 = 311 +	f23 = 312 +	f24 = 313 +} + +pub enum Direction { +	unknown +	up +	down +	left +	right +} + +pub enum MouseButton { +	unknown +	left +	middle +	right +} + +pub enum EventType { +	unknown +	mouse_down +	mouse_up +	mouse_move +	mouse_drag +	mouse_scroll +	key_down +	resized +} + +[flag] +pub enum Modifiers { +	ctrl +	shift +	alt +} + +pub struct Event { +pub: +	typ EventType +	// Mouse event info +	x         int +	y         int +	button    MouseButton +	direction Direction +	// Keyboard event info +	code      KeyCode +	modifiers Modifiers +	ascii     byte +	utf8      string +	// Resized event info +	width  int +	height int +} + +pub struct Context { +	ExtraContext // contains fields specific to an implementation +pub: +	cfg Config // adsasdas +mut: +	print_buf  []byte +	paused     bool +	enable_su  bool +	enable_rgb bool +pub mut: +	frame_count   u64 +	window_width  int +	window_height int +} + +pub struct Config { +	user_data  voidptr +	init_fn    fn (voidptr) +	frame_fn   fn (voidptr) +	cleanup_fn fn (voidptr) +	event_fn   fn (&Event, voidptr) +	fail_fn    fn (string) + +	buffer_size int = 256 +	frame_rate  int = 30 +	use_x11     bool + +	window_title         string +	hide_cursor          bool +	capture_events       bool +	use_alternate_buffer bool = true +	skip_init_checks     bool +	// All kill signals to set up exit listeners on: +	reset []os.Signal = [.hup, .int, .quit, .ill, .abrt, .bus, .fpe, .kill, .segv, .pipe, .alrm, .term, +	.stop, +] +} + +[inline] +fn (ctx &Context) init() { +	if ctx.cfg.init_fn != voidptr(0) { +		ctx.cfg.init_fn(ctx.cfg.user_data) +	} +} + +[inline] +fn (ctx &Context) frame() { +	if ctx.cfg.frame_fn != voidptr(0) { +		ctx.cfg.frame_fn(ctx.cfg.user_data) +	} +} + +[inline] +fn (ctx &Context) cleanup() { +	if ctx.cfg.cleanup_fn != voidptr(0) { +		ctx.cfg.cleanup_fn(ctx.cfg.user_data) +	} +} + +[inline] +fn (ctx &Context) fail(error string) { +	if ctx.cfg.fail_fn != voidptr(0) { +		ctx.cfg.fail_fn(error) +	} +} + +[inline] +fn (ctx &Context) event(event &Event) { +	if ctx.cfg.event_fn != voidptr(0) { +		ctx.cfg.event_fn(event, ctx.cfg.user_data) +	} +} diff --git a/v_windows/v/old/vlib/term/ui/input_nix.c.v b/v_windows/v/old/vlib/term/ui/input_nix.c.v new file mode 100644 index 0000000..e806fb8 --- /dev/null +++ b/v_windows/v/old/vlib/term/ui/input_nix.c.v @@ -0,0 +1,70 @@ +// Copyright (c) 2020-2021 Raúl Hernández. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module ui + +struct ExtraContext { +mut: +	read_buf []byte +} + +const ( +	ctx_ptr = &Context(0) +) + +pub fn init(cfg Config) &Context { +	mut ctx := &Context{ +		cfg: cfg +	} +	ctx.read_buf = []byte{cap: cfg.buffer_size} + +	// lmao +	unsafe { +		x := &ui.ctx_ptr +		*x = ctx +		_ = x +	} +	return ctx +} + +[inline] +fn save_title() { +	// restore the previously saved terminal title +	print('\x1b[22;0t') +} + +[inline] +fn load_title() { +	// restore the previously saved terminal title +	print('\x1b[23;0t') +} + +pub fn (mut ctx Context) run() ? { +	if ctx.cfg.use_x11 { +		ctx.fail('error: x11 backend not implemented yet') +		exit(1) +	} else { +		ctx.termios_setup() ? +		ctx.termios_loop() +	} +} + +// shifts the array left, to remove any data that was just read, and updates its len +// TODO: remove +[inline] +fn (mut ctx Context) shift(len int) { +	unsafe { +		C.memmove(ctx.read_buf.data, &byte(ctx.read_buf.data) + len, ctx.read_buf.cap - len) +		ctx.resize_arr(ctx.read_buf.len - len) +	} +} + +// TODO: don't actually do this, lmao +[inline] +fn (mut ctx Context) resize_arr(size int) { +	mut l := unsafe { &ctx.read_buf.len } +	unsafe { +		*l = size +		_ = l +	} +} diff --git a/v_windows/v/old/vlib/term/ui/input_windows.c.v b/v_windows/v/old/vlib/term/ui/input_windows.c.v new file mode 100644 index 0000000..bd9782d --- /dev/null +++ b/v_windows/v/old/vlib/term/ui/input_windows.c.v @@ -0,0 +1,326 @@ +// Copyright (c) 2020-2021 Raúl Hernández. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module ui + +import os +import time + +const ( +	buf_size         = 64 +	ctx_ptr          = &Context(0) +	stdin_at_startup = u32(0) +) + +struct ExtraContext { +mut: +	stdin_handle  C.HANDLE +	stdout_handle C.HANDLE +	read_buf      [buf_size]C.INPUT_RECORD +	mouse_down    MouseButton +} + +fn restore_terminal_state() { +	if ui.ctx_ptr != 0 { +		if ui.ctx_ptr.cfg.use_alternate_buffer { +			// clear the terminal and set the cursor to the origin +			print('\x1b[2J\x1b[3J') +			print('\x1b[?1049l') +		} +		C.SetConsoleMode(ui.ctx_ptr.stdin_handle, ui.stdin_at_startup) +	} +	load_title() +	os.flush() +} + +pub fn init(cfg Config) &Context { +	mut ctx := &Context{ +		cfg: cfg +	} +	// get the standard input handle +	stdin_handle := C.GetStdHandle(C.STD_INPUT_HANDLE) +	stdout_handle := C.GetStdHandle(C.STD_OUTPUT_HANDLE) +	if stdin_handle == C.INVALID_HANDLE_VALUE { +		panic('could not get stdin handle') +	} +	// save the current input mode, to be restored on exit +	if C.GetConsoleMode(stdin_handle, &ui.stdin_at_startup) == 0 { +		panic('could not get stdin console mode') +	} + +	// enable extended input flags (see https://stackoverflow.com/a/46802726) +	// 0x80 == C.ENABLE_EXTENDED_FLAGS +	if C.SetConsoleMode(stdin_handle, 0x80) == 0 { +		panic('could not set raw input mode') +	} +	// enable window and mouse input events. +	if C.SetConsoleMode(stdin_handle, C.ENABLE_WINDOW_INPUT | C.ENABLE_MOUSE_INPUT) == 0 { +		panic('could not set raw input mode') +	} +	// store the current title, so restore_terminal_state can get it back +	save_title() + +	if ctx.cfg.use_alternate_buffer { +		// switch to the alternate buffer +		print('\x1b[?1049h') +		// clear the terminal and set the cursor to the origin +		print('\x1b[2J\x1b[3J\x1b[1;1H') +	} + +	if ctx.cfg.hide_cursor { +		ctx.hide_cursor() +		ctx.flush() +	} + +	if ctx.cfg.window_title != '' { +		print('\x1b]0;$ctx.cfg.window_title\x07') +	} + +	unsafe { +		x := &ui.ctx_ptr +		*x = ctx +	} +	C.atexit(restore_terminal_state) +	for code in ctx.cfg.reset { +		os.signal_opt(code, fn (_ os.Signal) { +			mut c := ui.ctx_ptr +			if c != 0 { +				c.cleanup() +			} +			exit(0) +		}) or {} +	} + +	ctx.stdin_handle = stdin_handle +	ctx.stdout_handle = stdout_handle +	return ctx +} + +pub fn (mut ctx Context) run() ? { +	frame_time := 1_000_000 / ctx.cfg.frame_rate +	mut init_called := false +	mut sw := time.new_stopwatch(auto_start: false) +	mut sleep_len := 0 +	for { +		if !init_called { +			ctx.init() +			init_called = true +		} +		if sleep_len > 0 { +			time.sleep(sleep_len * time.microsecond) +		} +		if !ctx.paused { +			sw.restart() +			if ctx.cfg.event_fn != voidptr(0) { +				ctx.parse_events() +			} +			ctx.frame() +			sw.pause() +			e := sw.elapsed().microseconds() +			sleep_len = frame_time - int(e) +			ctx.frame_count++ +		} +	} +} + +fn (mut ctx Context) parse_events() { +	nr_events := u32(0) +	if !C.GetNumberOfConsoleInputEvents(ctx.stdin_handle, &nr_events) { +		panic('could not get number of events in stdin') +	} +	if nr_events < 1 { +		return +	} + +	// print('$nr_events | ') +	if !C.ReadConsoleInput(ctx.stdin_handle, &ctx.read_buf[0], ui.buf_size, &nr_events) { +		panic('could not read from stdin') +	} +	for i in 0 .. nr_events { +		// print('E ') +		match int(ctx.read_buf[i].EventType) { +			C.KEY_EVENT { +				e := unsafe { ctx.read_buf[i].Event.KeyEvent } +				ch := e.wVirtualKeyCode +				ascii := unsafe { e.uChar.AsciiChar } +				if e.bKeyDown == 0 { +					continue +				} +				// we don't handle key_up events because they don't exist on linux... +				// see: https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes +				code := match int(ch) { +					C.VK_BACK { KeyCode.backspace } +					C.VK_RETURN { KeyCode.enter } +					C.VK_PRIOR { KeyCode.page_up } +					14...20 { KeyCode.null } +					C.VK_NEXT { KeyCode.page_down } +					C.VK_END { KeyCode.end } +					C.VK_HOME { KeyCode.home } +					C.VK_LEFT { KeyCode.left } +					C.VK_UP { KeyCode.up } +					C.VK_RIGHT { KeyCode.right } +					C.VK_DOWN { KeyCode.down } +					C.VK_INSERT { KeyCode.insert } +					C.VK_DELETE { KeyCode.delete } +					65...90 { KeyCode(ch + 32) } // letters +					91...93 { KeyCode.null } // special keys +					96...105 { KeyCode(ch - 48) } // numpad numbers +					112...135 { KeyCode(ch + 178) } // f1 - f24 +					else { KeyCode(ascii) } +				} + +				mut modifiers := Modifiers{} +				if e.dwControlKeyState & (0x1 | 0x2) != 0 { +					modifiers.set(.alt) +				} +				if e.dwControlKeyState & (0x4 | 0x8) != 0 { +					modifiers.set(.ctrl) +				} +				if e.dwControlKeyState & 0x10 != 0 { +					modifiers.set(.shift) +				} + +				mut event := &Event{ +					typ: .key_down +					modifiers: modifiers +					code: code +					ascii: ascii +					width: int(e.dwControlKeyState) +					height: int(e.wVirtualKeyCode) +					utf8: unsafe { e.uChar.UnicodeChar.str() } +				} +				ctx.event(event) +			} +			C.MOUSE_EVENT { +				e := unsafe { ctx.read_buf[i].Event.MouseEvent } +				sb_info := C.CONSOLE_SCREEN_BUFFER_INFO{} +				if !C.GetConsoleScreenBufferInfo(ctx.stdout_handle, &sb_info) { +					panic('could not get screenbuffer info') +				} +				x := e.dwMousePosition.X + 1 +				y := int(e.dwMousePosition.Y) - sb_info.srWindow.Top + 1 +				mut modifiers := Modifiers{} +				if e.dwControlKeyState & (0x1 | 0x2) != 0 { +					modifiers.set(.alt) +				} +				if e.dwControlKeyState & (0x4 | 0x8) != 0 { +					modifiers.set(.ctrl) +				} +				if e.dwControlKeyState & 0x10 != 0 { +					modifiers.set(.shift) +				} +				// TODO: handle capslock/numlock/etc?? events exist for those keys +				match int(e.dwEventFlags) { +					C.MOUSE_MOVED { +						mut button := match int(e.dwButtonState) { +							0 { MouseButton.unknown } +							1 { MouseButton.left } +							2 { MouseButton.right } +							else { MouseButton.middle } +						} +						typ := if e.dwButtonState == 0 { +							if ctx.mouse_down != .unknown { +								button = ctx.mouse_down +								ctx.mouse_down = .unknown +								EventType.mouse_up +							} else { +								EventType.mouse_move +							} +						} else { +							EventType.mouse_drag +						} +						ctx.event(&Event{ +							typ: typ +							x: x +							y: y +							button: button +							modifiers: modifiers +						}) +					} +					C.MOUSE_WHEELED { +						ctx.event(&Event{ +							typ: .mouse_scroll +							direction: if i16(e.dwButtonState >> 16) < 0 { +								Direction.up +							} else { +								Direction.down +							} +							x: x +							y: y +							modifiers: modifiers +						}) +					} +					0x0008 /* C.MOUSE_HWHEELED */ { +						ctx.event(&Event{ +							typ: .mouse_scroll +							direction: if i16(e.dwButtonState >> 16) < 0 { +								Direction.right +							} else { +								Direction.left +							} +							x: x +							y: y +							modifiers: modifiers +						}) +					} +					0 /* CLICK */, C.DOUBLE_CLICK { +						button := match int(e.dwButtonState) { +							0 { ctx.mouse_down } +							1 { MouseButton.left } +							2 { MouseButton.right } +							else { MouseButton.middle } +						} +						ctx.mouse_down = button +						ctx.event(&Event{ +							typ: .mouse_down +							x: x +							y: y +							button: button +							modifiers: modifiers +						}) +					} +					else {} +				} +			} +			C.WINDOW_BUFFER_SIZE_EVENT { +				// e := unsafe { ctx.read_buf[i].Event.WindowBufferSizeEvent } +				sb := C.CONSOLE_SCREEN_BUFFER_INFO{} +				if !C.GetConsoleScreenBufferInfo(ctx.stdout_handle, &sb) { +					panic('could not get screenbuffer info') +				} +				w := sb.srWindow.Right - sb.srWindow.Left + 1 +				h := sb.srWindow.Bottom - sb.srWindow.Top + 1 +				utf8 := '($ctx.window_width, $ctx.window_height) -> ($w, $h)' +				if w != ctx.window_width || h != ctx.window_height { +					ctx.window_width, ctx.window_height = w, h +					mut event := &Event{ +						typ: .resized +						width: ctx.window_width +						height: ctx.window_height +						utf8: utf8 +					} +					ctx.event(event) +				} +			} +			// C.MENU_EVENT { +			// 	e := unsafe { ctx.read_buf[i].Event.MenuEvent } +			// } +			// C.FOCUS_EVENT { +			// 	e := unsafe { ctx.read_buf[i].Event.FocusEvent } +			// } +			else {} +		} +	} +} + +[inline] +fn save_title() { +	// restore the previously saved terminal title +	print('\x1b[22;0t') +} + +[inline] +fn load_title() { +	// restore the previously saved terminal title +	print('\x1b[23;0t') +} diff --git a/v_windows/v/old/vlib/term/ui/termios_nix.c.v b/v_windows/v/old/vlib/term/ui/termios_nix.c.v new file mode 100644 index 0000000..fb5ff76 --- /dev/null +++ b/v_windows/v/old/vlib/term/ui/termios_nix.c.v @@ -0,0 +1,530 @@ +// Copyright (c) 2020-2021 Raúl Hernández. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module ui + +import os +import time + +#include <termios.h> +#include <sys/ioctl.h> +#include <signal.h> + +fn C.tcgetattr(fd int, termios_p &C.termios) int + +fn C.tcsetattr(fd int, optional_actions int, termios_p &C.termios) int + +fn C.ioctl(fd int, request u64, arg voidptr) int + +struct C.termios { +mut: +	c_iflag u32 +	c_lflag u32 +	c_cc    [32]byte +} + +struct C.winsize { +	ws_row u16 +	ws_col u16 +} + +const ( +	termios_at_startup = get_termios() +) + +[inline] +fn get_termios() C.termios { +	mut t := C.termios{} +	C.tcgetattr(C.STDIN_FILENO, &t) +	return t +} + +[inline] +fn get_terminal_size() (u16, u16) { +	winsz := C.winsize{} +	C.ioctl(0, C.TIOCGWINSZ, &winsz) +	return winsz.ws_row, winsz.ws_col +} + +fn restore_terminal_state_signal(_ os.Signal) { +	restore_terminal_state() +} + +fn restore_terminal_state() { +	termios_reset() +	mut c := ctx_ptr +	if c != 0 { +		c.paused = true +		load_title() +	} +	os.flush() +} + +fn (mut ctx Context) termios_setup() ? { +	// store the current title, so restore_terminal_state can get it back +	save_title() + +	if !ctx.cfg.skip_init_checks && !(os.is_atty(C.STDIN_FILENO) != 0 +		&& os.is_atty(C.STDOUT_FILENO) != 0) { +		return error('not running under a TTY') +	} + +	mut termios := get_termios() + +	if ctx.cfg.capture_events { +		// Set raw input mode by unsetting ICANON and ECHO, +		// as well as disable e.g. ctrl+c and ctrl.z +		termios.c_iflag &= ~u32(C.IGNBRK | C.BRKINT | C.PARMRK | C.IXON) +		termios.c_lflag &= ~u32(C.ICANON | C.ISIG | C.ECHO | C.IEXTEN | C.TOSTOP) +	} else { +		// Set raw input mode by unsetting ICANON and ECHO +		termios.c_lflag &= ~u32(C.ICANON | C.ECHO) +	} + +	if ctx.cfg.hide_cursor { +		ctx.hide_cursor() +		ctx.flush() +	} + +	if ctx.cfg.window_title != '' { +		print('\x1b]0;$ctx.cfg.window_title\x07') +	} + +	if !ctx.cfg.skip_init_checks { +		// prevent blocking during the feature detections, but allow enough time for the terminal +		// to send back the relevant input data +		termios.c_cc[C.VTIME] = 1 +		termios.c_cc[C.VMIN] = 0 +		C.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH, &termios) +		// feature-test the SU spec +		sx, sy := get_cursor_position() +		print('$bsu$esu') +		ex, ey := get_cursor_position() +		if sx == ex && sy == ey { +			// the terminal either ignored or handled the sequence properly, enable SU +			ctx.enable_su = true +		} else { +			ctx.draw_line(sx, sy, ex, ey) +			ctx.set_cursor_position(sx, sy) +			ctx.flush() +		} +		// feature-test rgb (truecolor) support +		ctx.enable_rgb = supports_truecolor() +	} +	// Prevent stdin from blocking by making its read time 0 +	termios.c_cc[C.VTIME] = 0 +	termios.c_cc[C.VMIN] = 0 +	C.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH, &termios) +	// enable mouse input +	print('\x1b[?1003h\x1b[?1006h') +	if ctx.cfg.use_alternate_buffer { +		// switch to the alternate buffer +		print('\x1b[?1049h') +		// clear the terminal and set the cursor to the origin +		print('\x1b[2J\x1b[3J\x1b[1;1H') +	} +	ctx.window_height, ctx.window_width = get_terminal_size() + +	// Reset console on exit +	C.atexit(restore_terminal_state) +	os.signal_opt(.tstp, restore_terminal_state_signal) or {} +	os.signal_opt(.cont, fn (_ os.Signal) { +		mut c := ctx_ptr +		if c != 0 { +			c.termios_setup() or { panic(err) } +			c.window_height, c.window_width = get_terminal_size() +			mut event := &Event{ +				typ: .resized +				width: c.window_width +				height: c.window_height +			} +			c.paused = false +			c.event(event) +		} +	}) or {} +	for code in ctx.cfg.reset { +		os.signal_opt(code, fn (_ os.Signal) { +			mut c := ctx_ptr +			if c != 0 { +				c.cleanup() +			} +			exit(0) +		}) or {} +	} + +	os.signal_opt(.winch, fn (_ os.Signal) { +		mut c := ctx_ptr +		if c != 0 { +			c.window_height, c.window_width = get_terminal_size() + +			mut event := &Event{ +				typ: .resized +				width: c.window_width +				height: c.window_height +			} +			c.event(event) +		} +	}) or {} + +	os.flush() +} + +fn get_cursor_position() (int, int) { +	print('\033[6n') +	mut s := '' +	unsafe { +		buf := malloc_noscan(25) +		len := C.read(C.STDIN_FILENO, buf, 24) +		buf[len] = 0 +		s = tos(buf, len) +	} +	a := s[2..].split(';') +	if a.len != 2 { +		return -1, -1 +	} +	return a[0].int(), a[1].int() +} + +fn supports_truecolor() bool { +	// faster/simpler, but less reliable, check +	if os.getenv('COLORTERM') in ['truecolor', '24bit'] { +		return true +	} +	// set the bg color to some arbirtrary value (#010203), assumed not to be the default +	print('\x1b[48:2:1:2:3m') +	// andquery the current color +	print('\x1bP\$qm\x1b\\') +	mut s := '' +	unsafe { +		buf := malloc_noscan(25) +		len := C.read(C.STDIN_FILENO, buf, 24) +		buf[len] = 0 +		s = tos(buf, len) +	} +	return s.contains('1:2:3') +} + +fn termios_reset() { +	// C.TCSANOW ?? +	C.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH, &ui.termios_at_startup) +	print('\x1b[?1003l\x1b[?1006l\x1b[?25h') +	c := ctx_ptr +	if c != 0 && c.cfg.use_alternate_buffer { +		print('\x1b[?1049l') +	} +	os.flush() +} + +/////////////////////////////////////////// +// TODO: do multiple sleep/read cycles, rather than one big one +fn (mut ctx Context) termios_loop() { +	frame_time := 1_000_000 / ctx.cfg.frame_rate +	mut init_called := false +	mut sw := time.new_stopwatch(auto_start: false) +	mut sleep_len := 0 +	for { +		if !init_called { +			ctx.init() +			init_called = true +		} +		// println('SLEEPING: $sleep_len') +		if sleep_len > 0 { +			time.sleep(sleep_len * time.microsecond) +		} +		if !ctx.paused { +			sw.restart() +			if ctx.cfg.event_fn != voidptr(0) { +				unsafe { +					len := C.read(C.STDIN_FILENO, &byte(ctx.read_buf.data) + ctx.read_buf.len, +						ctx.read_buf.cap - ctx.read_buf.len) +					ctx.resize_arr(ctx.read_buf.len + len) +				} +				if ctx.read_buf.len > 0 { +					ctx.parse_events() +				} +			} +			ctx.frame() +			sw.pause() +			e := sw.elapsed().microseconds() +			sleep_len = frame_time - int(e) + +			ctx.frame_count++ +		} +	} +} + +fn (mut ctx Context) parse_events() { +	// Stop this from getting stuck in rare cases where something isn't parsed correctly +	mut nr_iters := 0 +	for ctx.read_buf.len > 0 { +		nr_iters++ +		if nr_iters > 100 { +			ctx.shift(1) +		} +		mut event := &Event(0) +		if ctx.read_buf[0] == 0x1b { +			e, len := escape_sequence(ctx.read_buf.bytestr()) +			event = e +			ctx.shift(len) +		} else { +			event = single_char(ctx.read_buf.bytestr()) +			ctx.shift(1) +		} +		if event != 0 { +			ctx.event(event) +			nr_iters = 0 +		} +	} +} + +fn single_char(buf string) &Event { +	ch := buf[0] + +	mut event := &Event{ +		typ: .key_down +		ascii: ch +		code: KeyCode(ch) +		utf8: buf +	} + +	match ch { +		// special handling for `ctrl + letter` +		// TODO: Fix assoc in V and remove this workaround :/ +		// 1  ... 26 { event = Event{ ...event, code: KeyCode(96 | ch), modifiers: .ctrl  } } +		// 65 ... 90 { event = Event{ ...event, code: KeyCode(32 | ch), modifiers: .shift } } +		// The bit `or`s here are really just `+`'s, just written in this way for a tiny performance improvement +		// don't treat tab, enter as ctrl+i, ctrl+j +		1...8, 11...26 { +			event = &Event{ +				typ: event.typ +				ascii: event.ascii +				utf8: event.utf8 +				code: KeyCode(96 | ch) +				modifiers: .ctrl +			} +		} +		65...90 { +			event = &Event{ +				typ: event.typ +				ascii: event.ascii +				utf8: event.utf8 +				code: KeyCode(32 | ch) +				modifiers: .shift +			} +		} +		else {} +	} + +	return event +} + +// Gets an entire, independent escape sequence from the buffer +// Normally, this just means reading until the first letter, but there are some exceptions... +fn escape_end(buf string) int { +	mut i := 0 +	for { +		if i + 1 == buf.len { +			return buf.len +		} + +		if buf[i].is_letter() || buf[i] == `~` { +			if buf[i] == `O` && i + 2 <= buf.len { +				n := buf[i + 1] +				if (n >= `A` && n <= `D`) || (n >= `P` && n <= `S`) || n == `F` || n == `H` { +					return i + 2 +				} +			} +			return i + 1 +			// escape hatch to avoid potential issues/crashes, although ideally this should never eval to true +		} else if buf[i + 1] == 0x1b { +			return i + 1 +		} +		i++ +	} +	// this point should be unreachable +	assert false +	return 0 +} + +fn escape_sequence(buf_ string) (&Event, int) { +	end := escape_end(buf_) +	single := buf_[..end] // read until the end of the sequence +	buf := single[1..] // skip the escape character + +	if buf.len == 0 { +		return &Event{ +			typ: .key_down +			ascii: 27 +			code: .escape +			utf8: single +		}, 1 +	} + +	if buf.len == 1 { +		c := single_char(buf) +		mut modifiers := c.modifiers +		modifiers.set(.alt) +		return &Event{ +			typ: c.typ +			ascii: c.ascii +			code: c.code +			utf8: single +			modifiers: modifiers +		}, 2 +	} +	// ---------------- +	//   Mouse events +	// ---------------- +	// Documentation: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking +	if buf.len > 2 && buf[1] == `<` { +		split := buf[2..].split(';') +		if split.len < 3 { +			return &Event(0), 0 +		} + +		typ, x, y := split[0].int(), split[1].int(), split[2].int() +		lo := typ & 0b00011 +		hi := typ & 0b11100 + +		mut modifiers := Modifiers{} +		if hi & 4 != 0 { +			modifiers.set(.shift) +		} +		if hi & 8 != 0 { +			modifiers.set(.alt) +		} +		if hi & 16 != 0 { +			modifiers.set(.ctrl) +		} + +		match typ { +			0...31 { +				last := buf[buf.len - 1] +				button := if lo < 3 { MouseButton(lo + 1) } else { MouseButton.unknown } +				event := if last == `m` || lo == 3 { +					EventType.mouse_up +				} else { +					EventType.mouse_down +				} + +				return &Event{ +					typ: event +					x: x +					y: y +					button: button +					modifiers: modifiers +					utf8: single +				}, end +			} +			32...63 { +				button, event := if lo < 3 { +					MouseButton(lo + 1), EventType.mouse_drag +				} else { +					MouseButton.unknown, EventType.mouse_move +				} + +				return &Event{ +					typ: event +					x: x +					y: y +					button: button +					modifiers: modifiers +					utf8: single +				}, end +			} +			64...95 { +				direction := if typ & 1 == 0 { Direction.down } else { Direction.up } +				return &Event{ +					typ: .mouse_scroll +					x: x +					y: y +					direction: direction +					modifiers: modifiers +					utf8: single +				}, end +			} +			else { +				return &Event{ +					typ: .unknown +					utf8: single +				}, end +			} +		} +	} +	// ---------------------------- +	//   Special key combinations +	// ---------------------------- + +	mut code := KeyCode.null +	mut modifiers := Modifiers{} +	match buf { +		'[A', 'OA' { code = .up } +		'[B', 'OB' { code = .down } +		'[C', 'OC' { code = .right } +		'[D', 'OD' { code = .left } +		'[5~', '[[5~' { code = .page_up } +		'[6~', '[[6~' { code = .page_down } +		'[F', 'OF', '[4~', '[[8~' { code = .end } +		'[H', 'OH', '[1~', '[[7~' { code = .home } +		'[2~' { code = .insert } +		'[3~' { code = .delete } +		'OP', '[11~' { code = .f1 } +		'OQ', '[12~' { code = .f2 } +		'OR', '[13~' { code = .f3 } +		'OS', '[14~' { code = .f4 } +		'[15~' { code = .f5 } +		'[17~' { code = .f6 } +		'[18~' { code = .f7 } +		'[19~' { code = .f8 } +		'[20~' { code = .f9 } +		'[21~' { code = .f10 } +		'[23~' { code = .f11 } +		'[24~' { code = .f12 } +		else {} +	} + +	if buf == '[Z' { +		code = .tab +		modifiers.set(.shift) +	} + +	if buf.len == 5 && buf[0] == `[` && buf[1].is_digit() && buf[2] == `;` { +		match buf[3] { +			`2` { modifiers = .shift } +			`3` { modifiers = .alt } +			`4` { modifiers = .shift | .alt } +			`5` { modifiers = .ctrl } +			`6` { modifiers = .ctrl | .shift } +			`7` { modifiers = .ctrl | .alt } +			`8` { modifiers = .ctrl | .alt | .shift } +			else {} +		} + +		if buf[1] == `1` { +			match buf[4] { +				`A` { code = KeyCode.up } +				`B` { code = KeyCode.down } +				`C` { code = KeyCode.right } +				`D` { code = KeyCode.left } +				`F` { code = KeyCode.end } +				`H` { code = KeyCode.home } +				`P` { code = KeyCode.f1 } +				`Q` { code = KeyCode.f2 } +				`R` { code = KeyCode.f3 } +				`S` { code = KeyCode.f4 } +				else {} +			} +		} else if buf[1] == `5` { +			code = KeyCode.page_up +		} else if buf[1] == `6` { +			code = KeyCode.page_down +		} +	} + +	return &Event{ +		typ: .key_down +		code: code +		utf8: single +		modifiers: modifiers +	}, end +} diff --git a/v_windows/v/old/vlib/term/ui/ui.v b/v_windows/v/old/vlib/term/ui/ui.v new file mode 100644 index 0000000..6ba3d7c --- /dev/null +++ b/v_windows/v/old/vlib/term/ui/ui.v @@ -0,0 +1,256 @@ +// Copyright (c) 2020-2021 Raúl Hernández. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module ui + +import strings + +pub struct Color { +pub: +	r byte +	g byte +	b byte +} + +pub fn (c Color) hex() string { +	return '#$c.r.hex()$c.g.hex()$c.b.hex()' +} + +// Synchronized Updates spec, designed to avoid tearing during renders +// https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec +const ( +	bsu = '\x1bP=1s\x1b\\' +	esu = '\x1bP=2s\x1b\\' +) + +// write puts the string `s` into the print buffer. +[inline] +pub fn (mut ctx Context) write(s string) { +	if s == '' { +		return +	} +	unsafe { ctx.print_buf.push_many(s.str, s.len) } +} + +// flush displays the accumulated print buffer to the screen. +[inline] +pub fn (mut ctx Context) flush() { +	// TODO: Diff the previous frame against this one, and only render things that changed? +	if !ctx.enable_su { +		C.write(1, ctx.print_buf.data, ctx.print_buf.len) +	} else { +		C.write(1, ui.bsu.str, ui.bsu.len) +		C.write(1, ctx.print_buf.data, ctx.print_buf.len) +		C.write(1, ui.esu.str, ui.esu.len) +	} +	ctx.print_buf.clear() +} + +// bold sets the character state to bold. +[inline] +pub fn (mut ctx Context) bold() { +	ctx.write('\x1b[1m') +} + +// set_cursor_position positions the cusor at the given coordinates `x`,`y`. +[inline] +pub fn (mut ctx Context) set_cursor_position(x int, y int) { +	ctx.write('\x1b[$y;${x}H') +} + +// show_cursor will make the cursor appear if it is not already visible +[inline] +pub fn (mut ctx Context) show_cursor() { +	ctx.write('\x1b[?25h') +} + +// hide_cursor will make the cursor invisible +[inline] +pub fn (mut ctx Context) hide_cursor() { +	ctx.write('\x1b[?25l') +} + +// set_color sets the current foreground color used by any succeeding `draw_*` calls. +[inline] +pub fn (mut ctx Context) set_color(c Color) { +	if ctx.enable_rgb { +		ctx.write('\x1b[38;2;${int(c.r)};${int(c.g)};${int(c.b)}m') +	} else { +		ctx.write('\x1b[38;5;${rgb2ansi(c.r, c.g, c.b)}m') +	} +} + +// set_color sets the current background color used by any succeeding `draw_*` calls. +[inline] +pub fn (mut ctx Context) set_bg_color(c Color) { +	if ctx.enable_rgb { +		ctx.write('\x1b[48;2;${int(c.r)};${int(c.g)};${int(c.b)}m') +	} else { +		ctx.write('\x1b[48;5;${rgb2ansi(c.r, c.g, c.b)}m') +	} +} + +// reset_color sets the current foreground color back to it's default value. +[inline] +pub fn (mut ctx Context) reset_color() { +	ctx.write('\x1b[39m') +} + +// reset_bg_color sets the current background color back to it's default value. +[inline] +pub fn (mut ctx Context) reset_bg_color() { +	ctx.write('\x1b[49m') +} + +// reset restores the state of all colors and text formats back to their default values. +[inline] +pub fn (mut ctx Context) reset() { +	ctx.write('\x1b[0m') +} + +[inline] +pub fn (mut ctx Context) clear() { +	ctx.write('\x1b[2J\x1b[3J') +} + +// set_window_title sets the string `s` as the window title. +[inline] +pub fn (mut ctx Context) set_window_title(s string) { +	print('\x1b]0;$s\x07') +} + +// draw_point draws a point at position `x`,`y`. +[inline] +pub fn (mut ctx Context) draw_point(x int, y int) { +	ctx.set_cursor_position(x, y) +	ctx.write(' ') +} + +// draw_text draws the string `s`, starting from position `x`,`y`. +[inline] +pub fn (mut ctx Context) draw_text(x int, y int, s string) { +	ctx.set_cursor_position(x, y) +	ctx.write(s) +} + +// draw_line draws a line segment, starting at point `x`,`y`, and ending at point `x2`,`y2`. +pub fn (mut ctx Context) draw_line(x int, y int, x2 int, y2 int) { +	min_x, min_y := if x < x2 { x } else { x2 }, if y < y2 { y } else { y2 } +	max_x, _ := if x > x2 { x } else { x2 }, if y > y2 { y } else { y2 } +	if y == y2 { +		// Horizontal line, performance improvement +		ctx.set_cursor_position(min_x, min_y) +		ctx.write(strings.repeat(` `, max_x + 1 - min_x)) +		return +	} +	// Draw the various points with Bresenham's line algorithm: +	mut x0, x1 := x, x2 +	mut y0, y1 := y, y2 +	sx := if x0 < x1 { 1 } else { -1 } +	sy := if y0 < y1 { 1 } else { -1 } +	dx := if x0 < x1 { x1 - x0 } else { x0 - x1 } +	dy := if y0 < y1 { y0 - y1 } else { y1 - y0 } // reversed +	mut err := dx + dy +	for { +		// res << Segment{ x0, y0 } +		ctx.draw_point(x0, y0) +		if x0 == x1 && y0 == y1 { +			break +		} +		e2 := 2 * err +		if e2 >= dy { +			err += dy +			x0 += sx +		} +		if e2 <= dx { +			err += dx +			y0 += sy +		} +	} +} + +// draw_dashed_line draws a dashed line segment, starting at point `x`,`y`, and ending at point `x2`,`y2`. +pub fn (mut ctx Context) draw_dashed_line(x int, y int, x2 int, y2 int) { +	// Draw the various points with Bresenham's line algorithm: +	mut x0, x1 := x, x2 +	mut y0, y1 := y, y2 +	sx := if x0 < x1 { 1 } else { -1 } +	sy := if y0 < y1 { 1 } else { -1 } +	dx := if x0 < x1 { x1 - x0 } else { x0 - x1 } +	dy := if y0 < y1 { y0 - y1 } else { y1 - y0 } // reversed +	mut err := dx + dy +	mut i := 0 +	for { +		if i % 2 == 0 { +			ctx.draw_point(x0, y0) +		} +		if x0 == x1 && y0 == y1 { +			break +		} +		e2 := 2 * err +		if e2 >= dy { +			err += dy +			x0 += sx +		} +		if e2 <= dx { +			err += dx +			y0 += sy +		} +		i++ +	} +} + +// draw_rect draws a rectangle, starting at top left `x`,`y`, and ending at bottom right `x2`,`y2`. +pub fn (mut ctx Context) draw_rect(x int, y int, x2 int, y2 int) { +	if y == y2 || x == x2 { +		ctx.draw_line(x, y, x2, y2) +		return +	} +	min_y, max_y := if y < y2 { y, y2 } else { y2, y } +	for y_pos in min_y .. max_y + 1 { +		ctx.draw_line(x, y_pos, x2, y_pos) +	} +} + +// draw_empty_dashed_rect draws a rectangle with dashed lines, starting at top left `x`,`y`, and ending at bottom right `x2`,`y2`. +pub fn (mut ctx Context) draw_empty_dashed_rect(x int, y int, x2 int, y2 int) { +	if y == y2 || x == x2 { +		ctx.draw_dashed_line(x, y, x2, y2) +		return +	} + +	min_x, max_x := if x < x2 { x, x2 } else { x2, x } +	min_y, max_y := if y < y2 { y, y2 } else { y2, y } + +	ctx.draw_dashed_line(min_x, min_y, max_x, min_y) +	ctx.draw_dashed_line(min_x, min_y, min_x, max_y) +	if (max_y - min_y) & 1 == 0 { +		ctx.draw_dashed_line(min_x, max_y, max_x, max_y) +	} else { +		ctx.draw_dashed_line(min_x + 1, max_y, max_x, max_y) +	} +	if (max_x - min_x) & 1 == 0 { +		ctx.draw_dashed_line(max_x, min_y, max_x, max_y) +	} else { +		ctx.draw_dashed_line(max_x, min_y + 1, max_x, max_y) +	} +} + +// draw_empty_rect draws a rectangle with no fill, starting at top left `x`,`y`, and ending at bottom right `x2`,`y2`. +pub fn (mut ctx Context) draw_empty_rect(x int, y int, x2 int, y2 int) { +	if y == y2 || x == x2 { +		ctx.draw_line(x, y, x2, y2) +		return +	} +	ctx.draw_line(x, y, x2, y) +	ctx.draw_line(x, y2, x2, y2) +	ctx.draw_line(x, y, x, y2) +	ctx.draw_line(x2, y, x2, y2) +} + +// horizontal_separator draws a horizontal separator, spanning the width of the screen. +[inline] +pub fn (mut ctx Context) horizontal_separator(y int) { +	ctx.set_cursor_position(0, y) +	ctx.write(strings.repeat(`-`, ctx.window_width)) // /* `⎽` */ +}  | 
