term.ui: allow setting the terminal title (#6809)
parent
7feb1742d3
commit
c315218ed1
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue