v/examples/term.ui/term_drawing.v

511 lines
12 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.

// Copyright (c) 2020 Raúl Hernández. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module main
import term.ui
// The color palette, taken from Google's Material design
const (
colors = [
[
ui.Color{239, 154, 154},
ui.Color{244, 143, 177},
ui.Color{206, 147, 216},
ui.Color{179, 157, 219},
ui.Color{159, 168, 218},
ui.Color{144, 202, 249},
ui.Color{129, 212, 250},
ui.Color{128, 222, 234},
ui.Color{128, 203, 196},
ui.Color{165, 214, 167},
ui.Color{197, 225, 165},
ui.Color{230, 238, 156},
ui.Color{255, 245, 157},
ui.Color{255, 224, 130},
ui.Color{255, 204, 128},
ui.Color{255, 171, 145},
ui.Color{188, 170, 164},
ui.Color{238, 238, 238},
ui.Color{176, 190, 197},
],
[
ui.Color{244, 67, 54},
ui.Color{233, 30, 99},
ui.Color{156, 39, 176},
ui.Color{103, 58, 183},
ui.Color{63, 81, 181},
ui.Color{33, 150, 243},
ui.Color{3, 169, 244},
ui.Color{0, 188, 212},
ui.Color{0, 150, 136},
ui.Color{76, 175, 80},
ui.Color{139, 195, 74},
ui.Color{205, 220, 57},
ui.Color{255, 235, 59},
ui.Color{255, 193, 7},
ui.Color{255, 152, 0},
ui.Color{255, 87, 34},
ui.Color{121, 85, 72},
ui.Color{120, 120, 120},
ui.Color{96, 125, 139},
],
[
ui.Color{198, 40, 40},
ui.Color{173, 20, 87},
ui.Color{106, 27, 154},
ui.Color{69, 39, 160},
ui.Color{40, 53, 147},
ui.Color{21, 101, 192},
ui.Color{2, 119, 189},
ui.Color{0, 131, 143},
ui.Color{0, 105, 92},
ui.Color{46, 125, 50},
ui.Color{85, 139, 47},
ui.Color{158, 157, 36},
ui.Color{249, 168, 37},
ui.Color{255, 143, 0},
ui.Color{239, 108, 0},
ui.Color{216, 67, 21},
ui.Color{78, 52, 46},
ui.Color{33, 33, 33},
ui.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:
ui &ui.Context = 0
header_text []string
mouse_pos Point
msg string
msg_hide_tick int
primary_color ui.Color = colors[1][6]
secondary_color ui.Color = colors[1][9]
primary_color_idx int = 25
secondary_color_idx int = 28
bg_color ui.Color = ui.Color{0, 0, 0}
drawing [][]ui.Color = [][]ui.Color{len: h, init: []ui.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.ui = ui.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.ui.clear()
app.ui.run() ?
}
fn frame(x voidptr) {
mut app := &App(x)
mut redraw := app.should_redraw
if app.msg != '' && app.ui.frame_count >= app.msg_hide_tick {
app.msg = ''
redraw = true
}
if redraw {
app.render(false)
app.should_redraw = false
}
}
fn event(event &ui.Event, x voidptr) {
mut app := &App(x)
match event.typ {
.mouse_down {
app.is_dragging = true
if app.ui.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 {
app.mouse_pos = {
x: event.x
y: event.y
}
d := event.direction == .down
if event.modifiers.has(.ctrl) {
p := !event.modifiers.has(.shift)
c := if d {
if p { app.primary_color_idx - 1 } else { app.secondary_color_idx - 1 }
} else {
if p { app.primary_color_idx + 1 } else { app.secondary_color_idx + 1 }
}
app.select_color(p, c)
} else {
if d {
app.inc_size()
} else {
app.dec_size()
}
}
}
.key_down {
match event.code {
.f1, ._1 {
oevent := *event
nevent := ui.Event{
...oevent
button: ui.MouseButton.left
x: app.mouse_pos.x
y: app.mouse_pos.y
}
app.paint(nevent)
}
.f2, ._2 {
oevent := *event
nevent := ui.Event{
...oevent
button: ui.MouseButton.right
x: app.mouse_pos.x
y: app.mouse_pos.y
}
app.paint(nevent)
}
.space, .enter {
oevent := *event
nevent := ui.Event{
...oevent
button: .left
x: app.mouse_pos.x
y: app.mouse_pos.y
}
app.paint(nevent)
}
.delete, .backspace {
oevent := *event
nevent := ui.Event{
...oevent
button: .middle
x: app.mouse_pos.x
y: app.mouse_pos.y
}
app.paint(nevent)
}
.j, .down {
if event.modifiers.has(.shift) {
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.has(.shift) {
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.has(.shift) {
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.has(.shift) {
app.set_pixel((1 + app.mouse_pos.x) / 2, app.mouse_pos.y, app.primary_color)
}
app.mouse_pos.x += 2
}
.t {
p := !event.modifiers.has(.alt)
c := if event.modifiers.has(.shift) {
if p { app.primary_color_idx - 19 } else { app.secondary_color_idx - 19 }
} else {
if p { app.primary_color_idx + 19 } else { app.secondary_color_idx + 19 }
}
app.select_color(p, c)
}
.r {
p := !event.modifiers.has(.alt)
c := if event.modifiers.has(.shift) {
if p { app.primary_color_idx - 1 } else { app.secondary_color_idx - 1 }
} else {
if p { app.primary_color_idx + 1 } else { app.secondary_color_idx + 1 }
}
app.select_color(p, c)
}
.plus {
app.inc_size()
}
.minus {
app.dec_size()
}
.c {
app.drawing = [][]ui.Color{len: h, init: []ui.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.ui.clear()
app.draw_header()
app.draw_content()
if !paint_only {
app.draw_footer()
app.draw_cursor()
}
app.ui.flush()
}
fn (mut app App) select_color(primary bool, idx int) {
c := (idx + 57) % 57
cx := c % 19
cy := (c / 19) % 3
color := colors[cy][cx]
if primary {
app.primary_color_idx = c % (19 * 3)
app.primary_color = color
} else {
app.secondary_color_idx = c % (19 * 3)
app.secondary_color = color
}
c_str := if primary { 'primary' } else { 'secondary' }
app.show_msg('set $c_str color idx: $idx', 1)
}
fn (mut app App) set_pixel(x_ int, y_ int, c ui.Color) {
// Term coords start at 1, and adjust for the header
x, y := x_ - 1, y_ - 4
if y < 0 || app.ui.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 &ui.Event) {
if event.y < 4 || app.ui.window_height - event.y < 4 {
return
}
x_start, y_start := int(f32((event.x - 1) / 2) - app.size / 2 + 1), event.y - app.size / 2
color := match event.button {
.left { app.primary_color }
.right { 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.ui.window_width / 2, app.ui.window_height - 8
if h_ > app.drawing.len {
h_ = app.drawing.len
}
for row_idx, row in app.drawing[..h_] {
app.ui.set_cursor_position(0, row_idx + 4)
mut last := ui.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.ui.reset()
}
} else {
if !(cell.r == last.r && cell.g == last.g && cell.b == last.b) {
app.ui.set_bg_color(cell)
}
}
app.ui.write(spaces)
last = cell
}
app.ui.reset()
}
}
fn (mut app App) draw_cursor() {
if app.mouse_pos.y in [3, app.ui.window_height - 5] {
// inside the horizontal separators
return
}
cursor_color := if app.is_dragging { ui.Color{220, 220, 220} } else { ui.Color{160, 160, 160} }
app.ui.set_bg_color(cursor_color)
if app.mouse_pos.y >= 3 && app.mouse_pos.y <= app.ui.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.ui.window_width {
x_end = app.ui.window_width
}
if y_end > app.ui.window_height - 5 {
y_end = app.ui.window_height - 5
}
app.ui.draw_rect(x_start, y_start, x_end, y_end)
} else {
app.ui.draw_text(app.mouse_pos.x, app.mouse_pos.y, space)
}
app.ui.reset()
}
fn (mut app App) draw_header() {
if app.msg != '' {
app.ui.set_color(
r: 0
g: 0
b: 0
)
app.ui.set_bg_color(
r: 220
g: 220
b: 220
)
app.ui.draw_text(0, 0, ' $app.msg ')
app.ui.reset()
}
//'tick: $app.ui.frame_count | ' +
app.ui.draw_text(3, 2, 'terminal size: ($app.ui.window_width, $app.ui.window_height) | primary color: $app.primary_color.hex() | secondary color: $app.secondary_color.hex()')
app.ui.horizontal_separator(3)
}
fn (mut app App) draw_footer() {
_, wh := app.ui.window_width, app.ui.window_height
app.ui.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.ui.set_bg_color(color)
if app.primary_color_idx == j + (i * 19) {
app.ui.set_color(r: 0, g: 0, b: 0)
app.ui.draw_text(x, y, '><')
app.ui.reset_color()
} else if app.secondary_color_idx == j + (i * 19) {
app.ui.set_color(r: 255, g: 255, b: 255)
app.ui.draw_text(x, y, '><')
app.ui.reset_color()
} else {
app.ui.draw_rect(x, y, x + 1, y)
}
}
}
app.ui.reset_bg_color()
app.ui.draw_text(3, wh - 3, select_color)
app.ui.bold()
app.ui.draw_text(3, wh - 1, '$select_size $app.size')
app.ui.reset()
// TODO: help button
// if ww >= 90 {
// app.ui.draw_text(80, wh - 3, help_1)
// app.ui.draw_text(80, wh - 2, help_2)
// app.ui.draw_text(80, wh - 1, help_3)
// }
}
[inline]
fn (mut app App) inc_size() {
if app.size < 30 {
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 &ui.Event) {
footer_y := 3 - (app.ui.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
}
app.select_color(event.button == .left, idx)
}
else {}
}
}
fn (mut app App) show_msg(text string, time int) {
frames := time * frame_rate
app.msg_hide_tick = if time > 0 { int(app.ui.frame_count) + frames } else { -1 }
app.msg = text
}