term.ui: handle middle mouse button events and modifier keys (#6821)

pull/6819/head
spaceface777 2020-11-14 08:14:54 +01:00 committed by GitHub
parent 5069be04a2
commit 6b47c61fe4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 94 additions and 59 deletions

View File

@ -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('V term.input event viewer (press `esc` to exit)\n\n')
app.tui.write('$e') app.tui.write('$e')
app.tui.write('\n\nRaw event bytes: "${e.utf8.bytes().hex()}" = ${e.utf8.bytes()}') 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() app.tui.flush()
if e.typ == .key_down && e.code == .escape { exit(0) } if e.typ == .key_down && e.code == .escape { exit(0) }
@ -28,5 +34,5 @@ app.tui = tui.init(
frame_rate: 60 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() app.tui.run()

View File

@ -165,6 +165,10 @@ fn event(event &tui.Event, x voidptr) {
} }
} }
.mouse_scroll { .mouse_scroll {
app.mouse_pos = {
x: event.x
y: event.y
}
if event.direction == .down { if event.direction == .down {
app.inc_size() app.inc_size()
} else { } else {
@ -175,17 +179,17 @@ fn event(event &tui.Event, x voidptr) {
match event.code { match event.code {
.f1, ._1 { .f1, ._1 {
oevent := *event 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) app.paint(nevent)
} }
.f2, ._2 { .f2, ._2 {
oevent := *event oevent := *event
nevent := { oevent | button: tui.MouseButton.secondary, x: app.mouse_pos.x , y: app.mouse_pos.y } nevent := { oevent | button: tui.MouseButton.right, x: app.mouse_pos.x , y: app.mouse_pos.y }
app.paint(nevent) app.paint(nevent)
} }
.space { .space {
oevent := *event 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) app.paint(nevent)
} }
.j, .down { .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) { 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 x_start, y_start := int(f32((event.x - 1) / 2) - app.size / 2 + 1), event.y - app.size / 2
color := match event.button { color := match event.button {
.primary { app.primary_color } .left { app.primary_color }
.secondary { app.secondary_color } .right { app.secondary_color }
else { app.bg_color } else { app.bg_color }
} }
for x in x_start .. x_start + app.size { 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.draw_text(0, 0, ' $app.msg ')
app.tui.reset() 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.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) 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() { 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) app.tui.horizontal_separator(wh - 4)
for i, color_row in colors { for i, color_row in colors {
for j, color in color_row { for j, color in color_row {
@ -399,7 +403,7 @@ fn (mut app App) draw_footer() {
app.tui.bold() app.tui.bold()
app.tui.draw_text(3, wh - 1, '$select_size $app.size') app.tui.draw_text(3, wh - 1, '$select_size $app.size')
app.tui.reset() app.tui.reset()
// TODO: help button // TODO: help button
// if ww >= 90 { // if ww >= 90 {
// app.tui.draw_text(80, wh - 3, help_1) // 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 idx := footer_y * 19 - 6 + event.x / 3
if idx < 0 || idx > 56 { return } if idx < 0 || idx > 56 { return }
color := colors[idx / 19][idx % 19] color := colors[idx / 19][idx % 19]
if event.button == .primary { if event.button == .left {
app.primary_color = color app.primary_color = color
} else { } else if event.button == .right {
app.secondary_color = color app.secondary_color = color
} }
app.show_msg('set $event.button.str().to_lower() color idx: $idx', 1) app.show_msg('set $event.button.str().to_lower() color idx: $idx', 1)

View File

@ -123,9 +123,9 @@ pub enum Direction {
pub enum MouseButton { pub enum MouseButton {
unknown unknown
primary left
secondary middle
tertiary right
} }
pub enum EventType { pub enum EventType {

View File

@ -9,15 +9,6 @@ pub fn init(cfg Config) &Context {
cfg: cfg, cfg: cfg,
read_buf: []byte{ cap: cfg.buffer_size } 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 // lmao
unsafe { unsafe {

View File

@ -48,9 +48,12 @@ fn restore_terminal_state() {
c.load_title() c.load_title()
} }
println('') println('')
} }
fn (mut ctx Context) termios_setup() { 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() mut termios := get_termios()
if ctx.cfg.capture_events { if ctx.cfg.capture_events {
@ -62,23 +65,31 @@ fn (mut ctx Context) termios_setup() {
// Set raw input mode by unsetting ICANON and ECHO // Set raw input mode by unsetting ICANON and ECHO
termios.c_lflag &= ~u32(C.ICANON | C.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 // Prevent stdin from blocking by making its read time 0
termios.c_cc[C.VTIME] = 0 termios.c_cc[C.VTIME] = 0
termios.c_cc[C.VMIN] = 0 termios.c_cc[C.VMIN] = 0
C.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH, &termios) C.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH, &termios)
print('\x1b[?1003h\x1b[?1015h\x1b[?1006h') print('\x1b[?1003h\x1b[?1006h')
ctx.termios = termios ctx.termios = termios
ctx.window_height, ctx.window_width = get_terminal_size() ctx.window_height, ctx.window_width = get_terminal_size()
// Reset console on exit // Reset console on exit
C.atexit(restore_terminal_state) C.atexit(restore_terminal_state)
os.signal(C.SIGTSTP, restore_terminal_state) os.signal(C.SIGTSTP, restore_terminal_state)
os.signal(C.SIGCONT, fn () { os.signal(C.SIGCONT, fn () {
mut c := ctx_ptr mut c := ctx_ptr
if c != 0 { if c != 0 {
c.save_title()
c.termios_setup() c.termios_setup()
c.window_height, c.window_width = get_terminal_size() c.window_height, c.window_width = get_terminal_size()
mut event := &Event{ mut event := &Event{
@ -260,31 +271,43 @@ fn escape_sequence(buf_ string) (&Event, int) {
// Mouse events // 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] == `<` { if buf.len > 2 && buf[1] == `<` {
split := buf[2..].split(';') split := buf[2..].split(';')
if split.len < 3 { return &Event(0), 0 } if split.len < 3 { return &Event(0), 0 }
typ, x, y := split[0].int(), split[1].int(), split[2].int() 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 { match typ {
0, 2 { 0...31 {
last := buf[buf.len - 1] last := buf[buf.len - 1]
button := if typ == 0 { MouseButton.primary } else { MouseButton.secondary } button := if lo < 3 { MouseButton(lo + 1) } else { MouseButton.unknown }
event := if last == `M` { EventType.mouse_down } else { EventType.mouse_up } event := if last == `m` || lo == 3 { EventType.mouse_up } else { EventType.mouse_down }
return &Event{ typ: event, x: x, y: y, button: button, utf8: single }, end
return &Event{ typ: event, x: x, y: y, button: button, modifiers: modifiers utf8: single }, end
} }
32, 34 { 32...63 {
button := if typ == 32 { MouseButton.primary } else { MouseButton.secondary } button, event := if lo < 3 {
return &Event{ typ: .mouse_drag, x: x, y: y, button: button, utf8: single }, end 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 { 64...95 {
return &Event{ typ: .mouse_move, x: x, y: y, utf8: single }, end 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 {} 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) // code = KeyCode(buf[4] + 197)
modifiers = match buf[3] { modifiers = match buf[3] {
`2` { shift } `2` { shift }
@ -333,20 +356,25 @@ fn escape_sequence(buf_ string) (&Event, int) {
else { modifiers } // probably unreachable? idk, terminal events are strange else { modifiers } // probably unreachable? idk, terminal events are strange
} }
code = match buf[4] { if buf[1] == `1` {
`A` { KeyCode.up } code = match buf[4] {
`B` { KeyCode.down } `A` { KeyCode.up }
`C` { KeyCode.right } `B` { KeyCode.down }
`D` { KeyCode.left } `C` { KeyCode.right }
`F` { KeyCode.end } `D` { KeyCode.left }
`H` { KeyCode.home } `F` { KeyCode.end }
`P` { KeyCode.f1 } `H` { KeyCode.home }
`Q` { KeyCode.f2 } `P` { KeyCode.f1 }
`R` { KeyCode.f3 } `Q` { KeyCode.f2 }
`S` { KeyCode.f4 } `R` { KeyCode.f3 }
else { code } `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 return &Event{ typ: .key_down, code: code, utf8: single, modifiers: modifiers }, end

View File

@ -1,5 +1,6 @@
module ui module ui
import os
import strings import strings
pub struct Color { pub struct Color {
@ -18,6 +19,7 @@ pub fn (c Color) hex() string {
const ( const (
bsu = '\x1bP=1s\x1b\\' bsu = '\x1bP=1s\x1b\\'
esu = '\x1bP=2s\x1b\\' esu = '\x1bP=2s\x1b\\'
vno_bsu_esu = os.getenv('VNO_BSU_ESU').len>0
) )
[inline] [inline]
@ -32,9 +34,13 @@ pub fn (mut ctx Context) flush() {
// TODO // TODO
} $else { } $else {
// TODO: Diff the previous frame against this one, and only render things that changed? // TODO: Diff the previous frame against this one, and only render things that changed?
C.write(C.STDOUT_FILENO, bsu.str, bsu.len) if vno_bsu_esu {
C.write(C.STDOUT_FILENO, ctx.print_buf.data, ctx.print_buf.len) C.write(C.STDOUT_FILENO, ctx.print_buf.data, ctx.print_buf.len)
C.write(C.STDOUT_FILENO, esu.str, esu.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() ctx.print_buf.clear()
} }
} }