pushing out a lot of module changes
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 9 Jun 2021 00:53:18 +0000 (19:53 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 9 Jun 2021 00:53:18 +0000 (19:53 -0500)
14 files changed:
core/container/array.onyx
modules/bmfont/bmfont_loader.onyx [new file with mode: 0644]
modules/bmfont/module.onyx [new file with mode: 0644]
modules/bmfont/types.onyx [new file with mode: 0644]
modules/ttf/binary_reader.onyx [new file with mode: 0644]
modules/ttf/module.onyx [new file with mode: 0644]
modules/ttf/ttf.onyx [new file with mode: 0644]
modules/ui/components/button.onyx [new file with mode: 0644]
modules/ui/flow.onyx
modules/ui/module.onyx
modules/ui/resources/fonts/FiraCode.data [new file with mode: 0644]
modules/ui/resources/fonts/FiraCode.fnt [new file with mode: 0644]
modules/ui/resources/fonts/FiraCode.png [new file with mode: 0644]
modules/ui/ui.onyx

index 4eb216810cd2b93372514b14c0b9800e3aac7ff4..bfc16120ff60f916efad5a423733640b037dc05f 100644 (file)
@@ -266,12 +266,14 @@ fold :: proc {
 }
 
 map :: proc {
-    (arr: ^[..] $T, f: (^T) -> void)          do for ^it: *arr do f(it);,
-    (arr: ^[..] $T, f: (T) -> T)              do for ^it: *arr do *it = f(*it);,
-    (arr: ^[..] $T, data: $R, f: (T, R) -> T) do for ^it: *arr do *it = f(*it, data);,
-    (arr: [] $T, f: (^T) -> void)             do for ^it:  arr do f(it);,
-    (arr: [] $T, f: (T) -> T)                 do for ^it:  arr do *it = f(*it);,
-    (arr: [] $T, data: $R, f: (T, R) -> T)    do for ^it:  arr do *it = f(*it, data);,
+    (arr: ^[..] $T, f: (^T) -> void)              do for ^it: *arr do f(it);,
+    (arr: ^[..] $T, f: (T) -> T)                  do for ^it: *arr do *it = f(*it);,
+    (arr: ^[..] $T, data: $R, f: (^T, R) -> void) do for ^it: *arr do f(it, data);,
+    (arr: ^[..] $T, data: $R, f: (T, R) -> T)     do for ^it: *arr do *it = f(*it, data);,
+    (arr: [] $T, f: (^T) -> void)                 do for ^it:  arr do f(it);,
+    (arr: [] $T, f: (T) -> T)                     do for ^it:  arr do *it = f(*it);,
+    (arr: [] $T, data: $R, f: (^T, R) -> void)    do for ^it:  arr do f(it, data);,
+    (arr: [] $T, data: $R, f: (T, R) -> T)        do for ^it:  arr do *it = f(*it, data);,
 }
 
 every :: (arr: ^[..] $T, predicate: (T) -> bool) -> bool {
diff --git a/modules/bmfont/bmfont_loader.onyx b/modules/bmfont/bmfont_loader.onyx
new file mode 100644 (file)
index 0000000..014144d
--- /dev/null
@@ -0,0 +1,138 @@
+package bmfont
+
+use package core
+
+
+load_bmfont :: (fnt_data: [] u8) -> BMFont {
+    bmf: BMFont;
+    map.init(^bmf.pages);
+    map.init(^bmf.glyphs);
+
+    parse_bmfont(fnt_data, ^bmf);
+
+    @Cleanup // this was a stupid way of doing this. Just use a f-ing for loop.
+    array.map(^bmf.glyphs.entries, ^bmf, (glyph: ^map.Map.Entry(i32, BMFont_Glyph), font: ^BMFont) {
+        glyph.value.tex_x = ~~ glyph.value.x / cast(f32) font.common.scale_width;        
+        glyph.value.tex_y = ~~ glyph.value.y / cast(f32) font.common.scale_width;        
+        glyph.value.tex_w = ~~ glyph.value.w / cast(f32) font.common.scale_width;        
+        glyph.value.tex_h = ~~ glyph.value.h / cast(f32) font.common.scale_width;        
+    });
+
+    m_glyph := map.get_ptr(^bmf.glyphs, #char "M");
+    bmf.em = ~~m_glyph.h / cast(f32) bmf.common.line_height;
+
+    return bmf;
+}
+
+#private_file
+parse_bmfont :: (fnt_data: [] u8, font: ^BMFont) {
+    R :: package core.string.reader
+
+    parser_arena := alloc.arena.make(context.allocator, arena_size=4 * 1024);
+    parser_allocator := alloc.arena.make_allocator(^parser_arena);
+    defer alloc.arena.free(^parser_arena);
+
+    reader := R.make(fnt_data);
+
+    while !R.empty(^reader) {
+        line := R.read_line(^reader);
+        pieces := string.split(line, #char " ", allocator=parser_allocator);
+
+        tag := pieces[0];
+        pieces = pieces.data[1 .. pieces.count];
+
+        if tag == "page" {
+            id := -1;
+            filename := null_str;
+
+            for ^piece: pieces {
+                key := string.read_until(piece, #char "=");
+                string.advance(piece, 1);
+
+                if key == "id"   do id = ~~ conv.str_to_i64(*piece);
+                if key == "file" do filename = string.alloc_copy(*piece);
+            }
+
+            map.put(^font.pages, id, filename);
+            continue;
+        }
+
+        if tag == "char" {
+            char: BMFont_Glyph;
+
+            for ^piece: pieces {
+                key := string.read_until(piece, #char "=");
+                string.advance(piece, 1);
+
+                if     key == "id"       do char.id = ~~ conv.str_to_i64(*piece);
+                elseif key == "x"        do char.x  = ~~ conv.str_to_i64(*piece);
+                elseif key == "y"        do char.y  = ~~ conv.str_to_i64(*piece);
+                elseif key == "width"    do char.w  = ~~ conv.str_to_i64(*piece);
+                elseif key == "height"   do char.h  = ~~ conv.str_to_i64(*piece);
+                elseif key == "xoffset"  do char.xoffset = ~~ conv.str_to_i64(*piece);
+                elseif key == "yoffset"  do char.yoffset = ~~ conv.str_to_i64(*piece);
+                elseif key == "xadvance" do char.xadvance = ~~ conv.str_to_i64(*piece);
+                elseif key == "page"     do char.page = ~~ conv.str_to_i64(*piece);
+                elseif key == "chhl"     do char.channel = ~~ conv.str_to_i64(*piece);
+            }
+
+            map.put(^font.glyphs, char.id, char);
+            continue;
+        }
+
+        @Note // this for loop is very destructive of the data that is here, but
+        // it is assumed that after a line is processed, the data will not be needed
+        // again. To be clear, this means that it destroys the pieces from the split,
+        // not the data in fnt_data.
+        for ^piece: pieces {
+            key := string.read_until(piece, #char "=");
+            string.advance(piece, 1);
+
+            if tag == "info"   do parse_info_tag(font, parser_allocator, key, *piece);
+            if tag == "common" do parse_common_tag(font, parser_allocator, key, *piece);
+        }
+    }
+
+    parse_info_tag :: (font: ^BMFont, parser_allocator: Allocator, key: str, value: str) {
+        info := ^font.info;
+
+        if     key == "face"     do info.face_name = string.alloc_copy(value);
+        elseif key == "size"     do info.size = ~~ conv.str_to_i64(value);
+        elseif key == "bold"     do info.bold   = value == "1";
+        elseif key == "italic"   do info.italic = value == "1";
+        elseif key == "charset"  do info.charset = string.alloc_copy(value);
+        elseif key == "unicode"  do info.unicode = value == "1";
+        elseif key == "stretchH" do info.stretchH = ~~ conv.str_to_i64(value);
+        elseif key == "smooth"   do info.smooth = value == "1";
+        elseif key == "aa"       do info.supersampling = ~~ conv.str_to_i64(value);
+
+        elseif key == "padding" {
+            values := string.split(value, #char ",", allocator=parser_allocator);
+            info.padding.top    = ~~ conv.str_to_i64(values[0]);
+            info.padding.right  = ~~ conv.str_to_i64(values[1]);
+            info.padding.bottom = ~~ conv.str_to_i64(values[2]);
+            info.padding.left   = ~~ conv.str_to_i64(values[3]);
+        }
+
+        elseif key == "spacing" {
+            values := string.split(value, #char ",", allocator=parser_allocator);
+            info.spacing.horizontal = ~~ conv.str_to_i64(values[0]);
+            info.spacing.vertical   = ~~ conv.str_to_i64(values[1]);
+        }
+    }
+
+    parse_common_tag :: (font: ^BMFont, parser_allocator: Allocator, key: str, value: str) {
+        common := ^font.common;
+
+        if     key == "lineHeight" do common.line_height = ~~ conv.str_to_i64(value);
+        elseif key == "base"       do common.baseline = ~~ conv.str_to_i64(value);
+        elseif key == "scaleW"     do common.scale_width = ~~ conv.str_to_i64(value);
+        elseif key == "scaleH"     do common.scale_height = ~~ conv.str_to_i64(value);
+        elseif key == "pages"      do common.page_count = ~~ conv.str_to_i64(value);
+        elseif key == "packed"     do common.packed = value == "1";
+        elseif key == "alphaChnl"  do common.alpha_channel = ~~ conv.str_to_i64(value);
+        elseif key == "redChnl"    do common.red_channel   = ~~ conv.str_to_i64(value);
+        elseif key == "greenChnl"  do common.alpha_channel = ~~ conv.str_to_i64(value);
+        elseif key == "blueChnl"   do common.blue_channel  = ~~ conv.str_to_i64(value);
+    }
+}
diff --git a/modules/bmfont/module.onyx b/modules/bmfont/module.onyx
new file mode 100644 (file)
index 0000000..b0f20b2
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+TODO:
+
+
+
+
+A simple package for reading BMFont files. A good source of documentation can be found here: https://www.angelcode.com/products/bmfont/
+
+This pacakge is responible for:
+    - Reading *.fnt files to parse their contents into glyphs
+
+*/
+
+
+package bmfont
+
+#load "./types"
+#load "./bmfont_loader"
+
diff --git a/modules/bmfont/types.onyx b/modules/bmfont/types.onyx
new file mode 100644 (file)
index 0000000..03456c4
--- /dev/null
@@ -0,0 +1,81 @@
+package bmfont
+
+use package core
+
+
+BMFont :: struct {
+    info   : BMFont_Info;
+    common : BMFont_Common;
+
+    pages  : map.Map(i32, str);
+    glyphs : map.Map(i32, BMFont_Glyph);
+
+    em : f32;
+
+    get_glyph :: (use bmfont: ^BMFont, char: u8) -> ^BMFont_Glyph {
+        return map.get_ptr(^glyphs, ~~char);
+    }
+}
+
+BMFont_Info :: struct {
+    face_name     : str;
+    size          : u32;
+    bold          : bool;
+    italic        : bool;
+    charset       : str;
+    unicode       : bool;
+    stretchH      : u32;
+    smooth        : bool;
+    supersampling : u32;
+
+    padding: struct {
+        top, right, bottom, left: i32;
+    };
+
+    spacing: struct {
+        horizontal, vertical: i32;
+    };
+
+    outline: u32;
+}
+
+BMFont_Common :: struct {
+
+    @Cleanup // I made a lot of these fields 32-bits in size, even though most of them could be probably be 16-bit if not 8-bit.
+    line_height  : i32;
+    baseline     : i32; // From absolute top of the line to the base of the characaters
+    scale_width  : u32;
+    scale_height : u32;
+    page_count   : u32;
+    packed       : bool;
+
+    alpha_channel : Channel;
+    red_channel   : Channel;
+    green_channel : Channel;
+    blue_channel  : Channel;
+
+    Channel :: enum {
+        Glyph             :: 0x00;
+        Outline           :: 0x01;
+        Glyph_And_Outline :: 0x02;
+        Zero              :: 0x03;
+        One               :: 0x04;
+    }
+}
+
+BMFont_Glyph :: struct {
+    id   : i32;
+    x, y : u32;
+    w, h : u32;
+
+    xoffset, yoffset : i32;
+    xadvance         : i32;
+
+    page    : u8;
+    channel : u8;
+
+    tex_x: f32 = 0;
+    tex_y: f32 = 0;
+    tex_w: f32 = 0;
+    tex_h: f32 = 0;
+}
\ No newline at end of file
diff --git a/modules/ttf/binary_reader.onyx b/modules/ttf/binary_reader.onyx
new file mode 100644 (file)
index 0000000..7f14ccc
--- /dev/null
@@ -0,0 +1,73 @@
+package ttf
+
+
+// A simple big-endian binary reader out of a byte buffer
+TTF_Reader :: struct {
+    data     : [] u8;
+    position : u32 = 0;
+
+    seek :: (use br: ^TTF_Reader, pos: u32) -> u32 {
+        assert(pos >= 0 && pos < data.count, "Trying to seek outside of buffer.");
+        defer position = pos;
+        return position;
+    }
+
+    tell :: (use br: ^TTF_Reader) -> u32 do return position;
+
+    get_u8 :: (use br: ^TTF_Reader) -> u8 {
+        defer position += 1;
+        return data[position];
+    }
+
+    get_u16 :: (use br: ^TTF_Reader) -> u16 {
+        byte1 := cast(u16) get_u8(br);
+        byte2 := cast(u16) get_u8(br);
+        return byte1 << 8 | byte2;
+    }
+
+    get_u32 :: (use br: ^TTF_Reader) -> u32 {
+        byte1 := cast(u32) get_u8(br);
+        byte2 := cast(u32) get_u8(br);
+        byte3 := cast(u32) get_u8(br);
+        byte4 := cast(u32) get_u8(br);
+        return byte1 << 24 | byte2 << 16 | byte3 << 8 | byte4; 
+    }
+
+    get_i16 :: (use br: ^TTF_Reader) -> i16 {
+        result := get_u16(br);
+        if result & ~~ 0x8000 != 0 {
+            result -= (1 << 16);
+        }
+    }
+
+    get_i32 :: (use br: ^TTF_Reader) -> i32 {
+        return ~~ get_u32(br);
+    }
+
+    get_fword :: (use br: ^TTF_Reader) -> i16 {
+        return get_i16(br);
+    }
+
+    get_2dot14 :: (use br: ^TTF_Reader) -> f32 {
+        numerator := cast(i32) get_i16(br);
+        return ~~ numerator / cast(f32) (1 << 14);
+    }
+
+    get_fixed :: (use br: ^TTF_Reader) -> f32 {
+        numerator := get_i32(br);
+        return ~~ numerator / cast(f32) (1 << 16);
+    }
+
+    @Note // does not allocate a new string.
+    get_string :: (use br: ^TTF_Reader, length: u32) -> str {
+        defer position += length;
+        return data.data[position .. position + length];
+    }
+
+    @Fix // this is not correct at all.
+    get_date :: (use br: ^TTF_Reader) -> u64 {
+        mac_time := ~~get_u32(br) * 0x100000000 + cast(u64) get_u32(br);
+        utc_time := mac_time * 1000;
+        return utc_time;
+    }
+}
\ No newline at end of file
diff --git a/modules/ttf/module.onyx b/modules/ttf/module.onyx
new file mode 100644 (file)
index 0000000..67c95d5
--- /dev/null
@@ -0,0 +1,7 @@
+@Incomplete
+// This whole module is very imcomplete
+
+package ttf
+
+#load "./ttf"
+#load "./binary_reader"
\ No newline at end of file
diff --git a/modules/ttf/ttf.onyx b/modules/ttf/ttf.onyx
new file mode 100644 (file)
index 0000000..819952e
--- /dev/null
@@ -0,0 +1,531 @@
+package ttf
+
+use package core
+
+True_Type_Font :: struct {
+    reader : TTF_Reader;
+
+    scalar_type    : u32;
+    search_range   : u16;
+    entry_selector : u16;
+    range_shift    : u16;
+
+    tables : map.Map(u32, TTF_Table_Info);
+    char_maps : [..] TTF_Cmap;
+
+    version : u32;
+    font_revision : u32;
+    checksum_adjustment : u32;
+    magic_number : u32;
+    flags : u16;
+    units_per_em : u16;
+    // created : u64;
+    // modified : u64;
+    x_min : i16;
+    x_max : i16;
+    y_min : i16;
+    y_max : i16;
+    mac_style : u16;
+    lowest_rec_ppem : u16;
+    font_direction_hint : i16;
+    index_to_loc_format : TTF_Index_To_Loc_Format;
+    glyph_data_format : i16;
+
+    hhea : struct {
+        version : u32;
+        ascent  : i16;
+        descent : i16;
+        line_gap : i16;
+        advance_width_max : u16;
+        min_left_side_bearing : i16;
+        min_right_side_bearing : i16;
+        x_max_extent : i16;
+        caret_slope_rise : i16;
+        caret_slope_run : i16;
+        caret_offset : i16;
+        metric_data_format : i16;
+        num_of_long_hor_metrics : u16;
+    };
+}
+
+TTF_Table_Info :: struct {
+    checksum : u32 = 0;
+    offset   : u32 = 0;
+    length   : u32 = 0;
+}
+
+TTF_Index_To_Loc_Format :: enum (i16) {
+    Short :: 0x00;
+    Long  :: 0x01;
+}
+
+TTF_Glyph :: struct {
+    contour_count : i16;
+    x_min : i16;
+    x_max : i16;
+    y_min : i16;
+    y_max : i16;
+
+    points : [..] TTF_Glyph_Point;
+    contour_ends : [..] u16;
+}
+
+TTF_Glyph_Point :: struct {
+    on_curve : bool;
+    x : i16 = 0;
+    y : i16 = 0;
+}
+
+
+TTF_Cmap_Format :: enum (u16) {
+    Simple    :: 0x00;
+    Segmented :: 0x04;
+}
+
+TTF_Cmap_Base  :: struct { format : TTF_Cmap_Format; }
+
+TTF_Cmap0 :: struct {
+    use base: TTF_Cmap_Base;
+
+    glyph_indicies: [] u8;
+}
+
+TTF_Cmap4 :: struct {
+    use base: TTF_Cmap_Base;
+
+    seg_count      : u16;
+    search_range   : u16;
+    entry_selector : u16;
+    range_shift    : u16;
+
+    segments : [..] TTF_Segment;
+    cache    : map.Map(i32, i32);
+}
+
+TTF_Segment :: struct {
+    start_code      : u16;
+    end_code        : u16;
+    id_delta        : u16;
+    id_range_offset : u16;
+}
+
+TTF_Cmap :: struct #union {
+    use base: TTF_Cmap_Base;
+    cmap0: TTF_Cmap0;
+    cmap4: TTF_Cmap4;
+}
+
+
+ttf_create :: (ttf_data: [] u8) -> True_Type_Font {
+    ttf : True_Type_Font;
+    ttf.reader = TTF_Reader.{ data = ttf_data };
+
+    map.init(^ttf.tables, .{});
+    array.init(^ttf.char_maps);
+
+    ttf_read_offset_table(^ttf);
+    ttf_read_head_table(^ttf);
+    ttf_read_cmap_table(^ttf);
+    ttf_read_hhea_table(^ttf);
+
+    return ttf;
+}
+
+#private
+ttf_read_offset_table :: (use ttf: ^True_Type_Font) {
+    scalar_type = reader->get_u32();
+    num_tables := reader->get_u16();
+    search_range = reader->get_u16();
+    entry_selector = reader->get_u16();
+    range_shift = reader->get_u16();
+
+    for i: 0 .. ~~num_tables {
+        tag := reader->get_string(4);
+        tag_int := string_to_beu32(tag);
+        println(tag);
+
+        table_info : TTF_Table_Info;
+        table_info.checksum = reader->get_u32();
+        table_info.offset   = reader->get_u32();
+        table_info.length   = reader->get_u32();
+
+        map.put(^tables, tag_int, table_info);
+
+        if !string.equal(tag, "head") {
+            csum := ttf_calc_table_checksum(^reader, table_info.offset, table_info.length);
+            if table_info.checksum != csum {
+                print("WARNING: Checksum for table '");
+                print(tag);
+                print("' did not match.");
+            }
+        }
+    }
+}
+
+#private
+ttf_read_head_table :: (use ttf: ^True_Type_Font) {
+    head_table_info  := map.get(^tables, string_to_beu32("head"));
+    reader->seek(head_table_info.offset);
+
+    version = reader->get_u32();
+    font_revision = reader->get_u32();
+    checksum_adjustment = reader->get_u32();
+    magic_number = reader->get_u32();            // NOTE: Should be 0x5f0f3cf5
+    assert(magic_number == 0x5f0f3cf5, "Magic number was wrong.");
+
+    flags = reader->get_u16();
+    units_per_em = reader->get_u16();
+    reader->get_date(); // created
+    reader->get_date(); // modified
+    x_min = reader->get_fword();
+    y_min = reader->get_fword();
+    x_max = reader->get_fword();
+    y_max = reader->get_fword();
+    mac_style = reader->get_u16();
+    lowest_rec_ppem = reader->get_u16();
+    font_direction_hint = reader->get_i16();
+    index_to_loc_format = cast(TTF_Index_To_Loc_Format) reader->get_i16();
+    glyph_data_format = reader->get_i16();
+}
+
+ttf_lookup_glyph_offset :: (use ttf: ^True_Type_Font, glyph_index: i32) -> i32 {
+    loca_table_info := map.get(^tables, string_to_beu32("loca"));
+    glyf_table_info := map.get(^tables, string_to_beu32("glyf"));
+
+    old: u32;
+    defer reader->seek(old);
+
+    switch index_to_loc_format {
+        case .Long {
+            old = reader->seek(loca_table_info.offset + glyph_index * 4);
+            return reader->get_u32() + glyf_table_info.offset;
+        }
+
+        case #default {
+            old = reader->seek(loca_table_info.offset + glyph_index * 2);
+            return 2 * cast(u32) reader->get_u16() + glyf_table_info.offset;
+        }
+    }
+
+    return -1;
+}
+
+// Result is expected to be freed
+ttf_read_glyph :: (use ttf: ^True_Type_Font, glyph_index: i32, allocator := context.allocator) -> ^TTF_Glyph {
+    offset := ttf_lookup_glyph_offset(ttf, glyph_index);
+
+    glyf_table_info := map.get(^tables, string_to_beu32("glyf"));
+
+    if offset >= glyf_table_info.offset + glyf_table_info.length do return null;
+
+    reader->seek(offset);
+
+    glyph := make(TTF_Glyph, allocator);
+    glyph.contour_count = reader->get_i16();
+    glyph.x_min = reader->get_fword();
+    glyph.y_min = reader->get_fword();
+    glyph.x_max = reader->get_fword();
+    glyph.y_max = reader->get_fword();
+
+    if glyph.contour_count < 0 { raw_free(allocator, glyph); return null; }
+    if glyph.contour_count == -1 {
+        // Compound glyph
+        return null;
+
+    } else {
+        // Simple glyph
+        ttf_read_simple_glyph(ttf, glyph);
+    }
+
+    return glyph;
+}
+
+ttf_glyph_destroy :: (glyph: ^TTF_Glyph, allocator := context.allocator) {
+    array.free(^glyph.contour_ends);
+    array.free(^glyph.points);
+    raw_free(allocator, glyph);
+}
+
+#private_file
+TTF_Glyph_Flags :: enum #flags {
+    On_Curve  :: 0x01;
+    X_Is_Byte :: 0x02;
+    Y_Is_Byte :: 0x04;
+    Repeat    :: 0x08;
+    X_Delta   :: 0x10;
+    Y_Delta   :: 0x20;
+}
+
+#private_file
+ttf_read_simple_glyph :: (use ttf: ^True_Type_Font, glyph: ^TTF_Glyph) {
+    array.init(^glyph.contour_ends, ~~glyph.contour_count);
+    array.init(^glyph.points);
+
+    for i: 0 .. ~~glyph.contour_count {
+        array.push(^glyph.contour_ends, reader->get_u16());
+    }
+
+    reader->seek(~~ reader->get_u16() + reader->tell());
+
+    if glyph.contour_count == 0 do return;
+
+    num_points := array.fold(^glyph.contour_ends, cast(u16) 0, math.max_poly) + 1;
+
+    flags : [..] TTF_Glyph_Flags;
+    array.init(^flags);
+    defer array.free(^flags);
+
+    for i: 0 .. ~~num_points {
+        flag := cast(TTF_Glyph_Flags) reader->get_u8();
+        array.push(^flags, flag);
+        array.push(^glyph.points, .{ on_curve = (flag & .On_Curve) != ~~ 0 });
+
+        if (flag & .Repeat) != ~~ 0 {
+            rep_count := reader->get_u8();
+            i += ~~rep_count;
+
+            for i: 0 .. ~~rep_count {
+                array.push(^flags, flag);
+                array.push(^glyph.points, .{ on_curve = (flag & .On_Curve) != ~~ 0 });
+            }
+        }
+    }
+
+    value := cast(i16) 0;
+    for i: 0 .. ~~num_points {
+        flag := flags[i];
+
+        if (flag & .X_Is_Byte) != ~~ 0 {
+            if (flag & .X_Delta) != ~~ 0 {
+                value += ~~ reader->get_u8();
+            } else {
+                value -= ~~ reader->get_u8();
+            }
+        } elseif (flag & .X_Delta) == ~~ 0 {
+            value += reader->get_i16();
+        }
+
+        glyph.points[i].x = value;
+    }
+
+    value = 0;
+    for i: 0 .. ~~num_points {
+        flag := flags[i];
+
+        if (flag & .Y_Is_Byte) != ~~ 0 {
+            if (flag & .Y_Delta) != ~~ 0 {
+                value += ~~ reader->get_u8();
+            } else {
+                value -= ~~ reader->get_u8();
+            }
+        } elseif (flag & .Y_Delta) == ~~ 0 {
+            value += reader->get_i16();
+        }
+
+        glyph.points[i].y = value;
+    }
+}
+
+ttf_glyph_count :: (use ttf: ^True_Type_Font) -> u32 {
+    maxp_table_info  := map.get(^tables, string_to_beu32("maxp"));
+    old := reader->seek(maxp_table_info.offset + 4);
+    defer reader->seek(old);
+
+    return ~~reader->get_u16();
+}
+
+ttf_read_cmap_table :: (use ttf: ^True_Type_Font) {
+    cmap_table_info  := map.get(^tables, string_to_beu32("cmap"));
+    reader->seek(cmap_table_info.offset);
+
+    version := reader->get_u16();
+    num_subtables := reader->get_u16();
+
+    for i: 0 .. ~~num_subtables {
+        platform_id := reader->get_u16();
+        platform_specific_id := reader->get_u16();
+        offset := reader->get_u16();
+
+        // Microsoft Unicode, BMP only
+        if platform_id == 3 && platform_specific_id <= 1 {
+            ttf_read_cmap(ttf, ~~offset + cmap_table_info.offset);
+        }
+    }
+}
+
+ttf_read_cmap :: (use ttf: ^True_Type_Font, offset: u32) {
+    old := reader->seek(offset);
+    defer reader->seek(old);
+
+    format := cast(TTF_Cmap_Format) reader->get_u16();
+    length := reader->get_u16();
+    lang   := reader->get_u16();
+
+    switch format {
+        case .Simple    do ttf_read_cmap0(ttf);
+        case .Segmented do ttf_read_cmap4(ttf);
+
+        case #default { printf("Unsupported cmap format: %i\n", cast(i32) format); }
+    }
+}
+
+ttf_read_cmap0 :: (use ttf: ^True_Type_Font) {
+    cmap : TTF_Cmap;
+    cmap.cmap0.format = .Simple;
+
+    glyphs : [..] u8;
+    array.init(^glyphs, 256);
+    for i: 0 .. 256 do array.push(^glyphs, reader->get_u8());
+
+    cmap.cmap0.glyph_indicies = array.to_slice(^glyphs);
+
+    array.push(^char_maps, cmap);
+}
+
+ttf_read_cmap4 :: (use ttf: ^True_Type_Font) {
+    cmap : TTF_Cmap;
+    cmap.cmap4.format = .Segmented;
+    imap := ^cmap.cmap4;
+    map.init(^imap.cache);
+
+    imap.seg_count = reader->get_u16() >> 1;
+    imap.search_range = reader->get_u16();
+    imap.entry_selector = reader->get_u16();
+    imap.range_shift = reader->get_u16();
+
+    array.init(^imap.segments, ~~imap.seg_count);
+    imap.segments.count = cast(u32) imap.seg_count;
+
+    for ^seg: imap.segments do seg.end_code        = reader->get_u16();
+    reader->get_u16(); // Reserved and unused
+    for ^seg: imap.segments do seg.start_code      = reader->get_u16();
+    for ^seg: imap.segments do seg.id_delta        = reader->get_u16();
+    for ^seg: imap.segments {
+        seg.id_range_offset = reader->get_u16();
+        if seg.id_range_offset != ~~0 do seg.id_range_offset += ~~(reader->tell() - 2);
+    }
+
+    array.push(^char_maps, cmap);
+}
+
+ttf_lookup_glyph_by_char :: (use ttf: ^True_Type_Font, charcode: u32) -> u32 {
+    potential_code := 0;
+
+    for ^cmap: char_maps {
+        switch cmap.format {
+            case .Simple    do potential_code = ttf_lookup_in_cmap0(ttf, ~~cmap, charcode);
+            case .Segmented do potential_code = ttf_lookup_in_cmap4(ttf, ~~cmap, charcode);
+        }
+
+        if potential_code != 0 do return potential_code;
+    }
+
+    return potential_code;
+}
+
+#private_file
+ttf_lookup_in_cmap0 :: (use ttf: ^True_Type_Font, cmap: ^TTF_Cmap0, charcode: u32) -> u32 {
+    if charcode < 0 || charcode >= 256 do return 0;
+    return ~~cmap.glyph_indicies[charcode];
+}
+
+#private_file
+ttf_lookup_in_cmap4 :: (use ttf: ^True_Type_Font, cmap: ^TTF_Cmap4, charcode: u32) -> u32 {
+    if map.has(^cmap.cache, charcode) do return map.get(^cmap.cache, charcode);
+
+    index := 0;
+    for ^seg: cmap.segments {
+        if ~~seg.start_code <= charcode && ~~charcode <= seg.end_code {
+            if seg.id_range_offset != 0 {
+                glyph_index_address := ~~seg.id_range_offset + 2 * (charcode - ~~seg.start_code);
+                reader->seek(glyph_index_address);
+                index = cast(u32) reader->get_u16();
+            } else {
+                index = (~~seg.id_delta + charcode) & 0xffff;
+            }
+
+            break;
+        }
+    }
+
+    map.put(^cmap.cache, charcode, index);
+
+    return index;
+}
+
+
+ttf_read_hhea_table :: (use ttf: ^True_Type_Font) {
+    hhea_table_info  := map.get(^tables, string_to_beu32("hhea"));
+    reader->seek(hhea_table_info.offset);
+
+    hhea.version = reader->get_u32();
+    hhea.ascent  = reader->get_fword();
+    hhea.descent = reader->get_fword();
+    hhea.line_gap = reader->get_fword();
+    hhea.advance_width_max = reader->get_u16();
+    hhea.min_left_side_bearing = reader->get_u16();
+    hhea.min_right_side_bearing = reader->get_u16();
+    hhea.x_max_extent = reader->get_fword();
+    hhea.caret_slope_rise = reader->get_i16();
+    hhea.caret_slope_run = reader->get_i16();
+    hhea.caret_offset = reader->get_fword();
+    reader->get_i16(); // Reserved
+    reader->get_i16(); // Reserved
+    reader->get_i16(); // Reserved
+    reader->get_i16(); // Reserved
+    hhea.metric_data_format = reader->get_i16();
+    hhea.num_of_long_hor_metrics = reader->get_u16();
+}
+
+TTF_Horizontal_Metrics :: struct {
+    advance_width     : u16;
+    left_side_bearing : i16;
+}
+
+ttf_lookup_horizontal_metrics :: (use ttf: ^True_Type_Font, glyph_index: u32) -> TTF_Horizontal_Metrics {
+    hmtx_table_info  := map.get(^tables, string_to_beu32("hmtx"));
+    offset := hmtx_table_info.offset;
+
+    hmtx : TTF_Horizontal_Metrics;
+
+    nmets := cast(u32) hhea.num_of_long_hor_metrics;
+
+    if glyph_index < nmets {
+        offset += glyph_index * 4;
+        old := reader->seek(offset);
+        defer reader->seek(old);
+
+        hmtx.advance_width = reader->get_u16();
+        hmtx.left_side_bearing = reader->get_i16();
+
+    } else {
+        old := reader->seek(offset + (nmets - 1) * 4);
+        defer reader->seek(old);
+
+        hmtx.advance_width = reader->get_u16();
+        reader->seek(offset + nmets * 4 + 2 * (glyph_index - nmets));
+        hmtx.left_side_bearing = reader->get_i16();
+    }
+
+    return hmtx;
+}
+
+
+#private_file
+ttf_calc_table_checksum :: (reader: ^TTF_Reader, offset: u32, length: u32) -> u32 {
+    old := reader->seek(offset);
+    defer reader->seek(old);
+
+    sum := 0;
+    nlongs := (length + 3) >> 2;
+    for i: 0 .. nlongs do sum += reader->get_u32();
+    return sum;
+}
+
+#private_file
+string_to_beu32 :: (s: str) -> u32 {
+    return (cast(u32) s[0] << 24)
+         | (cast(u32) s[1] << 16)
+         | (cast(u32) s[2] << 8)
+         | (cast(u32) s[3]);
+}
diff --git a/modules/ui/components/button.onyx b/modules/ui/components/button.onyx
new file mode 100644 (file)
index 0000000..c292380
--- /dev/null
@@ -0,0 +1,85 @@
+package ui
+use package core
+
+// Button states are stored globally as there is not much to the state of a button.
+// Forcing the end user to store a structure for each button that is just the animation
+// state of the button feels very wrong.
+#private button_states : map.Map(UI_Id, Button_State);
+#private Button_State :: struct {
+    hover_time := 0.0f;
+    click_time := 0.0f;
+}
+
+Button_Theme :: struct {
+    use text_theme := Text_Theme.{};
+
+    background_color := gfx.Color4.{ 0.1, 0.1, 0.1 };
+    hover_color      := gfx.Color4.{ 0.3, 0.3, 0.3 };
+    click_color      := gfx.Color4.{ 0.5, 0.5, 0.7 };
+
+    border_color := gfx.Color4.{ 0.2, 0.2, 0.2 };
+    border_width := 6.0f;    @InPixels
+}
+
+@Bug // there is a compile-time known bug if either of the 'Button_Theme's below are omitted.
+default_button_theme: Button_Theme = Button_Theme.{};
+
+@Themeing
+button :: (use r: Rectangle, text: str, theme := ^default_button_theme, site := #callsite, increment := 0) -> bool {
+    gfx.set_texture();
+
+    result := false;
+
+    hash := get_site_hash(site, increment);
+    button_state := map.get(^button_states, hash);
+
+    if is_active_item(hash) {
+        if mouse_state.left_button_just_up {
+            if is_hot_item(hash) && Rectangle.contains(r, mouse_state.x, mouse_state.y) {
+                result = true;
+                button_state.click_time = 1.0f;
+            }
+
+            set_active_item(0);
+        }
+
+    } elseif is_hot_item(hash) {
+        if mouse_state.left_button_down {
+            set_active_item(hash);
+        }
+    }
+
+    if Rectangle.contains(r, mouse_state.x, mouse_state.y) {
+        set_hot_item(hash);
+    }
+
+    if is_hot_item(hash) {
+        move_towards(^button_state.hover_time, 1.0f, 0.1f);
+    } else {
+        move_towards(^button_state.hover_time, 0.0f, 0.1f);
+    }
+
+    move_towards(^button_state.click_time, 0.0f, 0.08f);
+
+    border_width  := theme.border_width;
+    width, height := Rectangle.dimensions(r);
+
+    gfx.rect(.{ x0, y0 }, .{ width, height }, theme.border_color);
+
+    surface_color := color_lerp(button_state.hover_time, theme.background_color, theme.hover_color);
+    surface_color  = color_lerp(button_state.click_time, surface_color, theme.click_color);
+    gfx.rect(.{ x0 + border_width, y0 + border_width }, .{ width - border_width * 2, height - border_width * 2 }, surface_color);
+
+    text_width  := 0.0f; @Cleanup // font->get_width(text, theme.font_size);
+    text_height := 0.0f; @Cleanup // font->get_height(text, theme.font_size);
+
+    draw_text_raw(text, x0 + (width - text_width) / 2, y0 + (height - text_height) / 2, theme.font_size, theme.text_color);
+
+    if button_state.click_time > 0 || button_state.hover_time > 0 {
+        map.put(^button_states, hash, button_state);
+    } else {
+        map.delete(^button_states, hash);
+    }
+
+    return result;
+}
index 64f28692c23e344c3a9a429d3023b2af4e9961fb..3c389455b24edc23a43d881673041f3a9d2a3bac 100644 (file)
@@ -4,54 +4,54 @@ package ui
 
 Flow :: struct {
     split_vertical :: proc {
-        (r: Rectangle, left_percent: f32) -> (left: Rectangle, right: Rectangle) {
-            return split_vertical(r, left_width=left_percent * Rectangle.width(r));
+        (r: Rectangle, left_percent: f32, padding := 0.0f) -> (left: Rectangle, right: Rectangle) {
+            return split_vertical(r, left_width=left_percent * Rectangle.width(r), padding=padding);
         },
 
-        (r: Rectangle, right_percent: f32) -> (left: Rectangle, right: Rectangle) {
-            return split_vertical(r, right_width=right_percent * Rectangle.width(r));
+        (r: Rectangle, right_percent: f32, padding := 0.0f) -> (left: Rectangle, right: Rectangle) {
+            return split_vertical(r, right_width=right_percent * Rectangle.width(r), padding=padding);
         },
 
-        (r: Rectangle, left_width: f32) -> (left: Rectangle, right: Rectangle) {
+        (r: Rectangle, left_width: f32, padding := 0.0f) -> (left: Rectangle, right: Rectangle) {
             x0, y0 := Rectangle.top_left(r);
             x1, y1 := Rectangle.bottom_right(r);
 
             return .{ x0=x0, x1=x0+left_width, y0=y0, y1=y1 },
-                   .{ x0=x0+left_width, x1=x1, y0=y0, y1=y1 };
+                   .{ x0=x0+left_width+padding, x1=x1, y0=y0, y1=y1 };
         },
 
-        (r: Rectangle, right_width: f32) -> (left: Rectangle, right: Rectangle) {
+        (r: Rectangle, right_width: f32, padding := 0.0f) -> (left: Rectangle, right: Rectangle) {
             x0, y0 := Rectangle.top_left(r);
             x1, y1 := Rectangle.bottom_right(r);
 
-            return .{ x0=x0, x1=x1-right_width, y0=y0, y1=y1 },
+            return .{ x0=x0, x1=x1-right_width-padding, y0=y0, y1=y1 },
                    .{ x0=x1-right_width, x1=x1, y0=y0, y1=y1 };
         }
     }
 
 
     split_horizontal :: proc {
-        (r: Rectangle, top_percent: f32) -> (top: Rectangle, bottom: Rectangle) {
-            return split_horizontal(r, top_height=top_percent * Rectangle.height(r));
+        (r: Rectangle, top_percent: f32, padding := 0.0f) -> (top: Rectangle, bottom: Rectangle) {
+            return split_horizontal(r, top_height=top_percent * Rectangle.height(r), padding=padding);
         },
 
-        (r: Rectangle, bottom_percent: f32) -> (top: Rectangle, bottom: Rectangle) {
-            return split_horizontal(r, bottom_height=bottom_percent * Rectangle.height(r));
+        (r: Rectangle, bottom_percent: f32, padding := 0.0f) -> (top: Rectangle, bottom: Rectangle) {
+            return split_horizontal(r, bottom_height=bottom_percent * Rectangle.height(r), padding=padding);
         },
 
-        (r: Rectangle, top_height: f32) -> (top: Rectangle, bottom: Rectangle) {
+        (r: Rectangle, top_height: f32, padding := 0.0f) -> (top: Rectangle, bottom: Rectangle) {
             x0, y0 := Rectangle.top_left(r);
             x1, y1 := Rectangle.bottom_right(r);
 
             return .{ x0=x0, x1=x1, y0=y0, y1=y0+top_height },
-                   .{ x0=x0, x1=x1, y0=y0+top_height, y1=y1 };
+                   .{ x0=x0, x1=x1, y0=y0+top_height+padding, y1=y1 };
         },
 
-        (r: Rectangle, bottom_height: f32) -> (top: Rectangle, bottom: Rectangle) {
+        (r: Rectangle, bottom_height: f32, padding := 0.0f) -> (top: Rectangle, bottom: Rectangle) {
             x0, y0 := Rectangle.top_left(r);
             x1, y1 := Rectangle.bottom_right(r);
 
-            return .{ x0=x0, x1=x1, y0=y0, y1=y1-bottom_height },
+            return .{ x0=x0, x1=x1, y0=y0, y1=y1-bottom_height-padding },
                    .{ x0=x0, x1=x1, y0=y1-bottom_height, y1=y1 };
         }
     }
index 834d1edd333949f27afeba3166ca7df6f4f047d2..82cf6c92fa6b9a0fd684bd3b364d64abd5f1731d 100644 (file)
@@ -17,7 +17,14 @@ user interfaces in WebGL (and OpenGL when Onyx compiles to C).
 @Rename
 package ui
 
-use package immediate_mode // The immediate_mode module needs to be accessible
-
 #load "./ui"
-#load "./flow"
\ No newline at end of file
+#load "./flow"
+#load "./components/button"
+
+
+// Package inclusions that are part of all files in the "ui" package.
+#private gfx    :: package immediate_mode    // The immediate_mode module needs to be accessible
+#private gl     :: package gl
+#private bmfont :: package bmfont
+
+#private math   :: package core.math
diff --git a/modules/ui/resources/fonts/FiraCode.data b/modules/ui/resources/fonts/FiraCode.data
new file mode 100644 (file)
index 0000000..f80e70e
Binary files /dev/null and b/modules/ui/resources/fonts/FiraCode.data differ
diff --git a/modules/ui/resources/fonts/FiraCode.fnt b/modules/ui/resources/fonts/FiraCode.fnt
new file mode 100644 (file)
index 0000000..3c7f187
--- /dev/null
@@ -0,0 +1,102 @@
+info face="Fira Code Retina" size=32 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=-2,-2
+common lineHeight=40 base=30 scaleW=256 scaleH=256 pages=1 packed=0
+page id=0 file="FiraCode.png"
+chars count=97
+char id=0       x=109  y=42   width=20   height=28   xoffset=0    yoffset=5    xadvance=20   page=0    chnl=0 
+char id=10      x=0    y=0    width=0    height=0    xoffset=-1   yoffset=0    xadvance=0    page=0    chnl=0 
+char id=32      x=0    y=0    width=0    height=0    xoffset=-1   yoffset=0    xadvance=20   page=0    chnl=0 
+char id=33      x=161  y=122  width=8    height=25   xoffset=6    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=34      x=57   y=167  width=12   height=11   xoffset=4    yoffset=4    xadvance=20   page=0    chnl=0 
+char id=35      x=0    y=0    width=0    height=0    xoffset=0    yoffset=0    xadvance=20   page=0    chnl=0 
+char id=36      x=6    y=0    width=18   height=36   xoffset=1    yoffset=0    xadvance=20   page=0    chnl=0 
+char id=37      x=25   y=71   width=22   height=26   xoffset=-1   yoffset=6    xadvance=20   page=0    chnl=0 
+char id=38      x=235  y=122  width=20   height=25   xoffset=0    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=39      x=248  y=71   width=6    height=11   xoffset=7    yoffset=4    xadvance=20   page=0    chnl=0 
+char id=40      x=38   y=0    width=14   height=35   xoffset=2    yoffset=1    xadvance=20   page=0    chnl=0 
+char id=41      x=52   y=0    width=13   height=35   xoffset=4    yoffset=1    xadvance=20   page=0    chnl=0 
+char id=42      x=0    y=167  width=20   height=19   xoffset=0    yoffset=10   xadvance=20   page=0    chnl=0 
+char id=43      x=20   y=167  width=19   height=18   xoffset=0    yoffset=11   xadvance=20   page=0    chnl=0 
+char id=44      x=243  y=147  width=9    height=14   xoffset=5    yoffset=23   xadvance=20   page=0    chnl=0 
+char id=45      x=125  y=167  width=16   height=5    xoffset=2    yoffset=17   xadvance=20   page=0    chnl=0 
+char id=46      x=96   y=167  width=9    height=8    xoffset=5    yoffset=23   xadvance=20   page=0    chnl=0 
+char id=47      x=116  y=0    width=20   height=33   xoffset=0    yoffset=2    xadvance=20   page=0    chnl=0 
+char id=48      x=143  y=122  width=18   height=25   xoffset=1    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=49      x=36   y=122  width=18   height=25   xoffset=1    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=50      x=54   y=122  width=18   height=25   xoffset=0    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=51      x=72   y=122  width=18   height=25   xoffset=0    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=52      x=234  y=42   width=18   height=26   xoffset=1    yoffset=5    xadvance=20   page=0    chnl=0 
+char id=53      x=90   y=122  width=17   height=25   xoffset=1    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=54      x=107  y=122  width=18   height=25   xoffset=1    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=55      x=0    y=71   width=16   height=26   xoffset=2    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=56      x=125  y=122  width=18   height=25   xoffset=1    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=57      x=216  y=42   width=18   height=27   xoffset=1    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=58      x=235  y=147  width=8    height=20   xoffset=6    yoffset=9    xadvance=20   page=0    chnl=0 
+char id=59      x=16   y=71   width=9    height=26   xoffset=5    yoffset=11   xadvance=20   page=0    chnl=0 
+char id=60      x=0    y=0    width=0    height=0    xoffset=1    yoffset=0    xadvance=20   page=0    chnl=0 
+char id=61      x=69   y=167  width=16   height=11   xoffset=2    yoffset=14   xadvance=20   page=0    chnl=0 
+char id=62      x=186  y=122  width=49   height=25   xoffset=1    yoffset=7    xadvance=20   page=0    chnl=0 
+char id=63      x=169  y=122  width=17   height=25   xoffset=1    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=64      x=0    y=42   width=22   height=29   xoffset=-1   yoffset=6    xadvance=20   page=0    chnl=0 
+char id=65      x=47   y=71   width=22   height=25   xoffset=-1   yoffset=6    xadvance=20   page=0    chnl=0 
+char id=66      x=69   y=71   width=18   height=25   xoffset=1    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=67      x=87   y=71   width=20   height=25   xoffset=0    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=68      x=107  y=71   width=19   height=25   xoffset=1    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=69      x=126  y=71   width=17   height=25   xoffset=2    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=70      x=143  y=71   width=16   height=25   xoffset=3    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=71      x=159  y=71   width=19   height=25   xoffset=0    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=72      x=178  y=71   width=18   height=25   xoffset=1    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=73      x=196  y=71   width=16   height=25   xoffset=2    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=74      x=212  y=71   width=17   height=25   xoffset=1    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=75      x=229  y=71   width=19   height=25   xoffset=2    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=76      x=0    y=97   width=16   height=25   xoffset=3    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=77      x=16   y=97   width=21   height=25   xoffset=-1   yoffset=6    xadvance=20   page=0    chnl=0 
+char id=78      x=37   y=97   width=18   height=25   xoffset=1    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=79      x=55   y=97   width=20   height=25   xoffset=0    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=80      x=75   y=97   width=18   height=25   xoffset=2    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=81      x=156  y=0    width=21   height=31   xoffset=0    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=82      x=93   y=97   width=18   height=25   xoffset=2    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=83      x=111  y=97   width=19   height=25   xoffset=0    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=84      x=130  y=97   width=20   height=25   xoffset=0    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=85      x=150  y=97   width=18   height=25   xoffset=1    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=86      x=168  y=97   width=22   height=25   xoffset=-1   yoffset=6    xadvance=20   page=0    chnl=0 
+char id=87      x=190  y=97   width=22   height=25   xoffset=-1   yoffset=6    xadvance=20   page=0    chnl=0 
+char id=88      x=212  y=97   width=21   height=25   xoffset=-1   yoffset=6    xadvance=20   page=0    chnl=0 
+char id=89      x=233  y=97   width=22   height=25   xoffset=-1   yoffset=6    xadvance=20   page=0    chnl=0 
+char id=90      x=0    y=122  width=18   height=25   xoffset=1    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=91      x=93   y=0    width=12   height=33   xoffset=4    yoffset=2    xadvance=20   page=0    chnl=0 
+char id=92      x=136  y=0    width=20   height=33   xoffset=0    yoffset=2    xadvance=20   page=0    chnl=0 
+char id=93      x=105  y=0    width=11   height=33   xoffset=4    yoffset=2    xadvance=20   page=0    chnl=0 
+char id=94      x=39   y=167  width=18   height=13   xoffset=1    yoffset=0    xadvance=20   page=0    chnl=0 
+char id=95      x=177  y=0    width=54   height=31   xoffset=0    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=96      x=85   y=167  width=11   height=9    xoffset=4    yoffset=2    xadvance=20   page=0    chnl=0 
+char id=97      x=0    y=147  width=18   height=20   xoffset=1    yoffset=11   xadvance=20   page=0    chnl=0 
+char id=98      x=22   y=42   width=17   height=28   xoffset=2    yoffset=3    xadvance=20   page=0    chnl=0 
+char id=99      x=18   y=147  width=18   height=20   xoffset=1    yoffset=11   xadvance=20   page=0    chnl=0 
+char id=100     x=39   y=42   width=17   height=28   xoffset=1    yoffset=3    xadvance=20   page=0    chnl=0 
+char id=101     x=36   y=147  width=18   height=20   xoffset=1    yoffset=11   xadvance=20   page=0    chnl=0 
+char id=102     x=129  y=42   width=19   height=27   xoffset=1    yoffset=4    xadvance=20   page=0    chnl=0 
+char id=103     x=231  y=0    width=20   height=29   xoffset=0    yoffset=9    xadvance=20   page=0    chnl=0 
+char id=104     x=148  y=42   width=16   height=27   xoffset=2    yoffset=4    xadvance=20   page=0    chnl=0 
+char id=105     x=56   y=42   width=16   height=28   xoffset=2    yoffset=3    xadvance=20   page=0    chnl=0 
+char id=106     x=24   y=0    width=14   height=35   xoffset=2    yoffset=3    xadvance=20   page=0    chnl=0 
+char id=107     x=72   y=42   width=18   height=28   xoffset=2    yoffset=3    xadvance=20   page=0    chnl=0 
+char id=108     x=164  y=42   width=18   height=27   xoffset=0    yoffset=4    xadvance=20   page=0    chnl=0 
+char id=109     x=54   y=147  width=20   height=20   xoffset=0    yoffset=11   xadvance=20   page=0    chnl=0 
+char id=110     x=74   y=147  width=16   height=20   xoffset=2    yoffset=11   xadvance=20   page=0    chnl=0 
+char id=111     x=90   y=147  width=18   height=20   xoffset=1    yoffset=11   xadvance=20   page=0    chnl=0 
+char id=112     x=182  y=42   width=17   height=27   xoffset=2    yoffset=11   xadvance=20   page=0    chnl=0 
+char id=113     x=199  y=42   width=17   height=27   xoffset=1    yoffset=11   xadvance=20   page=0    chnl=0 
+char id=114     x=108  y=147  width=17   height=20   xoffset=2    yoffset=11   xadvance=20   page=0    chnl=0 
+char id=115     x=125  y=147  width=17   height=20   xoffset=1    yoffset=11   xadvance=20   page=0    chnl=0 
+char id=116     x=18   y=122  width=18   height=25   xoffset=1    yoffset=6    xadvance=20   page=0    chnl=0 
+char id=117     x=142  y=147  width=16   height=20   xoffset=2    yoffset=11   xadvance=20   page=0    chnl=0 
+char id=118     x=158  y=147  width=19   height=20   xoffset=0    yoffset=11   xadvance=20   page=0    chnl=0 
+char id=119     x=177  y=147  width=22   height=20   xoffset=-1   yoffset=11   xadvance=20   page=0    chnl=0 
+char id=120     x=199  y=147  width=20   height=20   xoffset=0    yoffset=11   xadvance=20   page=0    chnl=0 
+char id=121     x=90   y=42   width=19   height=28   xoffset=0    yoffset=11   xadvance=20   page=0    chnl=0 
+char id=122     x=219  y=147  width=16   height=20   xoffset=2    yoffset=11   xadvance=20   page=0    chnl=0 
+char id=123     x=65   y=0    width=14   height=35   xoffset=2    yoffset=1    xadvance=20   page=0    chnl=0 
+char id=124     x=0    y=0    width=6    height=42   xoffset=7    yoffset=-1   xadvance=20   page=0    chnl=0 
+char id=125     x=79   y=0    width=14   height=35   xoffset=3    yoffset=1    xadvance=20   page=0    chnl=0 
+char id=126     x=105  y=167  width=20   height=8    xoffset=0    yoffset=15   xadvance=20   page=0    chnl=0 
+kernings count=0
diff --git a/modules/ui/resources/fonts/FiraCode.png b/modules/ui/resources/fonts/FiraCode.png
new file mode 100644 (file)
index 0000000..4d7743f
Binary files /dev/null and b/modules/ui/resources/fonts/FiraCode.png differ
index 285600d0ced61439a211e8ebbae7c2c84794797c..48e4cd2dd10e63d4e959939d96c650adc2a217f5 100644 (file)
@@ -1,15 +1,14 @@
 package ui
 
-#private_file gfx :: package immediate_mode
-#private_file bitmap_font :: package bitmap_font
-#private_file gl :: package gl
-#private_file math :: package core.math
+use package core
 
-#private font : bitmap_font.Bitmap_Font;
+@Cleanup // Move these to the theme?
+// Or create a cache of fonts and put pointers/string in the themes?
+#private font : bmfont.BMFont;
 #private font_texture : gl.GLTexture;
 
 @Temporary
-DEFAULT_TEXT_SIZE :: 32.0f
+DEFAULT_TEXT_SIZE :: 40.0f
 
 
 UI_Id :: #type u32
@@ -34,6 +33,8 @@ mouse_state: MouseState = MouseState.{};
 
 init_ui :: () {
     init_font();
+
+    map.init(^button_states, default=.{});
 }
 
 clear_buttons :: () {
@@ -99,33 +100,41 @@ is_hot_item :: (id: UI_Id) -> bool {
     return hot_item == id;
 }
 
-// 'x' and 'y' are the top-left coordinates of the text. 'y' is NOT the baseline.
-draw_text_raw :: (text: str, x: f32, y: f32, size := DEFAULT_TEXT_SIZE, color := gfx.Color4.{1,1,1}) {
+draw_text_raw :: (text: str, x: f32, y: f32, line_height := DEFAULT_TEXT_SIZE, color := gfx.Color4.{1,1,1}) {
     gl.activeTexture(gl.TEXTURE0);
     gl.bindTexture(gl.TEXTURE_2D, font_texture);
     gfx.set_texture(0);
 
+    original_x := x;
+    baseline := cast(f32) font.common.baseline;
+
     for char: text {
+        if char == #char "\n" {
+            y += line_height + .5;
+            x = original_x;
+            continue;
+        }
+
         glyph := font->get_glyph(char);
         
         if glyph == null {
-            glyph = font->get_glyph(255);
+            glyph = font->get_glyph(0);
             assert(glyph != null, "NO NULL GLYPH");
         }
 
         // Round to the nearest pixel
-        tx, ty := math.floor(x + .5), math.floor(y + .5);
-        w      := math.floor(glyph.w * size * font.em + .5);
-        h      := math.floor(glyph.h * size * font.em + .5);
+        tx, ty := math.floor(x + ~~glyph.xoffset + .5), math.floor(y + ~~glyph.yoffset + baseline + .5);
+        w      := math.floor(cast(f32) glyph.w + .5);
+        h      := math.floor(cast(f32) glyph.h + .5);
 
         gfx.textured_rect(
             .{ tx, ty },
             .{ w, h },
-            .{ glyph.x0, glyph.y0 },
-            .{ glyph.x1 - glyph.x0, glyph.y1 - glyph.y0 },
+            .{ glyph.tex_x, glyph.tex_y },
+            .{ glyph.tex_w, glyph.tex_h },
             color = color);
 
-        x += glyph.w * size * font.em;
+        x += ~~glyph.xadvance;
     }
     
     gfx.flush();
@@ -148,59 +157,9 @@ draw_rect :: proc {
 
 @Themeing
 draw_text :: (use r: Rectangle, text: str, theme := ^default_text_theme, site := #callsite) -> bool {
-    height := Rectangle.height(r);
-    draw_text_raw(text, x0, y0, theme.font_size * height, theme.text_color);
+    draw_text_raw(text, x0, y0, theme.font_size, theme.text_color);
 }
 
-@Themeing
-draw_button :: (use r: Rectangle, text: str, theme := ^default_button_theme, site := #callsite) -> bool {
-    gfx.set_texture();
-
-    result := false;
-
-    hash := get_site_hash(site);
-    if is_active_item(hash) {
-        if mouse_state.left_button_just_up {
-            if is_hot_item(hash) && Rectangle.contains(r, mouse_state.x, mouse_state.y) {
-                result = true;
-            }
-
-            set_active_item(0);
-        }
-
-    } elseif is_hot_item(hash) {
-        if mouse_state.left_button_down {
-            set_active_item(hash);
-        }
-    }
-
-    if Rectangle.contains(r, mouse_state.x, mouse_state.y) {
-        set_hot_item(hash);
-    }
-
-    border_width  := theme.border_width;
-    width, height := Rectangle.dimensions(r);
-
-    gfx.rect(.{ x0, y0 }, .{ width, height }, theme.border_color);
-
-    @Lerp
-    if is_hot_item(hash) {
-        gfx.rect(.{ x0 + border_width, y0 + border_width }, .{ width - border_width * 2, height - border_width * 2 }, theme.hover_color);
-
-    } else {
-        gfx.rect(.{ x0 + border_width, y0 + border_width }, .{ width - border_width * 2, height - border_width * 2 }, theme.background_color);
-    }
-
-    font_size := height * theme.font_size;
-    text_width := font->get_width(text, font_size);
-    text_height := font->get_height(text, font_size);
-
-    draw_text_raw(text, x0 + (width - text_width) / 2, y0 + (height - text_height) / 2, font_size, theme.text_color);
-    return result;
-}
-
-
-
 Rectangle :: struct {
     //
     // x0,y0 ------------+
@@ -236,26 +195,9 @@ Rectangle :: struct {
 @Relocate
 Text_Theme :: struct {
     text_color := gfx.Color4.{ 1, 1, 1 };
-    font_size  := .4f; // Percentage of height of button
-}
-
-@Relocate
-Button_Theme :: struct {
-    @Bug
-    // I accidentally left this as 'text_color', and there was not a compiler error, even through there
-    // should have been. Instead it looked like the wrong offset was being used for the members and that
-    // was affecting the generated code.
-    use text_theme := Text_Theme.{};
-
-    background_color := gfx.Color4.{ 0.1, 0.1, 0.1 };
-    hover_color      := gfx.Color4.{ 0.2, 0.2, 0.2 };
-
-    border_color := gfx.Color4.{ 0.2, 0.2, 0.2 };
-    border_width := 10.0f; @InPixels
+    font_size  := 18.0f;
 }
 
-@Bug // there is a compile-time known bug if either of the 'Button_Theme's below are omitted.
-default_button_theme: Button_Theme = Button_Theme.{};
 default_text_theme:   Text_Theme   = Text_Theme.{};
 
 
@@ -264,38 +206,55 @@ default_text_theme:   Text_Theme   = Text_Theme.{};
 
 
 // Utilities
-get_site_hash :: (site: CallSite) -> u32 {
+get_site_hash :: (site: CallSite, increment := 0) -> UI_Id {
     hash :: package core.hash
 
     file_hash   := hash.to_u32(site.file);
     line_hash   := hash.to_u32(site.line);
     column_hash := hash.to_u32(site.column);
 
-    return file_hash * 0x472839 + line_hash * 0x6849210 + column_hash * 0x1248382;
+    return file_hash * 0x472839 + line_hash * 0x6849210 + column_hash * 0x1248382 + increment;
 }
 
 get_text_width :: (text: str, size := DEFAULT_TEXT_SIZE) -> f32 {
-    return font->get_width(text, size);
+    @Cleanup
+    return 0; // font->get_width(text, size);
 }
 
 
 #private init_font :: () {
-    font_data := #file_contents "./resources/font_2.data";
+    fnt_file_data := #file_contents "./resources/fonts/FiraCode.fnt";
+    texture_data := #file_contents "./resources/fonts/FiraCode.data";
 
-    bft := bitmap_font.Bitmap_Font_Texture.{
-        data = font_data,
-        width = 256,
-        height = 256,
-    };
+    font = bmfont.load_bmfont(fnt_file_data);
 
-    font = bitmap_font.bitmap_font_create(bft, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 \xff:");
+    tex_width, tex_height := font.common.scale_width, font.common.scale_height;
 
     font_texture = gl.createTexture();
     gl.bindTexture(gl.TEXTURE_2D, font_texture);
-    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 256, 0, gl.RGBA, gl.UNSIGNED_BYTE, font_data);
+    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, tex_width, tex_height, 0, gl.RGBA, gl.UNSIGNED_BYTE, texture_data);
     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
     gl.bindTexture(gl.TEXTURE_2D, -1);
 }
+
+
+
+
+
+#private move_towards :: (value: ^$T, target: T, step: T) {
+    if *value < target do *value += step;
+    if *value > target do *value -= step;
+    if *value > target - step && *value < target + step do *value = target;
+}
+
+#private color_lerp :: (t: f32, c1: gfx.Color4, c2: gfx.Color4) -> gfx.Color4 {
+    return .{
+        r = c1.r * (1 - t) + c2.r * t,
+        g = c1.g * (1 - t) + c2.g * t,
+        b = c1.b * (1 - t) + c2.b * t,
+        a = c1.a + c2.a,
+    };
+}
\ No newline at end of file