artificer/gui/ui.zig
2024-09-07 16:37:05 +03:00

160 lines
4.7 KiB
Zig

const rl = @import("raylib");
const std = @import("std");
const UI = @This();
font: rl.Font,
pub fn init() UI {
return UI{
.font = rl.getFontDefault()
};
}
pub fn deinit(self: UI) void {
rl.unloadFont(self.font);
}
// Reimplementation of `GetGlyphIndex` from raylib in src/rtext.c
fn GetGlyphIndex(font: rl.Font, codepoint: i32) usize {
var index: usize = 0;
var fallbackIndex: usize = 0; // Get index of fallback glyph '?'
for (0..@intCast(font.glyphCount), font.glyphs) |i, glyph| {
if (glyph.value == '?') fallbackIndex = i;
if (glyph.value == codepoint)
{
index = i;
break;
}
}
if ((index == 0) and (font.glyphs[0].value != codepoint)) index = fallbackIndex;
return index;
}
fn GetCodePointNext(text: []const u8, next: *usize) i32 {
var letter: i32 = '?';
if (std.unicode.utf8ByteSequenceLength(text[0])) |codepointSize| {
next.* = codepointSize;
if (std.unicode.utf8Decode(text[0..codepointSize])) |codepoint| {
letter = @intCast(codepoint);
} else |_| {}
} else |_| {}
return letter;
}
// NOTE: Line spacing is a global variable, use SetTextLineSpacing() to setup
const textLineSpacing = 2; // TODO: Assume that line spacing is not changed.
// Reimplementation of `rl.drawTextEx`, so a null terminated would not be required
pub fn drawTextEx(font: rl.Font, text: []const u8, position: rl.Vector2, font_size: f32, spacing: f32, tint: rl.Color) void {
var used_font = font;
if (font.texture.id == 0) {
used_font = rl.getFontDefault();
}
var text_offset_y: f32 = 0;
var text_offset_x: f32 = 0;
const scale_factor = font_size / @as(f32, @floatFromInt(used_font.baseSize));
var i: usize = 0;
while (i < text.len) {
var next: usize = 0;
const letter = GetCodePointNext(text[i..], &next);
const index = GetGlyphIndex(font, letter);
i += next;
if (letter == '\n') {
text_offset_x = 0;
text_offset_y += (font_size + textLineSpacing);
} else {
if (letter != ' ' and letter != '\t') {
rl.drawTextCodepoint(font, letter, .{
.x = position.x + text_offset_x,
.y = position.y + text_offset_y,
}, font_size, tint);
}
if (font.glyphs[index].advanceX == 0) {
text_offset_x += font.recs[index].width*scale_factor + spacing;
} else {
text_offset_x += @as(f32, @floatFromInt(font.glyphs[index].advanceX))*scale_factor + spacing;
}
}
}
}
// Reimplementation of `rl.measureTextEx`, so a null terminated would not be required
pub fn measureTextEx(font: rl.Font, text: []const u8, fontSize: f32, spacing: f32) rl.Vector2 {
var textSize = rl.Vector2.init(0, 0);
if (font.texture.id == 0) return textSize; // Security check
var tempByteCounter: i32 = 0; // Used to count longer text line num chars
var byteCounter: i32 = 0;
var textWidth: f32 = 0;
var tempTextWidth: f32 = 0; // Used to count longer text line width
var textHeight: f32 = fontSize;
const scaleFactor: f32 = fontSize/@as(f32, @floatFromInt(font.baseSize));
var i: usize = 0;
while (i < text.len)
{
byteCounter += 1;
var next: usize = 0;
const letter = GetCodePointNext(text[i..], &next);
const index = GetGlyphIndex(font, letter);
i += next;
if (letter != '\n')
{
if (font.glyphs[index].advanceX != 0) {
textWidth += @floatFromInt(font.glyphs[index].advanceX);
} else {
textWidth += font.recs[index].width;
textWidth += @floatFromInt(font.glyphs[index].offsetX);
}
}
else
{
if (tempTextWidth < textWidth) tempTextWidth = textWidth;
byteCounter = 0;
textWidth = 0;
textHeight += (fontSize + textLineSpacing);
}
if (tempByteCounter < byteCounter) tempByteCounter = byteCounter;
}
if (tempTextWidth < textWidth) tempTextWidth = textWidth;
textSize.x = tempTextWidth*scaleFactor + @as(f32, @floatFromInt(tempByteCounter - 1)) * spacing;
textSize.y = textHeight;
return textSize;
}
pub fn drawTextCentered(font: rl.Font, text: []const u8, position: rl.Vector2, font_size: f32, spacing: f32, color: rl.Color) void {
const text_size = measureTextEx(font, text, font_size, spacing);
const adjusted_position = rl.Vector2{
.x = position.x - text_size.x/2,
.y = position.y - text_size.y/2,
};
drawTextEx(font, text, adjusted_position, font_size, spacing, color);
}