package main
// import "core:c"
import "core:math"
import "core:math/linalg"
import "core:mem"
import "core:os"
import "core:fmt"
import mu "vendor:microui"
import stbtt "vendor:stb/truetype"
DEBUG_DRAW_UI_CALLS :: #config(DEBUG_DRAW_UI_CALLS, false)
color_u32 :: proc {
color_u8_to_u32,
color_mu_color_to_u32,
}
color_u8_to_u32 :: #force_inline proc(r, g, b, a: u8) -> u32 {
return (u32(a) << 24) + (u32(r) << 16) + (u32(g) << 8) + u32(b)
}
color_mu_color_to_u32 :: #force_inline proc(c: mu.Color) -> u32 {
return (u32(c.a) << 24) + (u32(c.r) << 16) + (u32(c.g) << 8) + u32(c.b)
}
round_i32 :: #force_inline proc(v: f32) -> i32 {
return i32(v + 0.5)
}
abs :: proc(a: i32) -> i32 {
v := a >> 31
return (a + v) ~ v
}
clamp :: proc(a, min, max: i32) -> i32 {
t := (a < min ? min : a)
return t > max ? max : t
}
// h: 0-360, s: 0-1, v: 0-1
rgb_to_hsv :: proc(h: u32, s: f32, v: f32) -> u32 {
c := s * v
x := c * (1 - linalg.abs(math.mod(f32(h) / 60, 2) - 1))
m := v - c
r1, g1, b1: f32
if h >= 0 && h < 60 {
r1, g1, b1 = c, x, 0
} else if h >= 60 && h < 120 {
r1, g1, b1 = x, c, 0
} else if h >= 120 && h < 180 {
r1, g1, b1 = 0, c, x
} else if h >= 180 && h < 240 {
r1, g1, b1 = 0, x, c
} else if h >= 240 && h < 300 {
r1, g1, b1 = x, 0, c
} else {
r1, g1, b1 = c, 0, x
}
r := u8((r1 + m) * 255)
g := u8((g1 + m) * 255)
b := u8((b1 + m) * 255)
return color_u32(r, g, b, 0xFF)
}
draw_xor :: proc(bitmap: ^Bitmap) {
@(static) offset: u32 = 0
i: u32
for y: u32; y < bitmap.height; y += 1 {
for x: u32; x < bitmap.width; x += 1 {
c := u8(((x + offset) ~ (y + offset)) % 256)
bitmap.buffer[i] = color_u32(c, c, c, 0xFF)
i += 1
}
}
offset += 2
}
Fixed_Width :: 1280
Fixed_Height :: 720
draw_fast_circles :: proc(bitmap: ^Bitmap) {
@(static) offset := 0
@(static) scale: f32 = 512
@(static) palette: [256]u32
if palette[0] == 0x0 {
for i := 0; i < 256; i += 1 {
palette[i] = rgb_to_hsv(u32((f32(i) * 1.4) + 0.5), 1, 1)
}
}
@(static) buffer: [Fixed_Width * Fixed_Height]u8
if buffer[0] == 0 {
i: u32
for y: u32; y < Fixed_Height; y += 1 {
for x: u32; x < Fixed_Width; x += 1 {
c := u8(128 + (math.sin(f32((x * x) + (y * y)) / scale) * 127))
buffer[i] = c
i += 1
}
}
}
for p, i in buffer {
bitmap.buffer[i] = palette[(offset + int(p)) % 256]
}
offset = (offset + 1) % 256
}
draw_plasma :: proc(bitmap: ^Bitmap) {
@(static) offset := 0
@(static) palette: [256]u32
if palette[0] == 0x0 {
for i := 0; i < 256; i += 1 {
palette[i] = rgb_to_hsv(u32((f32(i) * 1.4) + 0.5), 1, 1)
}
}
@(static) buffer: [Fixed_Width * Fixed_Height]u8
if buffer[0] == 0 {
i: u32
for y: u32; y < Fixed_Height; y += 1 {
for x: u32; x < Fixed_Width; x += 1 {
c := u8((128 + (math.sin(f32(x) / 16) * 128) + 128 + (math.sin(f32(y) / 16) * 128)) / 2)
buffer[i] = c
i += 1
}
}
}
for p, i in buffer {
bitmap.buffer[i] = palette[(offset + int(p)) % 256]
}
/** Debug palette drawing */
palette_start := int(bitmap.width * (bitmap.height - 11))
for x := 0; x <= 256; x += 1 {
bitmap.buffer[palette_start + x] = 0xFF000000
}
palette_start += int(bitmap.width)
for y := 0; y < 10; y += 1 {
y_offset := y * int(bitmap.width)
for c, i in palette {
bitmap.buffer[palette_start + y_offset + i] = c
}
bitmap.buffer[palette_start + y_offset + 256] = 0xFF000000
}
offset = (offset + 1) % 256
}
draw_line_tests :: proc(screen_buffer: ^Bitmap) {
draw_line(screen_buffer, 220, 360, 420, 360, 0xFFFFFFFF)
draw_line(screen_buffer, 320, 260, 320, 460, 0xFFFFFFFF)
draw_line(screen_buffer, 220, 260, 420, 460, 0xFFFFFFFF)
draw_line(screen_buffer, 220, 460, 420, 260, 0xFFFFFFFF)
draw_line(screen_buffer, 220, 260, 420, 260, 0xFFFFFFFF)
draw_line(screen_buffer, 220, 460, 420, 460, 0xFFFFFFFF)
draw_line(screen_buffer, 220, 260, 220, 460, 0xFFFFFFFF)
draw_line(screen_buffer, 420, 260, 420, 460, 0xFFFFFFFF)
for α := 0; α < 180; α += 1 {
θ := f32(α) / f32(math.DEG_PER_RAD)
x := i32((math.cos(θ) * 100) + 0.5)
y := -i32((math.sin(θ) * 100) + 0.5)
draw_line(screen_buffer, 960 - x, 360 - y, 960 + x, 360 + y, 0xFFFFFFFF)
}
px: i32 = 50
py: i32 = 0
for α := 0; α <= 360; α += 45 {
θ := f32(α) / f32(math.DEG_PER_RAD)
x := i32((math.cos(θ) * 50) + 0.5)
y := -i32((math.sin(θ) * 50) + 0.5)
draw_line(screen_buffer, 640 + px, 360 + py, 640 + x, 360 + y, 0xFFFFFFFF)
px = x
py = y
}
}
// TODO: Basic 24-bit .bmp-support. (can/should use stb libs in the future)
// TODO: Basic bitmap functions. (sub-copy, alpha, etc)
// TODO: Advanced bitmap functions. (line, rectangle, triangle, filled, etc)
// TODO: Controller support, win/lin.
// TODO: "Piano" scene. (after audio)
scene_funcs := []proc(_: ^Bitmap){draw_xor, draw_fast_circles, draw_plasma, draw_line_tests}
FONT_HEIGHT :: 20
font_bitmap: Bitmap
glyph_data: [96]stbtt.bakedchar
ctx: mu.Context
mu_has_init := false
init_mu :: proc() {
{
when ODIN_OS == .Windows {
data, err := os.read_entire_file_from_path("C:\\Windows\\Fonts\\arial.ttf")
}
when ODIN_OS == .Linux {
data, err := os.read_entire_file_from_path(
"/usr/share/fonts/TTF/JetBrainsMonoNL-Regular.ttf",
context.allocator,
)
}
if err != nil {
fmt.println("fail reading font")
return
}
font: stbtt.fontinfo
stbtt.InitFont(&font, &data[0], stbtt.GetFontOffsetForIndex(&data[0], 0))
temp_bitmap: [256 * 256]byte
stbtt.BakeFontBitmap(&data[0], 0, FONT_HEIGHT, &temp_bitmap[0], 256, 256, 32, 96, &glyph_data[0])
fmt.printf("%+v\n", glyph_data)
font_bitmap.buffer = make([]u32, 256 * 256)
font_bitmap.width = 256
font_bitmap.height = 256
for i in 0 ..< len(temp_bitmap) {
font_bitmap.buffer[i] = color_u32(temp_bitmap[i], 0, 0, 255)
}
}
mu.init(&ctx)
ctx.text_width = proc(font: mu.Font, str: string) -> i32 {
// fmt.printf("checking text_width: %s\n", str)
width: i32
for r in str {
gd := &glyph_data[r - 32]
width += i32(gd.xadvance + 0.5)
}
return width
}
ctx.text_height = proc(font: mu.Font) -> i32 {
return FONT_HEIGHT
}
}
// TODO: Text drawing.
// TODO: VirtualAlloc'ed/shm memory, hooked into Odin's context.
app_update_and_render :: proc(screen_buffer: ^Bitmap, input: ^AppInput) {
if !mu_has_init {
mu_has_init = true
init_mu()
}
@(static) scene_num := 0
@(static) mouse_x: i32
@(static) mouse_y: i32
if input.keyboard[AppInputKey.Num1].down {
scene_num = 0
mem.zero_slice(screen_buffer.buffer)
} else if input.keyboard[AppInputKey.Num2].down {
scene_num = 1
mem.zero_slice(screen_buffer.buffer)
} else if input.keyboard[AppInputKey.Num3].down {
scene_num = 2
mem.zero_slice(screen_buffer.buffer)
} else if input.keyboard[AppInputKey.Num4].down {
scene_num = 3
mem.zero_slice(screen_buffer.buffer)
}
scene_funcs[scene_num](screen_buffer)
draw_bitmap(screen_buffer, &font_bitmap, 400, 100)
mx: i32
my: i32
if input.mouse_x > 0 && input.mouse_x < i32(screen_buffer.width) - 10 {
mx = input.mouse_x
}
if input.mouse_y > 0 && input.mouse_y < i32(screen_buffer.height) - 10 {
my = input.mouse_y
}
if mx != mouse_x || my != mouse_y {
mu.input_mouse_move(&ctx, mx, my)
}
if input.mouse_button[0].transition {
if input.mouse_button[0].down {
mu.input_mouse_down(&ctx, mx, my, .LEFT)
} else {
mu.input_mouse_up(&ctx, mx, my, .LEFT)
}
}
mouse_x, mouse_y = mx, my
/*
input_scroll :: proc(ctx: ^Context, x, y: i32)
input_key_down :: proc(ctx: ^Context, key: Key)
input_key_up :: proc(ctx: ^Context, key: Key)
input_text :: proc(ctx: ^Context, text: string)
*/
draw_rect(
screen_buffer,
mouse_x,
mouse_y,
mouse_x + 10,
mouse_y + 10,
0xFFFF00FF if input.mouse_button[0].down else 0xFFFF0000,
)
mu.begin(&ctx)
if mu.begin_window(&ctx, "Test Window", {x = 10, y = 10, w = 200, h = 200}) {
mu.label(&ctx, "Test label")
mu.button(&ctx, "Button!")
mu.end_window(&ctx)
}
if mu.begin_window(&ctx, "Test Slider", {x = 100, y = 20, w = 200, h = 200}) {
value := f32(650)
mu.slider(&ctx, &value, 42, 1337, 10)
mu.end_window(&ctx)
}
mu.end(&ctx)
clip := [?]i32{0, 0, i32(screen_buffer.width), i32(screen_buffer.height)}
cmd: ^mu.Command = nil
for mu.next_command(&ctx, &cmd) {
switch c in cmd.variant {
case ^mu.Command_Jump:
fmt.println("Command_Jump")
case ^mu.Command_Text:
x := f32(c.pos[0])
y := f32(c.pos[1] + 16)
for r in c.str {
quad: stbtt.aligned_quad
stbtt.GetBakedQuad(&glyph_data[0], 256, 256, i32(r - 32), &x, &y, &quad, true)
if i32(quad.x0) < clip[0] || i32(quad.x1) > clip[2] {
continue
}
if i32(quad.y0) < clip[1] || i32(quad.y1) > clip[3] {
continue
}
sx1 := u32(quad.s0 * 256.0)
sy1 := u32(quad.t0 * 256.0)
sx2 := u32(quad.s1 * 256.0)
sy2 := u32(quad.t1 * 256.0)
// fmt.printf("%c #%+v -> %d, %d : %d, %d\n", r, quad, sx1, sy1, sx2, sy2)
draw_sub_bitmap(screen_buffer, &font_bitmap, i32(quad.x0), i32(quad.y0), sx1, sy1, sx2, sy2)
/*
when DEBUG_DRAW_UI_CALLS {
draw_rect(
screen_buffer,
i32(quad.x0 + 0.5),
i32(quad.y0 + 0.5),
i32(quad.x1 + 0.5),
i32(quad.y1 + 0.5),
0xFFFF00FF,
)
}
*/
}
case ^mu.Command_Rect:
x1, x2 := c.rect.x, c.rect.x + c.rect.w
y1, y2 := c.rect.y, c.rect.y + c.rect.h
for y in y1 ..< y2 {
if y < clip[1] || y >= clip[3] {
continue
}
for x in x1 ..< x2 {
if x < clip[0] || x >= clip[2] {
continue
}
screen_buffer.buffer[(y * i32(screen_buffer.width)) + x] = color_u32(c.color)
}
}
when DEBUG_DRAW_UI_CALLS {
draw_rect(screen_buffer, x1, y1, x2, y2, 0xFFFF0000)
}
case ^mu.Command_Icon:
when DEBUG_DRAW_UI_CALLS {
x1, x2 := c.rect.x, c.rect.x + c.rect.w
y1, y2 := c.rect.y, c.rect.y + c.rect.h
draw_rect(screen_buffer, x1, y1, x2, y2, 0xFF0000FF)
}
case ^mu.Command_Clip:
clip[0] = c.rect.x
clip[1] = c.rect.y
clip[2] = c.rect.x + c.rect.w
clip[3] = c.rect.y + c.rect.h
when DEBUG_DRAW_UI_CALLS {
draw_rect(screen_buffer, clip[0], clip[1], clip[2], clip[3], 0xFFFFAA00)
}
}
}
}
glyph :: struct {
bitmap: [^]byte,
width: int,
height: int,
}
/*
@(init)
test_stbtt :: proc() {
when ODIN_OS == .Windows {
data, ok := os.read_entire_file_from_filename("C:\\Windows\\Fonts\\arialbd.ttf")
}
when ODIN_OS == .Linux {
data, ok := os.read_entire_file_from_filename("/usr/share/fonts/TTF/OpenSans-Regular.ttf")
}
if !ok {
fmt.println("fail reading font")
return
}
font: stbtt.fontinfo
stbtt.InitFont(&font, &data[0], stbtt.GetFontOffsetForIndex(&data[0], 0))
max_h: int
msg := "Hello stb_truetype!"
glyphs := make(map[rune]glyph)
defer delete(glyphs)
for r in msg {
if !(r in glyphs) {
w, h: c.int
bitmap := stbtt.GetCodepointBitmap(&font, 0, stbtt.ScaleForPixelHeight(&font, 16), r, &w, &h, nil, nil)
glyphs[r] = {
bitmap = bitmap,
width = int(w),
height = int(h),
}
if int(h) > max_h {
max_h = int(h)
}
}
}
defer {
for r in glyphs {
defer stbtt.FreeBitmap(glyphs[r].bitmap, nil)
}
}
fmt.printf("%+v\nmax_h %d\n", glyphs, max_h)
chars := [?]rune{' ', '.', ':', 'i', 'o', 'V', 'M', '@'}
for y in 0 ..< max_h {
for r in msg {
g := glyphs[r]
if g.bitmap == nil {
for _ in 0 ..< 4 {
fmt.printf(" ")
}
fmt.printf(" ")
continue
}
offset := max_h - g.height
if y < offset || y >= max_h - 1 {
for x in 0 ..< g.width {
fmt.printf(" ")
}
fmt.printf(" ")
continue
}
i := (y - offset) * g.width
for x in 0 ..< g.width {
fmt.printf("%c", chars[g.bitmap[i + x] >> 5])
}
fmt.printf(" ")
}
fmt.println()
}
fmt.println()
}
*/