v/examples/term.ui/term_drawing.v

460 lines
11 KiB
V
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

module main
import term.ui as tui
// The color palette, taken from Google's Material design
const (
colors = [
[
tui.Color{239, 154, 154},
tui.Color{244, 143, 177},
tui.Color{206, 147, 216},
tui.Color{179, 157, 219},
tui.Color{159, 168, 218},
tui.Color{144, 202, 249},
tui.Color{129, 212, 250},
tui.Color{128, 222, 234},
tui.Color{128, 203, 196},
tui.Color{165, 214, 167},
tui.Color{197, 225, 165},
tui.Color{230, 238, 156},
tui.Color{255, 245, 157},
tui.Color{255, 224, 130},
tui.Color{255, 204, 128},
tui.Color{255, 171, 145},
tui.Color{188, 170, 164},
tui.Color{238, 238, 238},
tui.Color{176, 190, 197},
],
[
tui.Color{244, 67, 54},
tui.Color{233, 30, 99},
tui.Color{156, 39, 176},
tui.Color{103, 58, 183},
tui.Color{63, 81, 181},
tui.Color{33, 150, 243},
tui.Color{3, 169, 244},
tui.Color{0, 188, 212},
tui.Color{0, 150, 136},
tui.Color{76, 175, 80},
tui.Color{139, 195, 74},
tui.Color{205, 220, 57},
tui.Color{255, 235, 59},
tui.Color{255, 193, 7},
tui.Color{255, 152, 0},
tui.Color{255, 87, 34},
tui.Color{121, 85, 72},
tui.Color{120, 120, 120},
tui.Color{96, 125, 139},
],
[
tui.Color{198, 40, 40},
tui.Color{173, 20, 87},
tui.Color{106, 27, 154},
tui.Color{69, 39, 160},
tui.Color{40, 53, 147},
tui.Color{21, 101, 192},
tui.Color{2, 119, 189},
tui.Color{0, 131, 143},
tui.Color{0, 105, 92},
tui.Color{46, 125, 50},
tui.Color{85, 139, 47},
tui.Color{158, 157, 36},
tui.Color{249, 168, 37},
tui.Color{255, 143, 0},
tui.Color{239, 108, 0},
tui.Color{216, 67, 21},
tui.Color{78, 52, 46},
tui.Color{33, 33, 33},
tui.Color{55, 71, 79},
],
]
)
const (
frame_rate = 30 // fps
msg_display_time = 5 * frame_rate
w = 200
h = 100
space = ' '
spaces = ' '
select_color = 'Select color: '
select_size = 'Size: '
help_1 = ''
help_2 = ' HELP '
help_3 = ''
)
struct App {
mut:
tui &tui.Context = 0
header_text []string
mouse_pos Point
msg string
msg_hide_tick int
primary_color tui.Color = colors[1][6]
secondary_color tui.Color = colors[1][9]
bg_color tui.Color = tui.Color{0, 0, 0}
color_index int
drawing [][]tui.Color = [][]tui.Color{len: h, init: []tui.Color{len: w}}
size int = 1
should_redraw bool = true
is_dragging bool
}
struct Point {
mut:
x int
y int
}
fn main() {
mut app := &App{}
app.tui = tui.init({
user_data: app
frame_fn: frame
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
app.tui.clear()
app.tui.run()
}
fn frame(x voidptr) {
mut app := &App(x)
mut redraw := app.should_redraw
if app.msg != '' && app.tui.frame_count >= app.msg_hide_tick {
app.msg = ''
redraw = true
}
if redraw {
app.render(false)
app.should_redraw = false
}
}
fn event(event &tui.Event, x voidptr) {
mut app := &App(x)
match event.typ {
.mouse_down {
app.is_dragging = true
if app.tui.window_height - event.y < 5 {
app.footer_click(event)
} else {
app.paint(event)
}
}
.mouse_up {
app.is_dragging = false
}
.mouse_drag {
app.mouse_pos = {
x: event.x
y: event.y
}
app.paint(event)
}
.mouse_move {
app.mouse_pos = {
x: event.x
y: event.y
}
}
.mouse_scroll {
if event.direction == .down {
app.inc_size()
} else {
app.dec_size()
}
}
.key_down {
match event.code {
.f1, ._1 {
oevent := *event
nevent := { oevent | button: tui.MouseButton.primary, 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 }
app.paint(nevent)
}
.space {
oevent := *event
nevent := { oevent | button: tui.MouseButton.tertiary, x: app.mouse_pos.x , y: app.mouse_pos.y }
app.paint(nevent)
}
.j, .down {
if event.modifiers & tui.shift != 0 {
app.set_pixel((1 + app.mouse_pos.x) / 2, app.mouse_pos.y, app.primary_color)
}
app.mouse_pos.y++
}
.k, .up {
if event.modifiers & tui.shift != 0 {
app.set_pixel((1 + app.mouse_pos.x) / 2, app.mouse_pos.y, app.primary_color)
}
app.mouse_pos.y--
}
.h, .left {
if event.modifiers & tui.shift != 0 {
app.set_pixel((1 + app.mouse_pos.x) / 2, app.mouse_pos.y, app.primary_color)
}
app.mouse_pos.x -= 2
}
.l, .right {
if event.modifiers & tui.shift != 0 {
app.set_pixel((1 + app.mouse_pos.x) / 2, app.mouse_pos.y, app.primary_color)
}
app.mouse_pos.x += 2
}
.t {
mut c := if event.modifiers & tui.shift != 0 { app.color_index - 19 } else { app.color_index +
19 }
if c < 0 {
c = 0
}
cx := c % 19
cy := (c / 19) % 3
color := colors[cy][cx]
app.primary_color = color
app.color_index = c % (19 * 3)
}
.r {
mut c := if event.modifiers & tui.shift != 0 { app.color_index - 1 } else { app.color_index +
1 }
if c < 0 {
c = 0
}
cx := c % 19
cy := (c / 19) % 3
color := colors[cy][cx]
app.primary_color = color
app.color_index = c % (19 * 3)
}
.plus {
app.inc_size()
}
.minus {
app.dec_size()
}
.c {
app.drawing = [][]tui.Color{len: h, init: []tui.Color{len: w}}
}
.q, .escape {
app.render(true)
exit(0)
}
else {}
}
}
else {}
}
app.should_redraw = true
}
fn (mut app App) render(paint_only bool) {
app.tui.clear()
app.draw_header()
app.draw_content()
if !paint_only {
app.draw_footer()
app.draw_cursor()
}
app.tui.flush()
}
fn (mut app App) set_pixel(x_ int, y_ int, c tui.Color) {
// Term coords start at 1, and adjust for the header
x, y := x_ - 1, y_ - 4
if y < 0 || app.tui.window_height - y < 3 {
return
}
if y >= app.drawing.len || x < 0 || x >= app.drawing[0].len {
return
}
app.drawing[y][x] = c
}
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 }
else { app.bg_color }
}
for x in x_start .. x_start + app.size {
for y in y_start .. y_start + app.size {
app.set_pixel(x, y, color)
}
}
}
fn (mut app App) draw_content() {
w, mut h := app.tui.window_width / 2, app.tui.window_height - 8
if h > app.drawing.len {
h = app.drawing.len
}
for row_idx, row in app.drawing[..h] {
app.tui.set_cursor_position(0, row_idx + 4)
mut last := tui.Color{0, 0, 0}
for cell in row[..w] {
if cell.r == 0 && cell.g == 0 && cell.b == 0 {
if !(cell.r == last.r && cell.g == last.g && cell.b == last.b) {
app.tui.reset()
}
} else {
if !(cell.r == last.r && cell.g == last.g && cell.b == last.b) {
app.tui.set_bg_color(cell)
}
}
app.tui.write(spaces)
last = cell
}
app.tui.reset()
}
}
fn (mut app App) draw_cursor() {
if app.mouse_pos.y in [3, app.tui.window_height - 5] {
// inside the horizontal separators
return
}
cursor_color := if app.is_dragging { tui.Color{220, 220, 220} } else { tui.Color{160, 160, 160} }
app.tui.set_bg_color(cursor_color)
if app.mouse_pos.y >= 3 && app.mouse_pos.y <= app.tui.window_height - 4 {
// inside the main content
mut x_start := int(f32((app.mouse_pos.x - 1) / 2) - app.size / 2 + 1) * 2 - 1
mut y_start := app.mouse_pos.y - app.size / 2
mut x_end := x_start + app.size * 2 - 1
mut y_end := y_start + app.size - 1
if x_start < 1 {
x_start = 1
}
if y_start < 4 {
y_start = 4
}
if x_end > app.tui.window_width {
x_end = app.tui.window_width
}
if y_end > app.tui.window_height - 5 {
y_end = app.tui.window_height - 5
}
app.tui.draw_rect(x_start, y_start, x_end, y_end)
} else {
app.tui.draw_text(app.mouse_pos.x, app.mouse_pos.y, space)
}
app.tui.reset()
}
fn (mut app App) draw_header() {
if app.msg != '' {
app.tui.set_color({
r: 0
g: 0
b: 0
})
app.tui.set_bg_color({
r: 220
g: 220
b: 220
})
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)
}
fn (mut app App) current_color(x int, y int) bool {
return app.color_index == x + (y * 19)
}
fn (mut app App) draw_footer() {
ww, 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 {
x := j * 3 + 19
y := wh - 3 + i
app.tui.set_bg_color(color)
if app.current_color(j, i) {
app.tui.set_color(tui.Color{0, 0, 0})
app.tui.draw_text(x, y, '><')
app.tui.reset_color()
} else {
app.tui.draw_rect(x, y, x + 1, y)
}
}
}
app.tui.reset_bg_color()
app.tui.draw_text(3, wh - 3, select_color)
app.tui.set_bg_color(app.primary_color)
app.tui.draw_text(3 + select_color.len, wh - 3, ' ')
app.tui.reset_bg_color()
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)
// app.tui.draw_text(80, wh - 2, help_2)
// app.tui.draw_text(80, wh - 1, help_3)
// }
}
[inline]
fn (mut app App) inc_size() {
if app.size < 20 {
app.size++
}
app.show_msg('inc. size: $app.size', 1)
}
[inline]
fn (mut app App) dec_size() {
if app.size > 1 {
app.size--
}
app.show_msg('dec. size: $app.size', 1)
}
fn (mut app App) footer_click(event &tui.Event) {
footer_y := 3 - (app.tui.window_height - event.y)
match event.x {
8...11 {
app.inc_size()
}
12...15 {
app.dec_size()
}
18...75 {
if (event.x % 3) == 0 {
// Inside the gap between tiles
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
} else {
app.secondary_color = color
}
app.show_msg('set $event.button.str().to_lower() color idx: $idx', 1)
}
else {}
}
}
fn (mut app App) show_msg(text string, time int) {
frames := time * frame_rate
app.msg_hide_tick = if time > 0 { int(app.tui.frame_count) + frames } else { -1 }
app.msg = text
}