From: Brendan Hansen Date: Wed, 9 Jun 2021 00:53:18 +0000 (-0500) Subject: pushing out a lot of module changes X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=8240d533100b35170528b862c89543f61d1b9f1e;p=onyx.git pushing out a lot of module changes --- diff --git a/core/container/array.onyx b/core/container/array.onyx index 4eb21681..bfc16120 100644 --- a/core/container/array.onyx +++ b/core/container/array.onyx @@ -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 index 00000000..014144df --- /dev/null +++ b/modules/bmfont/bmfont_loader.onyx @@ -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 index 00000000..b0f20b2b --- /dev/null +++ b/modules/bmfont/module.onyx @@ -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 index 00000000..03456c4b --- /dev/null +++ b/modules/bmfont/types.onyx @@ -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 index 00000000..7f14ccc7 --- /dev/null +++ b/modules/ttf/binary_reader.onyx @@ -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 index 00000000..67c95d50 --- /dev/null +++ b/modules/ttf/module.onyx @@ -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 index 00000000..819952e2 --- /dev/null +++ b/modules/ttf/ttf.onyx @@ -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 index 00000000..c2923805 --- /dev/null +++ b/modules/ui/components/button.onyx @@ -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; +} diff --git a/modules/ui/flow.onyx b/modules/ui/flow.onyx index 64f28692..3c389455 100644 --- a/modules/ui/flow.onyx +++ b/modules/ui/flow.onyx @@ -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 }; } } diff --git a/modules/ui/module.onyx b/modules/ui/module.onyx index 834d1edd..82cf6c92 100644 --- a/modules/ui/module.onyx +++ b/modules/ui/module.onyx @@ -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 index 00000000..f80e70e4 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 index 00000000..3c7f1870 --- /dev/null +++ b/modules/ui/resources/fonts/FiraCode.fnt @@ -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 index 00000000..4d7743f2 Binary files /dev/null and b/modules/ui/resources/fonts/FiraCode.png differ diff --git a/modules/ui/ui.onyx b/modules/ui/ui.onyx index 285600d0..48e4cd2d 100644 --- a/modules/ui/ui.onyx +++ b/modules/ui/ui.onyx @@ -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