diff --git a/examples/term.ui/event_viewer.v b/examples/term.ui/event_viewer.v index 74889e08ba..73abffe4b6 100644 --- a/examples/term.ui/event_viewer.v +++ b/examples/term.ui/event_viewer.v @@ -12,6 +12,12 @@ fn event(e &tui.Event, x voidptr) { app.tui.write('V term.input event viewer (press `esc` to exit)\n\n') app.tui.write('$e') app.tui.write('\n\nRaw event bytes: "${e.utf8.bytes().hex()}" = ${e.utf8.bytes()}') + if e.modifiers != 0 { + app.tui.write('\nModifiers: $e.modifiers = ') + if e.modifiers & tui.ctrl != 0 { app.tui.write('ctrl. ') } + if e.modifiers & tui.shift != 0 { app.tui.write('shift ') } + if e.modifiers & tui.alt != 0 { app.tui.write('alt. ') } + } app.tui.flush() if e.typ == .key_down && e.code == .escape { exit(0) } @@ -28,5 +34,5 @@ app.tui = tui.init( frame_rate: 60 ) -println('V term.input event viewer (press `esc` to exit)\n\n') +println('V term.ui event viewer (press `esc` to exit)\n\n') app.tui.run() diff --git a/examples/term.ui/term_drawing.v b/examples/term.ui/term_drawing.v index a341689491..42eb7ad570 100644 --- a/examples/term.ui/term_drawing.v +++ b/examples/term.ui/term_drawing.v @@ -165,6 +165,10 @@ fn event(event &tui.Event, x voidptr) { } } .mouse_scroll { + app.mouse_pos = { + x: event.x + y: event.y + } if event.direction == .down { app.inc_size() } else { @@ -175,17 +179,17 @@ fn event(event &tui.Event, x voidptr) { match event.code { .f1, ._1 { oevent := *event - nevent := { oevent | button: tui.MouseButton.primary, x: app.mouse_pos.x , y: app.mouse_pos.y } + nevent := { oevent | button: tui.MouseButton.left, x: app.mouse_pos.x , y: app.mouse_pos.y } app.paint(nevent) } .f2, ._2 { - oevent := *event - nevent := { oevent | button: tui.MouseButton.secondary, x: app.mouse_pos.x , y: app.mouse_pos.y } + oevent := *event + nevent := { oevent | button: tui.MouseButton.right, x: app.mouse_pos.x , y: app.mouse_pos.y } app.paint(nevent) } .space { oevent := *event - nevent := { oevent | button: tui.MouseButton.tertiary, x: app.mouse_pos.x , y: app.mouse_pos.y } + nevent := { oevent | button: tui.MouseButton.middle, x: app.mouse_pos.x , y: app.mouse_pos.y } app.paint(nevent) } .j, .down { @@ -283,8 +287,8 @@ fn (mut app App) set_pixel(x_ int, y_ int, c tui.Color) { fn (mut app App) paint(event &tui.Event) { x_start, y_start := int(f32((event.x - 1) / 2) - app.size / 2 + 1), event.y - app.size / 2 color := match event.button { - .primary { app.primary_color } - .secondary { app.secondary_color } + .left { app.primary_color } + .right { app.secondary_color } else { app.bg_color } } for x in x_start .. x_start + app.size { @@ -365,7 +369,7 @@ fn (mut app App) draw_header() { }) app.tui.draw_text(0, 0, ' $app.msg ') app.tui.reset() - } + } app.tui.draw_text(3, 2, /* 'tick: $app.tui.frame_count | ' + */ 'terminal size: ($app.tui.window_width, $app.tui.window_height) | primary color: $app.primary_color.hex() | secondary color: $app.secondary_color.hex()') app.tui.horizontal_separator(3) } @@ -375,7 +379,7 @@ fn (mut app App) current_color(x int, y int) bool { } fn (mut app App) draw_footer() { - ww, wh := app.tui.window_width, app.tui.window_height + _, wh := app.tui.window_width, app.tui.window_height app.tui.horizontal_separator(wh - 4) for i, color_row in colors { for j, color in color_row { @@ -399,7 +403,7 @@ fn (mut app App) draw_footer() { app.tui.bold() app.tui.draw_text(3, wh - 1, '$select_size $app.size') app.tui.reset() - + // TODO: help button // if ww >= 90 { // app.tui.draw_text(80, wh - 3, help_1) @@ -441,9 +445,9 @@ fn (mut app App) footer_click(event &tui.Event) { idx := footer_y * 19 - 6 + event.x / 3 if idx < 0 || idx > 56 { return } color := colors[idx / 19][idx % 19] - if event.button == .primary { + if event.button == .left { app.primary_color = color - } else { + } else if event.button == .right { app.secondary_color = color } app.show_msg('set $event.button.str().to_lower() color idx: $idx', 1) diff --git a/vlib/term/ui/input.v b/vlib/term/ui/input.v index c33a5f2c01..555d68e150 100644 --- a/vlib/term/ui/input.v +++ b/vlib/term/ui/input.v @@ -123,9 +123,9 @@ pub enum Direction { pub enum MouseButton { unknown - primary - secondary - tertiary + left + middle + right } pub enum EventType { diff --git a/vlib/term/ui/input_nix.c.v b/vlib/term/ui/input_nix.c.v index 11d131e9cf..459e8942d5 100644 --- a/vlib/term/ui/input_nix.c.v +++ b/vlib/term/ui/input_nix.c.v @@ -9,15 +9,6 @@ pub fn init(cfg Config) &Context { cfg: cfg, read_buf: []byte{ cap: cfg.buffer_size } } - ctx.save_title() - - if cfg.hide_cursor { - print('\x1b[?25l') - } - - if cfg.window_title != '' { - print('\x1b]0;$cfg.window_title\x07') - } // lmao unsafe { diff --git a/vlib/term/ui/termios_nix.c.v b/vlib/term/ui/termios_nix.c.v index 427e309ed2..0e3c134aeb 100644 --- a/vlib/term/ui/termios_nix.c.v +++ b/vlib/term/ui/termios_nix.c.v @@ -48,9 +48,12 @@ fn restore_terminal_state() { c.load_title() } println('') -} +} fn (mut ctx Context) termios_setup() { + // store the current title, so restore_terminal_state can get it back + ctx.save_title() + mut termios := get_termios() if ctx.cfg.capture_events { @@ -62,23 +65,31 @@ fn (mut ctx Context) termios_setup() { // Set raw input mode by unsetting ICANON and ECHO termios.c_lflag &= ~u32(C.ICANON | C.ECHO) } + + if ctx.cfg.hide_cursor { + print('\x1b[?25l') + } + + if ctx.cfg.window_title != '' { + print('\x1b]0;$ctx.cfg.window_title\x07') + } + // 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) - print('\x1b[?1003h\x1b[?1015h\x1b[?1006h') + print('\x1b[?1003h\x1b[?1006h') ctx.termios = termios ctx.window_height, ctx.window_width = get_terminal_size() // Reset console on exit - C.atexit(restore_terminal_state) + C.atexit(restore_terminal_state) os.signal(C.SIGTSTP, restore_terminal_state) os.signal(C.SIGCONT, fn () { mut c := ctx_ptr if c != 0 { - c.save_title() c.termios_setup() c.window_height, c.window_width = get_terminal_size() mut event := &Event{ @@ -260,31 +271,43 @@ fn escape_sequence(buf_ string) (&Event, int) { // Mouse events // ---------------- - // TODO: rxvt uses different escape sequences for 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 := u32(0) + if hi & 4 != 0 { modifiers |= shift } + if hi & 8 != 0 { modifiers |= alt } + if hi & 16 != 0 { modifiers |= ctrl } match typ { - 0, 2 { + 0...31 { last := buf[buf.len - 1] - button := if typ == 0 { MouseButton.primary } else { MouseButton.secondary } - event := if last == `M` { EventType.mouse_down } else { EventType.mouse_up } - return &Event{ typ: event, x: x, y: y, button: button, utf8: single }, end + 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, 34 { - button := if typ == 32 { MouseButton.primary } else { MouseButton.secondary } - return &Event{ typ: .mouse_drag, x: x, y: y, button: button, 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 } - 35 { - return &Event{ typ: .mouse_move, x: x, y: y, 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 } - 64, 65 { - direction := if typ == 64 { Direction.down } else { Direction.up } - return &Event{ typ: .mouse_scroll, x: x, y: y, direction: direction, utf8: single }, end - } else {} } } @@ -320,7 +343,7 @@ fn escape_sequence(buf_ string) (&Event, int) { else {} } - if buf.len == 5 && buf[0] == `[` && buf[1] == `1` && buf[2] == `;` { + if buf.len == 5 && buf[0] == `[` && buf[1].is_digit() && buf[2] == `;` { // code = KeyCode(buf[4] + 197) modifiers = match buf[3] { `2` { shift } @@ -333,20 +356,25 @@ fn escape_sequence(buf_ string) (&Event, int) { else { modifiers } // probably unreachable? idk, terminal events are strange } - code = match buf[4] { - `A` { KeyCode.up } - `B` { KeyCode.down } - `C` { KeyCode.right } - `D` { KeyCode.left } - `F` { KeyCode.end } - `H` { KeyCode.home } - `P` { KeyCode.f1 } - `Q` { KeyCode.f2 } - `R` { KeyCode.f3 } - `S` { KeyCode.f4 } - else { code } + if buf[1] == `1` { + code = match buf[4] { + `A` { KeyCode.up } + `B` { KeyCode.down } + `C` { KeyCode.right } + `D` { KeyCode.left } + `F` { KeyCode.end } + `H` { KeyCode.home } + `P` { KeyCode.f1 } + `Q` { KeyCode.f2 } + `R` { KeyCode.f3 } + `S` { KeyCode.f4 } + else { code } + } + } else if buf[1] == `5` { + code = KeyCode.page_up + } else if buf[1] == `6` { + code = KeyCode.page_down } - // && buf[3] >= `2` && buf[3] <= `8` && buf[4] >= `A` && buf[4] <= `D` } return &Event{ typ: .key_down, code: code, utf8: single, modifiers: modifiers }, end diff --git a/vlib/term/ui/ui.v b/vlib/term/ui/ui.v index 0c25a76456..166b69e3f9 100644 --- a/vlib/term/ui/ui.v +++ b/vlib/term/ui/ui.v @@ -1,5 +1,6 @@ module ui +import os import strings pub struct Color { @@ -18,6 +19,7 @@ pub fn (c Color) hex() string { const ( bsu = '\x1bP=1s\x1b\\' esu = '\x1bP=2s\x1b\\' + vno_bsu_esu = os.getenv('VNO_BSU_ESU').len>0 ) [inline] @@ -32,9 +34,13 @@ pub fn (mut ctx Context) flush() { // TODO } $else { // TODO: Diff the previous frame against this one, and only render things that changed? - C.write(C.STDOUT_FILENO, bsu.str, bsu.len) - C.write(C.STDOUT_FILENO, ctx.print_buf.data, ctx.print_buf.len) - C.write(C.STDOUT_FILENO, esu.str, esu.len) + if vno_bsu_esu { + C.write(C.STDOUT_FILENO, ctx.print_buf.data, ctx.print_buf.len) + } else { + C.write(C.STDOUT_FILENO, bsu.str, bsu.len) + C.write(C.STDOUT_FILENO, ctx.print_buf.data, ctx.print_buf.len) + C.write(C.STDOUT_FILENO, esu.str, esu.len) + } ctx.print_buf.clear() } }