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

View File

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

View File

@ -5,7 +5,39 @@ A V module for designing terminal UI apps
#### Quickstart
```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.
@ -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.
- `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.

View File

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

View File

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

View File

@ -1,10 +1,22 @@
module ui
const (
not_implemented_yet = "term.input: error: Windows support isn't implemented yet"
)
pub fn init(cfg Config) &Context {
panic("term.input: error: Windows support isn't implemented yet")
panic(not_implemented_yet)
return &Context{}
}
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
}
fn restore_terminal_state() {
termios_reset()
mut c := ctx_ptr
if c != 0 {
c.load_title()
}
println('')
}
fn (mut ctx Context) termios_setup() {
mut termios := get_termios()
@ -64,11 +73,12 @@ fn (mut ctx Context) termios_setup() {
ctx.window_height, ctx.window_width = get_terminal_size()
// Reset console on exit
C.atexit(termios_reset)
os.signal(C.SIGTSTP, termios_reset)
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{
@ -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
fn (mut ctx Context) termios_loop() {
frame_time := 1_000_000 / ctx.cfg.frame_rate
@ -252,7 +212,7 @@ fn escape_end(buf string) int {
for {
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 {
n := buf[i+1]
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
}
// 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
@ -299,7 +260,8 @@ fn escape_sequence(buf_ string) (&Event, int) {
// 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(';')
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')
}
// pub const (
// default_color = gx.rgb(183, 101, 94) // hopefully nobody actually tries to use this color...
// )
// pub struct DrawConfig {
// pub mut:
// fg_color gx.Color = default_color
// bg_color gx.Color = default_color
// }
[inline]
pub fn (mut ctx Context) set_window_title(s string) {
print('\x1b]0;$s\x07')
}
[inline]
pub fn (mut ctx Context) draw_point(x int, y int) {