term.ui: allow setting the terminal title (#6809)

pull/6821/head
spaceface777 2020-11-13 14:30:47 +01:00 committed by GitHub
parent 7feb1742d3
commit c315218ed1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 103 additions and 83 deletions

View File

@ -6,15 +6,13 @@ mut:
} }
fn event(e &tui.Event, x voidptr) { fn event(e &tui.Event, x voidptr) {
print('\x1b[0;0H\x1b[2J\x1b[3J') // Clear everything mut app := &App(x)
println('V term.input event viewer (press `esc` to exit)\n\n') app.tui.clear()
app.tui.set_cursor_position(0, 0)
println(e) app.tui.write('V term.input event viewer (press `esc` to exit)\n\n')
println('Raw event bytes: "${e.utf8.bytes().hex()}" = ${e.utf8.bytes()}') app.tui.write('$e')
app.tui.write('\n\nRaw event bytes: "${e.utf8.bytes().hex()}" = ${e.utf8.bytes()}')
if e.modifiers == tui.ctrl | tui.alt { app.tui.flush()
println('CTRL + ALT')
}
if e.typ == .key_down && e.code == .escape { exit(0) } if e.typ == .key_down && e.code == .escape { exit(0) }
} }
@ -24,6 +22,7 @@ app.tui = tui.init(
user_data: app, user_data: app,
event_fn: event event_fn: event
window_title: 'V term.ui event viewer'
hide_cursor: true hide_cursor: true
capture_events: true capture_events: true
frame_rate: 60 frame_rate: 60

View File

@ -116,6 +116,7 @@ fn main() {
event_fn: event event_fn: event
frame_rate: frame_rate frame_rate: frame_rate
hide_cursor: true hide_cursor: true
window_title: 'V terminal pixelart drawing app'
}) })
app.mouse_pos.x = 40 app.mouse_pos.x = 40
app.mouse_pos.y = 15 app.mouse_pos.y = 15
@ -398,11 +399,13 @@ 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()
if ww >= 90 {
app.tui.draw_text(80, wh - 3, help_1) // TODO: help button
app.tui.draw_text(80, wh - 2, help_2) // if ww >= 90 {
app.tui.draw_text(80, wh - 1, help_3) // app.tui.draw_text(80, wh - 3, help_1)
} // app.tui.draw_text(80, wh - 2, help_2)
// app.tui.draw_text(80, wh - 1, help_3)
// }
} }
[inline] [inline]
@ -436,6 +439,7 @@ fn (mut app App) footer_click(event &tui.Event) {
return return
} }
idx := footer_y * 19 - 6 + event.x / 3 idx := footer_y * 19 - 6 + event.x / 3
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 == .primary {
app.primary_color = color app.primary_color = color

View File

@ -5,7 +5,39 @@ A V module for designing terminal UI apps
#### Quickstart #### Quickstart
```v ```v
// todo 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)
}
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. See the `/examples/term.ui/` folder for more usage examples.
@ -22,6 +54,7 @@ See the `/examples/term.ui/` folder for more usage examples.
- `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. - `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. - `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. - `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. - `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. 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.

View File

@ -187,6 +187,7 @@ pub struct Config {
frame_rate int = 30 frame_rate int = 30
use_x11 bool use_x11 bool
window_title string
hide_cursor bool hide_cursor bool
capture_events bool capture_events bool
// All kill signals // All kill signals

View File

@ -9,12 +9,17 @@ 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 { if cfg.hide_cursor {
s := '\x1b[?25l' print('\x1b[?25l')
C.write(C.STDOUT_FILENO, s.str, s.len)
} }
if cfg.window_title != '' {
print('\x1b]0;$cfg.window_title\x07')
}
// lmao
unsafe { unsafe {
x := &ctx_ptr x := &ctx_ptr
*x = ctx *x = ctx
@ -23,6 +28,15 @@ pub fn init(cfg Config) &Context {
return ctx return ctx
} }
pub fn (mut ctx Context) save_title() {
// restore the previously saved terminal title
print('\x1b[22;0t')
}
pub fn (mut ctx Context) load_title() {
// restore the previously saved terminal title
print('\x1b[23;0t')
}
pub fn (mut ctx Context) run() { pub fn (mut ctx Context) run() {
if ctx.cfg.use_x11 { if ctx.cfg.use_x11 {
ctx.fail('error: x11 backend not implemented yet') ctx.fail('error: x11 backend not implemented yet')

View File

@ -1,10 +1,22 @@
module ui module ui
const (
not_implemented_yet = "term.input: error: Windows support isn't implemented yet"
)
pub fn init(cfg Config) &Context { pub fn init(cfg Config) &Context {
panic("term.input: error: Windows support isn't implemented yet") panic(not_implemented_yet)
return &Context{} return &Context{}
} }
pub fn (mut ctx Context) run() { pub fn (mut ctx Context) run() {
panic("term.input: error: Windows support isn't implemented yet") panic(not_implemented_yet)
}
pub fn (mut ctx Context) save_title() {
panic(not_implemented_yet)
}
pub fn (mut ctx Context) load_title() {
panic(not_implemented_yet)
} }

View File

@ -41,6 +41,15 @@ fn get_terminal_size() (u16, u16) {
return winsz.ws_row, winsz.ws_col return winsz.ws_row, winsz.ws_col
} }
fn restore_terminal_state() {
termios_reset()
mut c := ctx_ptr
if c != 0 {
c.load_title()
}
println('')
}
fn (mut ctx Context) termios_setup() { fn (mut ctx Context) termios_setup() {
mut termios := get_termios() mut termios := get_termios()
@ -64,11 +73,12 @@ fn (mut ctx Context) termios_setup() {
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(termios_reset) C.atexit(restore_terminal_state)
os.signal(C.SIGTSTP, termios_reset) 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{
@ -111,56 +121,6 @@ fn termios_reset() {
/////////////////////////////////////////// ///////////////////////////////////////////
/*
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 last_frame_time := 0
mut sleep_len := 0
for {
sw.restart()
if !init_called {
ctx.init()
init_called = true
}
for _ in 0 .. 7 {
// println('SLEEPING: $sleep_len')
if sleep_len > 0 {
time.usleep(sleep_len)
}
if ctx.cfg.event_fn != voidptr(0) {
len := C.read(C.STDIN_FILENO, ctx.read_buf.data, ctx.read_buf.cap - ctx.read_buf.len)
if len > 0 {
ctx.resize_arr(len)
ctx.parse_events()
}
}
}
ctx.frame()
sw.pause()
last_frame_time = int(sw.elapsed().microseconds())
if
println('Sleeping for $frame_time - $last_frame_time = ${frame_time - last_frame_time}')
// time.usleep(frame_time - last_frame_time - sleep_len * 7)
last_frame_time = 0
sw.start()
sw.pause()
last_frame_time += int(sw.elapsed().microseconds())
sleep_len = (frame_time - last_frame_time) / 8
ctx.frame_count++
}
}
*/
// TODO: do multiple sleep/read cycles, rather than one big one // TODO: do multiple sleep/read cycles, rather than one big one
fn (mut ctx Context) termios_loop() { fn (mut ctx Context) termios_loop() {
frame_time := 1_000_000 / ctx.cfg.frame_rate frame_time := 1_000_000 / ctx.cfg.frame_rate
@ -252,7 +212,7 @@ fn escape_end(buf string) int {
for { for {
if i + 1 == buf.len { return buf.len } if i + 1 == buf.len { return buf.len }
if buf[i].is_letter() { if buf[i].is_letter() || buf[i] == `~` {
if buf[i] == `O` && i + 2 <= buf.len { if buf[i] == `O` && i + 2 <= buf.len {
n := buf[i+1] n := buf[i+1]
if (n >= `A` && n <= `D`) || (n >= `P` && n <= `S`) || n == `F` || n == `H` { if (n >= `A` && n <= `D`) || (n >= `P` && n <= `S`) || n == `F` || n == `H` {
@ -260,7 +220,8 @@ fn escape_end(buf string) int {
} }
} }
return i + 1 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++ i++
} }
// this point should be unreachable // this point should be unreachable
@ -299,7 +260,8 @@ fn escape_sequence(buf_ string) (&Event, int) {
// Mouse events // Mouse events
// ---------------- // ----------------
if buf.len > 2 && buf[1] == `<` { // Mouse control // TODO: rxvt uses different escape sequences for mouse events :/
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 }

View File

@ -79,15 +79,10 @@ pub fn (mut ctx Context) clear() {
ctx.write('\x1b[2J\x1b[3J') ctx.write('\x1b[2J\x1b[3J')
} }
// pub const ( [inline]
// default_color = gx.rgb(183, 101, 94) // hopefully nobody actually tries to use this color... pub fn (mut ctx Context) set_window_title(s string) {
// ) print('\x1b]0;$s\x07')
}
// pub struct DrawConfig {
// pub mut:
// fg_color gx.Color = default_color
// bg_color gx.Color = default_color
// }
[inline] [inline]
pub fn (mut ctx Context) draw_point(x int, y int) { pub fn (mut ctx Context) draw_point(x int, y int) {