From 8240d533100b35170528b862c89543f61d1b9f1e Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Tue, 8 Jun 2021 19:53:18 -0500 Subject: [PATCH] pushing out a lot of module changes --- core/container/array.onyx | 14 +- modules/bmfont/bmfont_loader.onyx | 138 ++++++ modules/bmfont/module.onyx | 19 + modules/bmfont/types.onyx | 81 ++++ modules/ttf/binary_reader.onyx | 73 ++++ modules/ttf/module.onyx | 7 + modules/ttf/ttf.onyx | 531 +++++++++++++++++++++++ modules/ui/components/button.onyx | 85 ++++ modules/ui/flow.onyx | 32 +- modules/ui/module.onyx | 13 +- modules/ui/resources/fonts/FiraCode.data | Bin 0 -> 262144 bytes modules/ui/resources/fonts/FiraCode.fnt | 102 +++++ modules/ui/resources/fonts/FiraCode.png | Bin 0 -> 20128 bytes modules/ui/ui.onyx | 149 +++---- 14 files changed, 1124 insertions(+), 120 deletions(-) create mode 100644 modules/bmfont/bmfont_loader.onyx create mode 100644 modules/bmfont/module.onyx create mode 100644 modules/bmfont/types.onyx create mode 100644 modules/ttf/binary_reader.onyx create mode 100644 modules/ttf/module.onyx create mode 100644 modules/ttf/ttf.onyx create mode 100644 modules/ui/components/button.onyx create mode 100644 modules/ui/resources/fonts/FiraCode.data create mode 100644 modules/ui/resources/fonts/FiraCode.fnt create mode 100644 modules/ui/resources/fonts/FiraCode.png 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 0000000000000000000000000000000000000000..f80e70e41b99bc8d210e31bbdd56ec9be313663f GIT binary patch literal 262144 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 V1SB8<2}nQ!5|DrdByjl&`~$#8`j-Fz literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4d7743f2d9e69cd946ef804f14fb0c48d144a132 GIT binary patch literal 20128 zcmYIvcRZV2`1hRv&`VtGk6VoH;@MrW^P54^JzZ?v2%BcEJ!m4sUb zMbt#XMl&)6d%ocgnMcpA_rkI{k-r#oYi=xeu1aygIec@O(L|SaZK8hH0)LVWzsIe! zrb_45T+QNs7vX;I8V0WjZ-mM{nTwJ$Wm^0#7oJ{S@%Qf86FCX*iT&pNu&bL;6*i3F-&GFs#{PEiA`l%v8MO}8<rd zN?+=nI8zXcAk|%kyh9*obRe{x!cwXE!dh>hp)>CwV zg;vL@tkvUVxp~_Hv}zgK%VZp3%ENf(OZMEsYmQbv;zLoKb5z;8vyL7Px2TFY+&8I{ zu8Oz|SoN8|Uf^B;Awqd~*S8H^54Ozk@tRkJ(}>hhLs<(IjQuZ2?6TZfr>%!s{ie zwmQEv*DMGgS*nJKek1pGj46ELnNwLa}ORYUO;gb z_FbMOj()uqxIu)A8fk5r}ecIhcx1wf3H}wkN_pOgNEQ<=SBPFk& zA?!i0s;wb52nz&rOZa)$0|7wlyxp0{l(N5dN}Yprd{=SpkonD{FV18OMF3*JJbTnP zgc=RYJxRDm9W;y1ZgOQ;2YwC!E&bWkNvH!+k~b*x zTEtE7r+`D4*Pr%4v(y?&>;UY}C%>j74PkNwYxpXn_$Da#y8G%SY1*&YB6>ji2W;ni z&_kNV#`0;~$wU>kj=))%s^wDeC%Y?#iQBuH0MzA1W$jR8>5&lN%@#4MrPz<-9hN3R zc^A>NM>#*m$z&d_Kh=kS$31V&)qkTeKWZo&6#1{OXpne{)C`irEa%iRk>6C! zhm165|4hrb_Z%M{DEeVD39zFz%6%7vf3HlK^xVNhU<3wi_ifh8^y`x{C%g~1=A<(M z!~&jOOoM3$RKsU$%tt)%qYpd4dt&(oB;KT6hc#t=*VeSp4aGieCHI?dQ#*`|XNc#w zimBT6dGATu50|y_-XU30xbVJs$?Hb~fL&VxTQYC%Xe|s+3Id`(u#J)4g~Py!biYTGROR=(I1y%gw{5Im&=!p z{pn_eX-$POAUqb5<{A_(!IyMzzw`S??;N@F!sJgz<;6m$r!$B@uZv+T84AV7-I6HR z#IOGLuP+EOA8Kh$(%w$L^f7&DnGV5dR}+|Sm$kUF0VZ%OyhB(U085{ot{7>a^IN%+ z!+T&xtn|4J9YMKQ|9m%B@JknZ>H40-XkuL@rQHFxASe#{wfp#0kS2&73Y7Skwx*7N z9{l%W1d+;4X0v+hEiVb{sFsF0?Ng{rqgs*Nf657?p1r2ZQ{Aa&p5qpGFHy@U8va(T z1Vsc8*xXLpH62!XL9k@G4aBZHsQ8Rv5Ry%Rl%lD!yPo#=!> zXDsw8Ko)(=QZm%TOWt4@Q;)ER#WNaWhw-f5W`SR@U!{*>)-7!k}2FQ9?I;`zQtV*ep!0J6`0-A$_U(uvlZ@4>aVwmKF2^6A`Y04l+H zKm=}}bsq%TQ`&-VbcveM^CG2m?~(gz4%sEpZ}|tNHibAH&u1GXu!cvLv$)6*(i=f{C-NmxCWU zXPHNe2c;~$>0Qtke=cX01Bc- zIDG5V=;Zj(^xGb|_-zUzWREL>g+qJyLC}$|8nOo1zKx!C|EZsR!se^NaAF@RW^-Z$ zZn9lt+g7r%7a?$yOr^q1`|qQV7;_`7Xl!OP$ghsrVEoH{`+)e77X!fhCN3dv z*T<3pK(EeN_I6J3wcOZVlR&B~9)7icwFM4W;5j*Kqo^MJo4#?g9p5-?6%*d-Oxv>= zh3q2QdKvy)3`h0u-ag|gv(DnwXSmij-Q2wkQB`B$B3ZYZmFiZP)Q1(&46Prv{4eQg z4Cl@^iG^zRFh4WqSN-EIa=E1FGItZ=;alRi>zACRLTHgj!Lqf)s4dAqE6Ji>^A+8v zqT&gUhKs$pXYUxi34dSxysdt=fqgFs1aqpAWqt9p>Z89lKjAA-zb{H9Of5*{bMy>y z2S#~Cz3K_wyCa)RHQ6nATehUFI~WzT7_Mgn`Ku5RQ*xt>^Mxy{m_|_v70d;^Qc&nM z9uotcHpY_2gH<&{a|}ZC;L+bh~b)Hbaxu zqGHT+x8dcjr%Y&24{@>B#OjYecGFnc-z$Zm-)Nhn^*}JVgA$LJw2v>%Ek%8RrazC` z;@k-pFtT^Yw<8f4)0@BIH15!E_Y{#?#Vyrb>k%wt$c^vl+}>EKy3PgM6rI}3f&8ss zdvfIxe5M_7jA`}ftK9vg+4Ef&3? zRiFVK@LT@|=gcn_IurGl&7&L5b&Wq$yU#=(_fxmlag0XE&ZXDBTf41Vbi+wCF;K(( zp~J}bHwOHT>PNYtsWp8+wLg*j%LK{*F()ZZC;r7Smc(1#PO=%X%Y+=ES}MBE?S8pz zX`=ISlidRt^=?2vK&}&FT{hpG_9KhKen{1k(rHH_zMBvA=;lFKVxzoCVAy!E#^rXX zZB#cIYC@ZonjkkgReF@4ek+NdC$@$tI07w$IOz$AF({Ex_);XC?2#+ylTO(wl=d|P z4?E-$rHdTVL86Zy3!D#UvAq%i!Y7<_OS8et1BnUzvhPibwla;c-s(Ohgi%^rHRNQ6 zW{$)Lz8`V_RTI*QoZHzJW9PGbb7q)I>znII1}A1AlKPgcvKrn1oIV^8yLO_odlsn) zltq7GT=0-*#2<`ko6EnSxH`U)uAWQk--1SouyA4-c&X&As!V$%5AYMvgjI0q=Sr^go4+T%RZJBdNRFVMwaEh(!Ehbl*$O@0d zQ?41uX+gDaAU}Uqa@d&VI~ajG(l^B^mTLHClGV=3omN!SEH9Z)y(Yv@Yh#$YUXuZhaTD2Xb9>vbVmeTlO2o7004mMvwwO56k8gK@YZFo762aV2w!7i! z6MD%_=?9K--BQ#Sp&zf8UQuX8M;T8F}h>9z%pZ& zy{4rqjMwC$@VrRPENgan<_J@-&ij4Y_Ur|4?K}=)PUMZL3^4Yiaw?TkVJtbP`EgP? zYd=N<7-lAl^(yPmB9LYmRc+)0l>|E8t*Z}b6$lMV{Tv(h9BDpy*ETSnkRwX|iECL- z7&0_LF??q7@JPd`5lp@5c#%HICWX&di58_!+4}3g(H@1TGm_?%D7I^T_ttvOcOi|< z7@kbMW(MP1QEVO6K$*D^;du05lv~??)=`{{tue3F)6(@wv>9_kxC;K;}Pk_M+LT2TP(wj%rv-)Qj02r zNa3B!4bm)V7e4cilu@>XtJTTpAvrYbB#hCY$K_Rs*hYoFJU3G#S3B|7dTnPu%^!nj zzJJ0JQUj|MD}`l@_q4#k{nL30?rE46wRqm}p#1OecV;i~YxXqJEPCWhMiV9p0Sc0a zHzZv&cfKxR3C7Y1i`}} zRN(;@?kZjjr+l>Q&m7cvV?S|}XFSoEho;UIiX)R6o-}?s`j0~Ex;4mavdgRg-r5MF znGeX--^q15-&E4E7u3nPQ4F3TPwB|tc4N7d5u4HIa*!(h@mZ$e3A^jBU@dIJkE4&iJQ+~LPn zL{amt{&5EE@t*LE5aXBI#Tl*=r`8ba8;@Lnn(Y%pxt6uR>+_xYHh4)Lrh&IL8j7`# z7`_IsZ`2;_mxO^NK;{Fg#vS!g3{f{ZF?Gzkhk#Yf{)Ov5>IBaSgbBxQ3V5fDB7#Bc z{)mF-?Dx6<3jzOC9B(ZebNROK}dY+wYz<6+v&l|w$oWnJI&5}vah%TfZVQHxxj1pC<%(*V?3r1*g?@*&PS zet??LTY^}C8JdEheH#)268{iM{P&K^r|7$Jy3^uG)_sZNoo`4MbwGQ1`}H3N1ze&D^c0)KCGwjMhQm6}52}MQOj~MJ)l& z{vVvc@UNRZI5L;`#z9@yZOZPC0v1X-`_xx7-Cm!+Rjhi#-vh{axBpCzK7cnji@Nn% z3g<$>+z(}U{v?z;NhKh*g?8qg#>f}~W6V`YPt;E`vsW*zUxm=3MS=AL-`ZK1*EnOZ zBmjz59Z^+x9_02L@+0_gJpQV_*zmVVPmjeFJEzX+3S9GLSIhZ?AaLx{#nO6xw=@e$ zw2BekjKLIsrVs0mGt(cy4oZ71roNdBV@lrN@d+XKqf&IE2MC)S1|ShHv6}OS$9Im& zk2~>QH+Fc}73uMHEO7cApaX|#Qkfh3Omk%I(`2^!N$~6c{GcC#vvu|_OXIs#5dpW6 zrJ_eF$bo35wYO{Rs8B?!VyQw>dx_{GA9{|ws=9PzG0PE7R70|p7xzK#2_<&x%u||Q z_P?TIX$ugHPr+9o_*UxkCia2!&=AD1lviWQXKBsYQv= z{w!5a&zPfAnl3#2qxnc`S9;MlOG|>GAo6Vp)rzEWnF(Px+%T{a3bpNyGdnnAsh0BoCOTQa+SHS0-Fyi2Bc3M+yKwMWeWnV` zO*C(PQR|lBaB$)+8{hLtq1Qq3w0Xo&A3pMwwBqIo#X5OZ^R}=n4(MJZ<*_PhQG@;cs~bkwmxs z4Jj6%ei!*O{&Fq#tAB^8gKL`nRfEQbbe>PbN~L(g9ID6 zxT;7dPqZS^fOXGF_v`9z@-$TCO7l0|PK*^ttPK%ZOyu3o8S`A!l)o}?jO^S`E_FW%#2syz%yZNdp9*gUK zf~HyLLAgJ>dOXC1$}DbU@uW0$^y5_Xz1$xYcV5dmg*>4v$1b;A(cNnXY zqI?PH?gyit-m}Fo!HFqcO)09cmHzmibDrcI*9*1a$;g#-@Ev4@LC!olTg5byH_IjboG zykKyJi9Mfl7`1(5XC3uVF<{EPA|k1dslTm2e?g4Z?TLN*prQVqH%a+M9?D7k&Gh8z zli>S(`%-N2J=5TRl9S=J_h6RdDIMaum(WL-0<4z42aD?6C6MK|XmO~)^_RZo&$sU` zGW$7aDr`MnD9!GISc};okpKY!>XGf<=@X{ArLjdp0S`f#Eovc+>)hhZ(&>|)#+s=_ zvwW_ZVdDUwL9i6jW@iId7ng)G4l<#+@StfVE?x0pPF2D%r(CkJT?dm_>YQ+j_6snw z3nY7zDvRlck>&PsAgf+H%Zx50#;@u)xk=mEm7a~m063(HAz41c%h;DW!@2GqF#vv6 z;ab^Y|5RmOGSYC^`CrpCiN91aO$uC_;m+je(!8phK^CU<_kY!UL=aurX4#6=L-~%n+Yo zo$1D{XNr|O=(M>Eb3C6rZmZTmb&QrYqg8MPLIDFH!-Pn z7m?%o$yjmV7F=TgpVN>0#xq^s`Wdpr`PnO+c$fqnHejoA;smt)y~*tlXy7FcE>+Vt z$N5upFTAnjGbA2rlcT@L+iHA2O_|w>Mr%w|D1dFdnv{LTh8u|l7L`kdP+-ZSwBm;L z2yGe`c{x>zy4Y|Ue&QbM$xyt{EQR_gnf)1ano`{gqfFiH8Vsg?Mzq2W%dX3vcaz#7 z4)x$5kD2wnE?MD1IhdKmske^D3Rf@p89mRlC6B%Rz^hjZTFsU)1N+;{AE^Q-H!|T_ z{f6^{cqgc?hoMHBHuXfJ+;c1;f}|DO{~MMrAi!{_z4W8#M{Ervv#|f?s^o>sVaW%4 zavVExK7aZP3+D;lpye?enV#9zlOM@kemg*EYHLMp*sF6=E{QdNIDb#AUb~?+o|!0M zu<<9jo3riP7G1V_kY*O(6ZrhgFdNAa`QY3ws>zoBU2G1Xt&rI`poOJi_8*hCAz1yt zA<yeles9jbs`Iwmcek z&UMk(I@!Z{5?w~fkG{gPnK?qQ%bZ1M^3rFtY6*=d{tb?EZvTuJ)>qoG5$HU2+I!3R z80|x!1`?8S6Z@pa7uU#nuzq+q@8f-?))d7Tc?XqIpGgoMY#IKBQw7Mf;)P-Wp&KhR z87lilj7o2-!6;^*^h%~?i*ENH|^ z5yLRXRM3vgYXe`EyP2fQAw? zln>3}{#|?4)yrW#CL4`0Lx}VQF+oGA#T%Jdf{S~RzF}~I99lF{S-SmcGn@?J`gA1ko5~L+gBMExE_yOQ5_qM$3wXB z)CLC?(r;`R^QTiI=ZhRV$0 zqARrezJ=vIiXYR@q%|eONHg*btBPv~(1~gXd(QH*VqJr0UCx-8yYt{0CiZOwo7OI1 ztMA#fusG_H6Ty-?V&s1~gf*?(DMK>PQ@MEio|#RV1FmZ7LRL8Hb}gSR)sLvNKz1p=-b2V{Pxa9s zj%w&y8_t=39!(#_SsD-e&@hF?iZq}qG_od}MQE4;W2Gh0jt)?~jzfsNy;;v}d6GsN zMAC2=yO3uddS{drjJ$W+gk+F)hBrJoPBecc@546+Ou%qBDIOj}zkj7zk0G+7&zt0z zAJu+!L_}x+1g*>`eWz!HsE)Vd-Fx=$O~e%T*MYP&hgMJjMO`1O?aYYdq+IOLxOsSh z(EOm84mBS?_T~Zvz7yQ`P8%oun zM%GtJp+}y}!||23utWtPdG!)7A4S6hwe|}^w!C#Sau9P{2~2K+|1OeCIAU2Ey;1wU zU{WWnI(z5yb_rgwy>&po#fN7XQPC}}uY2XE*p_j>_a6zkczvLeM=`gaE27k*|CqGY zq9Jzj0p-Trs@VrOig@rPP=CPqu%3KwPG6*$qAJ7Ao1*&N{@WFDB&h+wy1Ki0V5^GH zH-E;^PkdLsd^&VzRq;}Ff*iDjA)>1Z?7fBW0(oY5TJ3MpyGn}{3SgtmNk}10q#yVl zN&S>-q7$yj2EovF&;WLrVwqTqERf#Y$YqW(v2OIzXtGpM^s!jCqXEI+`jcW)>!kIx zH03q~zwqNpGT5aTmZ^*+@85Yz({zVx+)QxPHA$=~e)vdeeG5>*VAZ(s#02S@Ok^5n z;lb6q+kN)C?p2i>TGoX^=OaE5-Z0K{uUOY;{>MpjSG4fH1NphR8Z@3A;O9>e5FPFZM4a{{DEAn6c*_#WDgLRfH_AA_X3K zy!%Fpc?pW0Qyx&FolRrX&g1~ySFAzecLr|=^N8MJeCo)3z$hgBHUM2B6-YdqY@gV{ zeLeBa)`}mkT-LjOYp*(6Ean+yhW|&cawlrH_0lYsa-zboelRI*9QJb~tr#VLa2;Vp zvefnPM~c!p4MY?0rNpv!M;`gfqh8<-)EQ4&nIrxBQ-4l8pXNs?Neq`VodDrV=l|Um ze4f^o#ABlO11$%meb=}B`S)f?`BB-Q(kpLi1nM4pPgjrW+j>ne?lu|DCcv<+{3=T# z_J1_Pol5+0(cgpL64J*rArKs$6w41dX&IV_!0>>aHF3o85DL6RAjeT0MxL7Y@oCB* zC~lSzK>A|0_40jTJqs2r^>cq9^-_Ltbi|x@Mbt+N816vuUsZ7dB!&LQE$LLt^frd| zT-PosEct91ey-LQX#wAm>iX*Se9nKbSBL69jJ0vKMEG zJn8?lYZZ^ucX24C*;ImtcMz8dPbtCK=` zQR`DIh*+Oi%AA0F^aq&bg5jJHcAfZulZI9$w#mtyTkzdrAQO$s^nfxh+$qtK^OMyp zfmud=f6|8W&YJ1i+-is1F1%)Vc!z%g+WBmbmrB?VdQK>WXjf0^^DOYgzSO(TvOIiBxIbc zP+e2$q4uNSY5qiA+{GCI>*S-?l;2lbSHsZE=KpUx zAO9KI(EqbSpL{zUjIcMDq&Kf7wk9TL{Fvr7XV8X>4YB~kE?_j9sCwnF&jYH6+wE-` zr!D6v=^IM@8Dl)v(G|;N^_JTCmOER*<^rzymcO*fdgci6k(;O{<(?=Tp?79%Nm3S{ zMysCmwDpL}*I))<%FFs6R;Y(&OS@`7vQ5zkN0n~Ensir`sHakK1J9|)^kZo|$I?d% zI&tPy6~6PGn8LB0LHWwh)4`6>%{)LIi+I)&iXg-v&Y5|^o)M68b1xKed+38}$yJEb zm-|spxd-f<)&AtaDX=`fKYo(^XUt+J1ai9*8~l-@4tKuuUZrGHkH)Oe+87&irq!o( zcyzrL^`*3r-8>vq9q|QnR+h{mGks6cbr$3aAc#e_+OScv5;%1*Co=nB3idlV9rGz7 z7LPd7%*HxJPAvT_JlkCo)Dcxxw}gBkE_XJPefDA#d)(&GwvJ+aB{mq~^x=s5J7FQi z*pZIpf8kj7PiOYcs^5baKrp9WGsO)l=R;&tro__My&v zx<9~-MD)v2RIx*SB?lyh+WM6r%c5d&?os#%_=6D9^!5?%YM%e0$&XA}<=Dne^?RK9 z^OiJiP*JtJ?<)h<27NI#CSKD95ytM4M#B3u{!7*Id^9}!)+4U=Zx!O`W@;w%Fo~8NU`Yu z_o3i(YxlT@pSM;6cao9=-oJd0H;l98_cTm)UYO1N`41hDZyKnjtBC9zso7|2@JG&} zuxz`OE;Y)~bO77SY#O}4jB>Bz@28oaNioX1J-%7wsW&@lMh_(`)c%OHcut-hHxs>P z^SU6PeobA0D|6R&BUtQzWD^!XTKFIp-qV55z}ezLpIZU?=$73_Uq5y&impz#&Z-N< zw~)S|x~FxnI$@)Orx9$3o+lWkFfv+MH@cAw!hYt=tcn%>4wgiHuPW1qj2D+Wc#1A~ zPe`+-3a+=$^A_9pO?Goy-wi%>^N_ey$D0dYxE60!{QjWXmO&Y?BOCD!n?anIYeQggK0=wK3__5of3{`80xsGGsg_TBh3`TH&A zs3`XdtIveg^gw3}n|@|KZWgu;^^Gk#7-mune>*6O<3{V1s^vozoQ+bJk01c|z@{}r zHK6lm1G-s&_DI8@bu%z2WcpIOyH!D%2VznLMzUP}th9_HHN3QSj>tgnZ`<^MB?^or z^Oxw#Us57L3|_XU{_-1cR6uPt;XttwAeejdx79Ezo$ry^Vj1}_i+S{&B8Y2*@%36G zNZJJ94SX^jNfzt7x8Ci+{DP1rx0z^&*+8{KKZvc?d+C-nUHo^4YX$xj{i$gG9T&MC zW?%iC0~Ne7;KnbTd^uyxm)Jpjx`b;g6NizRRMn~N!JMBgz-ysCn6vhA$kK&0dGfQIiMyUoMS#_KKN0{ea182DXqaNwo0WXJn{d7B*+I}SV z@JX|DX78@K(r2uC{|pj#d2C9bqpF+`HMerZ8uQMLii15N4n8h%Zau2cbPVOh4dpLhNv}Ja5Q|_F9=QsS zNcTB_e7poD#hiyR@^3^9-Fx20ZYs#3gN7#Rg^A8+ep5_0#SXmQ@fZ}r{QP*}=SMm< z3(VB`ua$qIRf=}S29Zy{98al98xKB|OZWsD8oAWYXp)Va`lO}WZ{!g3 z{jEJKyhlb09-H=P97GENJ{&o(l1x^SEid!+hCXFsh5L_2sU=vmwV>OkF^pR;clTo| z)XNCuE0>>@T)t9?cjb#?Yic_NguuXRD&OEa$#Hf=Kpp(lUi3LR8sHur-utXSnajRN+CQ?fpRn?KmOM`w z#beW=I~9mYlFEZ$?YoM_uBp=lI6S|9`IS0V%<_A`pXuJ4Kil-o zG!E)zykuMCaQ{mo%^HtM{9l3x_?Z-2p|%$L-;i)*r&w%TUzo!1Btdm`$#EmaVtOzc z=M6KoI+8z?u(FH4ERj0&k?DgiME2~?Y=*~dMW}ytbmlg)4bGtMrCX@r?~cXNuNowR_F`XOE8BA%xz#k+z(iWXYR?_xsrri}xmL z?@g10dMa!CpC{-9Kf66_F&T;&5Vv%er9bD;A-*x@PiWNn$>wTCV>1 zM6Ds!kbMj5b#AO*3za>)=T4KY(dPn-*?@BU+^_vD;yj%(v5(okWECLaIHwO~X2adVdfjBhVe}4} zN&4apj8oc(xwtozHiZ3pm`}o8&xs^!o1gmKj;?5+27|@$?Ec19XZWpt>EE%?p67V7 zR^&kRJL_VKr_)@7F=k0-tu+h5#SC?Pp94oA(6>Z@;&b?7h3mzooqqm$9AoE`!4 z&wD6dHpZHNj2ZoEs&^o7kIi}xp&yHl4!esQwemOPAhPS*+g-rQVg0G^REin!s!o;1 z27f15uD&1es<#tQ2f++!eUe#!J`wGt<=3BQe3O!0y5-&Pf4ubI890D!`JPLgxq3wZ z72VXUZ+{eYoQvo7qAQNO8{Vz(sGk0&0s$7w!?JH56c`fvk`7%3%d8skuV?x%f@d4q z$OcR9ndU{egXS(=Dv{OLHAg%_u#ctV*T0v5m~K~!J>wN&oi3kFOo?)CX4_`*-qx=s zHGMLDujG>IATkDzUMY50Sl-+X)-%of>}cNj977cCqSJe0NMpcn06VSE(H9`DRZ0}l zZM+$fhDkLdgA@6IOx3Rx*fX!p<*M`2d?QuW(;Fib+k}tXVo$#{)a{726X}qYE!$b- zC?qPAGtHPa+Vv6TOYhx79`RsbRQ8++mo~$`y)C;kn-RtNXQWk|MX%b61i$ekvcI}B zcgng=&zd(Xhvy~`oxw#Rjefc$a>P=V%J>R7uwpHsS1q5~0N9qUf(+SBI6AV?r!lEE zXohS-!VuFyC0nt=%+*wU6EpozCAA3MUj@)*b43CC z$dE;93fKnJ#b0A86At{c;h_6k`)ot8rxJEJ+hAW}yuXilzHN})Rv37^#ivRdD*CtWZxOpTp=!Mkg4hxf_pM{R?_?2yVF ze|hh`gP#PIHD&pZ7vUsZ93f6&y`xo;Vju~@uCIrIdrLi9>}I?}52vXb>@Ro}I27oU z_)G_U@?{-=-X>9_*LsnA@<;%9r4FT9?!9YH_bu{dfQzb%*hBM*qhG+LlyOQ?>m1K% zIg)#z2uWhVU#g(`QR}iQD{Q!*!o5RM*n}WeX%zgbjB97e@EVWMCa)Geg&yThW@3ut zi5{yxIEp5wGR7I$_sU12y~fJ=t%H=75rr{bA9aeQ>3y!DxU6P9p>es0kJ5E2-(E zP~CUVva+k;6#&BDPTGpGd##O-UCQ*GMk;Bj#{E~5hi{v~TbC{&Oxk{-ujU_ zJ@qLin+emK`+q~;s_KG_DHfaJ9ZWp9vI#_&-zGi2@4DnfAUy5!gsv}NPMcI`S+v_u5*+9My!z-~fh?vLF%N&DxicKrd<_#eDD zlGtxQ36=q+!C>i76BE$B`BxPS9~7v0fTI{O{k~1cH=^IHkS?uJMc0I$PmjJduxkP2Gks2*4CG7Y-2W5t!zHap;5SdHPLq% zsCyx4w|gmF+D#45w6Id`mEZbE_eslV?@(0Rje%M-`ZwL_!A>!O;iYky;ghpoWXECXy zum*Pf7JyduZC=yRc*e1cD~jKcteg_TZd9=bO`fXzo84FSV~TD?QL2%x9>jzeQdBZX z(xLCmYUh@ZMVS@MyAxFGP6up6ov1e~;1vNfCRV(SG>FnTy_RXmTEGORn5rlbNq|%i zCMS>PpWtQQWJz|k@GTYypp}<*9o(FR+q0fLx@;C)H50W1j$R**hzrFaKemk%9(QnP z-HE-gJ_V+q89+pxm25WSR4YV@L|&~y)$R}uu{0b8Kc>YzunZVF89hpNv}gQ%i|{WC z0sX7#fNx~gHo#NUbo0EG*RK$f&NNj?bj-Cxb8ZW6B5Q|t`t=5hl-*@ByKYr$tFoUBW%aW-4Jy!}f)yjHz0OR#g-uXju|?7zK@0kPb=oZo=E2y}e&5SF zj^K8QoSn2bsezrrJE;>M3LYTt{2(hC)G1;`QoyJP}Mtk zR~CnBggY6-Or~NDDalX{RtrJ^V>k^sbk4VJq~QJ)=|V2R-$3n#5jRfe?xw2B;btZn zi87YUA(*6)@;wt(lx1uB zNYG12cBLzZTy5m>Cz!+h(3bPlEsywh3r(eN`)76U8zba#!bWg|$J|au&YP3o1|Ip+ zY505b!Pnu=hVzj*2+}8iG~D`5JU-xWSk&A~dH5k%t3IlPwRGgRl~V7};qX(4gWXe> zF+Yc33dV2}$&UeYk>nXKC-0qxrTXc99o;Y8I-a~a6F!u_6Q-aR{YYE@3u7O2SVGS| zp{ig>D+Q^|-1#6j>~1~ib4SNwdPQ7UGvRaKuf{N$F78Hi!{@zHN2{GHUnC)6#zf_& zyo>3hCJ%9JbAn1Uls{gM_PUV*s^=o-meGA z6iWtMhZgl|xv1Qh#2^ga4}XVD{bo*gR?g7>m0x2($lyIZdSVsrK2}a$H!B4pH*7{l zMEZYvpqkBRh+!_&FWscB7cm@W3+6i1Nj)er4l%4epK3p<24RP#25Z>)xP; zmN%-h)q$C%fVU=}El#nBopo9JzBIk+SrJ1F^;03wh3!I2uBK3~*a&>I^xeS#Jxw97 zRom6S*G2dQ*DjJ;9-SYq)6XOl3NVaoSH}O+MBjrk+??6#xGz=iL99ZvQy` z!Im>4EwQq4$TX(g$SLN0jJfUZ7L%pWBHA2k8OD&T(m~QhyDK5m(&jMaRO&WoIp>hm z*qln;4ynGozmM;K@%`!g?Yge_FW2L`Uhn7g@-(Wg%)N1GV)&zj1%|R$GXa{;UjMz> zQa0DE2#XaSjmz*)UFjFPwu`7K=Ckg0jbS~Oaz2;dDDf{MQli-L?D3($TI6iSRm?Z) z9uuiTKgM(-t}o3oKd&&&3i}?BU%L`HK>nPPhvO%W)LlqgNpp#X?$vO1d2BH@^!v)p z2otNM1$L?-Tvclju=hX@fGgWIClu$(mi{!-LD)S&+5RQ&1jUsTrVhx_;1ZijZt~3v zKIV%v+b24s8*xP#mBN@ry}O+a{b=b~gV(}st49E<`%CBHyxt7vp=$RG{z=Actz20k zeT5+{cG?0(Pil^6z5LbxanJMh{0Ck9Fob?6UtzAz;@B}+#+`Zi!`;#rwWNl~6pCcTU>RUE&%t`r^cK{ilMt>5rR7Cuq z9d}zS8QAhSgH7}3|8&-rEis!6-W2i-U&S1xJV4GD=oltJ98E{&ycq9ff!M3epH@?o zho0V40goUTFT_T1y@`O2h({b&b(GKQa`@CtD0Z-`tLfUA9kjJc(N(@HHE}0Wd+lfO zEdkYV?toYrnFxh+4CVmlZUs(D6OyFS`2oWhLOs~b_G574&&y7rmo25h#?=R?Z5+4< zshh^eAb_E!S2>}91y8kW*3^L4ZzAluQYc3YOYbZUwqG2q-I{v0Izn;Bg)27T`nG7qE zbn^hPBCE6*p#+H~CWg}~It@G0#mxmn8Q^HckUm@KFMO~XqRVI&fo@f0H~Rg97*6C6 zY%n2kgX-rta^e_PRChrNB2A2~1bXcAMUsCqu7yw+lRS9-_N9^#=)<;e&d{-9UC2aM zj#G4KgrXzZ{n;xwu<~ zLHRD*!WEr-+-X%0);6!q-tdcT`=TL7C9*Ut2k54qQ3-19Z<{vx2676!w_|V!SZQJ% zx=+7!`{dUJXol7tJfifEPd?gr->#Q>g|DXsBnnhX7>N6RrX_l(av!Nq@7~?C3Ww$B zf_+%!n6DN0$l}sV^?}aY4KBbUxnSRgz~8^CJOUR{Gs&8(CwkYJPg&jowv-1KG+3HE znoBZq#$5QehKiJIhs9Cf>28+dr`HWZ-tyVLHDZe5xy1K^R_*L0G0bg+{;l228H# zbWtoXV(^>+6Diif`&kOX#4J+fX8+LccK_H*=Yv?w0c%#h{w&7pt@ct@5O6k|s|#@M zti8=#C^f~Fx7POGHh0|6BSZd%F{Xo`Z_PS6-YupZmg5tNT7(U(Pc#{3~-1cTCd{NLA&o#$yv-l_U0_ z3iUgjyV?qB%znB&5LO&xnd|H`QE`qrB~G>&nOZ(CjbPags)bTl{of;=!cWnI#`aZ+ zkANH{ZdHKRH5auXI&}3bmhUHC9dG@E@hYMUGV-u}YXHAYY4d2t>6T4|Z$*&wjZ0f6 zuHxPW$iIm<@91X)!j;VIiE(>4?0F>jvXacL;BFO1k7TTWg{S)&Gvj0GN}wZonO+hi zYZzQtIvx1n5 zWc=vWSW$a|6iL9cogU#`dyJ26TL6U^*AFSv{FFYKPu3KkuB5KoOTzPrhV#*9Z>A}J z=v`h^7@>LK4rvYC=cwEx2oxk`qp*PI0{Z+PGMvfU-QO4+RKv{g25gCP=Gp7ba&)fu zvBUZ>!lJxa7O+t`bCH)2m~C}{&dnsdnIVws(ud9ejtl2;GfF|>?h88N`$q~0^+^(v zOZ>85NY7E-tZ>!{6~c<<1(=cGRYzFTzZ`mFce|6rv23~*yegP9iS0*~ou9wG$=#0c57MH7 zX|dg7F*ws+C_%E7AOG!!jkJDZ{~2zWT8 zO?np1^dkMs#anb@`Zj!ViR*AFM)jd5xmUIpg=yY0&SCdG}iOUtKLGkkPGzAd~|@?Ze>TzwBBFXv!A{&RakjY0P9QL9jQ3 zd;_Q5RF(4O;x}^{;$`VmX}zkXxf4+Qb6G(Zo2JR{I|21dCLL|6x|L|{yJDdZBCIj4 zu?46$*pr=0z8Y40jtxfgfpvMn=qJfshMJkK!P)T4rt80-LsTqShN13RgZyx|-f0CY zwkMoS>diVVqL9goaR<)^4;{@xN_kB~`<~F7z<$zS`~A1qGl<4;Ie^K+qcC<&6xX7F z+DUO9m}j?Yar~#p&XZvK=o7kA@x_2SfAejB^g2*##hgFcH5@p}$&=IW><~S(I+^4+ z?d#*aB^cy~b^7^L|E7LPP)c$}SC#x-Cv|ACdbsQ;T50Kti^U5U)1i!NpBYs){eCO{ z{2WfZKZ73{rkGis5pqxvJ7&Mq!@a;(4bRi@jcUq%ZA@K^49a4_|fr!}f z>?iM_@5xneldyk&Qr?-%8gA=Q^VSD99!YU9t<-n5D!)U~u3G+r7G72}g7vkb!CsjZcVfArRnyL+YU__ptSG}i{1E+1?w*Yu`KtTd!)d|7_5enOkrPY>WgV7 z8uO>fHnW3i4)yP3IEt_L!MEDKj+(mq<##PhXwqryWKps$`NZ2lr9OunGPJT_3Of`b z>zTfJTQ;$RYuEEAu!!A2-3rl?8DJ)hY;?XezJ}r<=uL15lcrd&%Rga*=wg@=ndtl^ zfbbX)OEE7%NT9ec22U|lCg)!0CN>@zs@x&owZga_+sS6%Ms4oTyVlEc#tCcQNvC9J zO_S%P0 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 -- 2.25.1