}
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 {
--- /dev/null
+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);
+ }
+}
--- /dev/null
+/*
+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"
+
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+@Incomplete
+// This whole module is very imcomplete
+
+package ttf
+
+#load "./ttf"
+#load "./binary_reader"
\ No newline at end of file
--- /dev/null
+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]);
+}
--- /dev/null
+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;
+}
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 };
}
}
@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
--- /dev/null
+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
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
init_ui :: () {
init_font();
+
+ map.init(^button_states, default=.{});
}
clear_buttons :: () {
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();
@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 ------------+
@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.{};
// 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