~mil/mepo-devel

mepo: Port to Zig 0.11 v1 APPLIED

Reference: https://ziglang.org/download/0.11.0/release-notes.html

Nguyễn Gia Phong (5):
  Specify build for Zig 0.11
  Refactor redundant pin cycling logic
  Replace deprecated std cstr.cmp with mem.orderZ
  Use Zig 0.11 syntax for captured index in for loop
  Port to Zig 0.11 builtins with type inference

 build.zig                     |  33 +++---
 src/Mepo.zig                  |  64 ++++++------
 src/TileCache.zig             |  49 +++++----
 src/api/bind_button.zig       |   2 +-
 src/api/bind_click.zig        |   5 +-
 src/api/bind_gesture.zig      |   2 +-
 src/api/bind_key.zig          |   2 +-
 src/api/bind_signal.zig       |   8 +-
 src/api/bind_timer.zig        |   4 +-
 src/api/cache_dlbbox.zig      |  11 +-
 src/api/cache_dlradius.zig    |  10 +-
 src/api/filedump.zig          |   8 +-
 src/api/move_relative.zig     |   4 +-
 src/api/pin_activate.zig      |  12 +--
 src/api/pin_add.zig           |  10 +-
 src/api/pin_cycle.zig         |  34 +++----
 src/api/pin_delete.zig        |   8 +-
 src/api/pin_groupactivate.zig |   4 +-
 src/api/pin_meta.zig          |   9 +-
 src/api/pin_transfer.zig      |  18 ++--
 src/api/prefinc.zig           |   2 +-
 src/api/prefset_t.zig         |   2 +-
 src/api/shellpipe_async.zig   |   8 +-
 src/api/zoom_relative.zig     |   5 +-
 src/blit/blit.zig             | 183 +++++++++++++++++-----------------
 src/main.zig                  |   4 +-
 src/types.zig                 |   8 +-
 src/util/utilconversion.zig   |   2 +-
 src/util/utilmepolang.zig     |  30 +++---
 src/util/utilprefs.zig        | 104 +++++++++----------
 src/util/utilsdl.zig          |   4 +-
 31 files changed, 313 insertions(+), 336 deletions(-)

-- 
2.41.0
This is alot cleaner and readable - thanks for the rework on this one.
Thanks!  I was I was worrying that it was quite a lot to go through d-;
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~mil/mepo-devel/patches/43423/mbox | git am -3
Learn more about email & git

[PATCH mepo 1/5] Specify build for Zig 0.11 Export this patch

---
 build.zig    | 33 +++++++++++++++++++++------------
 src/main.zig |  4 ++--
 2 files changed, 23 insertions(+), 14 deletions(-)

diff --git a/build.zig b/build.zig
index f4bfc0071861..c15e65d2ff3c 100644
--- a/build.zig
@@ -13,7 +13,7 @@ fn setDependencies(step: *std.build.LibExeObjStep) void {

pub fn build(b: *Builder) void {
    b.installDirectory(.{
        .source_dir = "scripts",
        .source_dir = .{ .path = "scripts" },
        .install_dir = .{ .bin = {} },
        .install_subdir = "",
    });
@@ -22,26 +22,35 @@ pub fn build(b: *Builder) void {
    b.installFile("assets/mepo_128x128.png", "share/icons/hicolor/128x128/apps/mepo.png");
    b.installFile("assets/mepo_512x512.png", "share/icons/hicolor/512x512/apps/mepo.png");

    const exe = b.addExecutable("mepo", "src/main.zig");

    const target = b.standardTargetOptions(.{});
    const mode = b.standardReleaseOptions();
    exe.setTarget(target);
    exe.setBuildMode(mode);

    const optimize = b.standardOptimizeOption(.{});
    const exe = b.addExecutable(.{
        .name = "mepo",
        .root_source_file = .{ .path = "src/main.zig" },
        .target = target,
        .optimize = optimize,
    });
    setDependencies(exe);
    b.installArtifact(exe);

    exe.install();

    const run_cmd = exe.run();
    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }

    const run_step = b.step("run", "Run mepo");
    run_step.dependOn(&run_cmd.step);

    // Setup test
    const tests = b.addTest(.{
        .root_source_file = .{ .path = "./src/test.zig" },
        .target = target,
        .optimize = optimize,
    });
    setDependencies(tests);

    const run_tests = b.addRunArtifact(tests);
    const test_all_step = b.step("test", "Run all tests.");
    const run_tests = b.addTest("./src/test.zig");
    setDependencies(run_tests);
    test_all_step.dependOn(&run_tests.step);
}
diff --git a/src/main.zig b/src/main.zig
index fb0d64de8e9d..77b0eb43aed6 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -12,8 +12,8 @@ const utildbg = @import("./util/utildbg.zig");
pub fn main() !void {
    comptime {
        const v = builtin.zig_version;
        if (v.major != 0 or v.minor != 10)
            @panic("Must be built against Zig 0.10.x");
        if (v.major != 0 or v.minor != 11)
            @panic("Must be built against Zig 0.11.x");
    }

    const allocator = std.heap.c_allocator;
-- 
2.41.0

[PATCH mepo 2/5] Refactor redundant pin cycling logic Export this patch

---
 src/api/pin_cycle.zig | 27 +++++++++++++--------------
 1 file changed, 13 insertions(+), 14 deletions(-)

diff --git a/src/api/pin_cycle.zig b/src/api/pin_cycle.zig
index cc6d0e8316c8..674ae34abdcb 100644
--- a/src/api/pin_cycle.zig
+++ b/src/api/pin_cycle.zig
@@ -20,28 +20,27 @@ fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
}

pub fn pin_cycle(mepo: *Mepo, viewport_only: bool, delta: i32) !void {
    if (mepo.pin_groups[mepo.pin_group_active].items.len == 0) return;
    const active_group = mepo.pin_groups[mepo.pin_group_active].items;
    if (active_group.len == 0) return;
    defer mepo.blit_pinlayer_cache.clearAndFree();

    const group_unordered = !p.get(p.pingroup_prop(mepo.pin_group_active, .Ordered)).b;
    const add: i32 = if (delta > 0) 1 else -1;
    var delta_current = delta;
    while (delta_current != 0) : (delta_current -= add) {
        var pin_i: i32 = undefined;

        pin_i = if (mepo.pin_group_active_item == null) 0 else @intCast(i32, mepo.pin_group_active_item.?) + add;
    for (0..std.math.absCast(delta)) |_| {
        var pin_i: i32 = if (mepo.pin_group_active_item) |active_item| @as(i32, @intCast(active_item)) + add else 0;

        // E.g. two conditions to skip and continually increase pin_i:
        // 1. Within an ordered group, structural pins should be skipped
        // 2. Requested to cycle only viewport visible pins, non visible pins skipped
        while (pin_i > -1 and pin_i < mepo.pin_groups[mepo.pin_group_active].items.len and
            ((p.get(p.pingroup_prop(mepo.pin_group_active, .Ordered)).b and mepo.pin_groups[mepo.pin_group_active].items[@intCast(usize, pin_i)].category == .Structural) or
            (viewport_only and !pin_in_viewport(mepo, pin_i))))
        while (pin_i >= 0 and pin_i < active_group.len) {
            const unstructural = active_group[@intCast(pin_i)].category != .Structural;
            const visible = !viewport_only or pin_in_viewport(mepo, pin_i);
            if ((group_unordered or unstructural) and visible) {
                if (visible) mepo.pin_group_active_item = @intCast(pin_i);
                break;
            }
            pin_i += add;

        if (pin_i > -1 and
            mepo.pin_groups[mepo.pin_group_active].items.len > pin_i and
            (!viewport_only or (viewport_only and pin_in_viewport(mepo, pin_i))))
            mepo.pin_group_active_item = @intCast(u32, pin_i);
        }
    }
}

-- 
2.41.0
This is alot cleaner and readable - thanks for the rework on this one.

[PATCH mepo 3/5] Replace deprecated std cstr.cmp with mem.orderZ Export this patch

---
 src/api/bind_signal.zig | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/api/bind_signal.zig b/src/api/bind_signal.zig
index 504d55aa5844..235dde294dad 100644
--- a/src/api/bind_signal.zig
+++ b/src/api/bind_signal.zig
@@ -26,13 +26,13 @@ fn bind_signal(mepo: *Mepo, signo_str: [:0]const u8, expression: []const u8) !vo

    // Signal
    var signal_name: u6 = 0;
    if (std.cstr.cmp(signo_str, "USR1") == 0) {
    if (std.mem.orderZ(u8, signo_str, "USR1") == .eq) {
        signal_name = std.os.SIG.USR1;
    } else if (std.cstr.cmp(signo_str, "USR2") == 0) {
    } else if (std.mem.orderZ(u8, signo_str, "USR2") == .eq) {
        signal_name = std.os.SIG.USR2;
    } else if (std.cstr.cmp(signo_str, "TERM") == 0) {
    } else if (std.mem.orderZ(u8, signo_str, "TERM") == .eq) {
        signal_name = std.os.SIG.TERM;
    } else if (std.cstr.cmp(signo_str, "INT") == 0) {
    } else if (std.mem.orderZ(u8, signo_str, "INT") == .eq) {
        signal_name = std.os.SIG.INT;
    } else {
        return error.InvalidSignalName;
-- 
2.41.0

[PATCH mepo 4/5] Use Zig 0.11 syntax for captured index in for loop Export this patch

---
 src/Mepo.zig              | 10 +++++-----
 src/api/filedump.zig      |  2 +-
 src/api/pin_activate.zig  |  2 +-
 src/api/pin_delete.zig    |  2 +-
 src/api/pin_transfer.zig  |  4 ++--
 src/blit/blit.zig         |  4 ++--
 src/util/utilmepolang.zig | 30 +++++++++++++++---------------
 src/util/utilprefs.zig    |  6 +++---
 8 files changed, 30 insertions(+), 30 deletions(-)

diff --git a/src/Mepo.zig b/src/Mepo.zig
index cd5323152369..cbe53351605d 100644
--- a/src/Mepo.zig
+++ b/src/Mepo.zig
@@ -63,7 +63,7 @@ fn event_fingerdown(mepo: *@This(), e: sdl.SDL_Event) types.Pending {
}

fn event_fingerup(mepo: *@This(), e: sdl.SDL_Event) types.Pending {
    for (mepo.fingers.items) |f, i| {
    for (mepo.fingers.items, 0..) |f, i| {
        if (e.tfinger.fingerId == f) {
            _ = mepo.fingers.orderedRemove(i);
            break;
@@ -171,8 +171,8 @@ fn event_mousebuttonup(mepo: *@This(), e: sdl.SDL_Event) types.Pending {
            pin_i: u32,
            delta_dist: i32,
        } = null;
        for (mepo.pin_groups) |pin_group, pin_group_i| {
            for (pin_group.items) |*item, pin_i| {
        for (mepo.pin_groups, 0..) |pin_group, pin_group_i| {
            for (pin_group.items, 0..) |*item, pin_i| {
                if (item.category == .Structural) continue;
                const pin_x = mepo.convert_latlon_to_xy(.LonToX, item.lon);
                const pin_y = mepo.convert_latlon_to_xy(.LatToY, item.lat);
@@ -388,7 +388,7 @@ pub fn mepolang_execute(mepo: *@This(), mepolang_text: []const u8) !void {
fn mepolang_execute_validate_args(spec: types.MepoFnSpec, args: []types.MepoArg) !bool {
    if (spec.args.len != args.len) return error.NumberOfArgsInvalid;

    for (spec.args) |expect_spec_arg, expect_spec_arg_i| {
    for (spec.args, 0..) |expect_spec_arg, expect_spec_arg_i| {
        switch (expect_spec_arg.tag) {
            .Number => {
                switch (args[expect_spec_arg_i]) {
@@ -788,7 +788,7 @@ pub fn init(allocator: std.mem.Allocator, tile_cache: *TileCache, use_config: []
        .table_timers = std.array_hash_map.AutoArrayHashMap(types.TimerInput, []const u8).init(allocator),
        .pin_groups = pin_groups: {
            var pgs: [10]std.ArrayList(types.Pin) = undefined;
            for (pgs) |_, i| pgs[i] = std.ArrayList(types.Pin).init(allocator);
            for (pgs, 0..) |_, i| pgs[i] = std.ArrayList(types.Pin).init(allocator);
            break :pin_groups pgs;
        },
        .tile_cache = tile_cache,
diff --git a/src/api/filedump.zig b/src/api/filedump.zig
index fd821f92be5c..cd9045671f13 100644
--- a/src/api/filedump.zig
+++ b/src/api/filedump.zig
@@ -160,7 +160,7 @@ fn filedump(mepo: *Mepo, save_types: []const u8, filepath: []const u8) !void {
    if (std.mem.containsAtLeast(u8, save_types, 1, "p")) {
        try lines.append("\n# Pins;");

        for (mepo.pin_groups) |pg, pg_i| {
        for (mepo.pin_groups, 0..) |pg, pg_i| {
            for (pg.items) |pin| {
                const is_structural: u32 = b: {
                    if (pin.category == .Structural) break :b 1;
diff --git a/src/api/pin_activate.zig b/src/api/pin_activate.zig
index 98ec5d77b5ac..d41252d0febf 100644
--- a/src/api/pin_activate.zig
+++ b/src/api/pin_activate.zig
@@ -22,7 +22,7 @@ fn pin_activate(mepo: *Mepo, group: i32, handle: [:0]const u8) !void {
    const pin_group = if (group < 0) mepo.pin_group_active else @intCast(usize, group);
    defer mepo.blit_pinlayer_cache.clearAndFree();

    for (mepo.pin_groups[pin_group].items) |*pin, pin_i| {
    for (mepo.pin_groups[pin_group].items, 0..) |*pin, pin_i| {
        if (pin.handle == null) continue;
        if (std.mem.eql(u8, pin.handle.?, handle)) {
            mepo.pin_group_active = @intCast(u8, pin_group);
diff --git a/src/api/pin_delete.zig b/src/api/pin_delete.zig
index 1664cd6e85bf..80bc5b57c2e3 100644
--- a/src/api/pin_delete.zig
+++ b/src/api/pin_delete.zig
@@ -28,7 +28,7 @@ fn pin_delete(mepo: *Mepo, group: i32, handle: [:0]const u8) !void {
                break :target_pin_i active_item;
            }
        } else {
            for (mepo.pin_groups[pin_group].items) |pin, pin_i| {
            for (mepo.pin_groups[pin_group].items, 0..) |pin, pin_i| {
                if (pin.handle) |pin_handle| {
                    if (std.mem.eql(u8, handle, pin_handle)) {
                        break :target_pin_i pin_i;
diff --git a/src/api/pin_transfer.zig b/src/api/pin_transfer.zig
index 80953cc90b6e..ce20855aa9c0 100644
--- a/src/api/pin_transfer.zig
+++ b/src/api/pin_transfer.zig
@@ -33,7 +33,7 @@ fn pin_transfer(mepo: *Mepo, from_group_number: i32, target_pin_handle: [:0]cons
        } else if (std.mem.eql(u8, target_pin_handle, "all")) {
            break :from_pin_i_opt null;
        } else {
            for (mepo.pin_groups[from_group].items) |pin, pin_i| {
            for (mepo.pin_groups[from_group].items, 0..) |pin, pin_i| {
                if (pin.handle) |handle| {
                    if (std.mem.eql(u8, handle, target_pin_handle)) {
                        break :from_pin_i_opt pin_i;
@@ -58,7 +58,7 @@ fn pin_transfer(mepo: *Mepo, from_group_number: i32, target_pin_handle: [:0]cons
            mepo.pin_group_active_item = @intCast(u32, mepo.pin_groups[mepo.pin_group_active].items.len - 1);
        }
    } else {
        for (mepo.pin_groups[from_group].items) |_, pin_i| {
        for (mepo.pin_groups[from_group].items, 0..) |_, pin_i| {
            try mepo.pin_groups[to_group].append(mepo.pin_groups[from_group].items[pin_i]);
        }
        for (mepo.pin_groups[from_group].items) |_| {
diff --git a/src/blit/blit.zig b/src/blit/blit.zig
index d2c51f3fabe9..9ed08d677831 100644
--- a/src/blit/blit.zig
+++ b/src/blit/blit.zig
@@ -203,7 +203,7 @@ fn blit_tile_pinlayer(mepo: *Mepo, tile_x: u32, tile_y: u32, zoom: u8, x_off: i3
                const pin_group = mepo.pin_groups[pin_group_i];
                var is_active_path_track= false;
                var prev_pin: ?*types.Pin = null;
                for (pin_group.items) |*pin, pin_i| {
                for (pin_group.items, 0..) |*pin, pin_i| {
                    defer prev_pin = pin;

                    // is_active_path: Whether path from prevpin to pin red
@@ -602,7 +602,7 @@ fn blit_table(mepo: *Mepo, x: i32, y: i32, padding: c_int, rows: []const [2][:0]
        .h = height_row * @intCast(c_int, rows.len),
    }, .Fill);

    for (rows) |row, row_i| {
    for (rows, 0..) |row, row_i| {
        // Label
        const surf_lab = try utilsdl.errorcheck_ptr(
            sdl.SDL_Surface,
diff --git a/src/util/utilmepolang.zig b/src/util/utilmepolang.zig
index 84ad175ae779..4b859b3de940 100644
--- a/src/util/utilmepolang.zig
+++ b/src/util/utilmepolang.zig
@@ -129,8 +129,8 @@ test "statementize" {
        const input = &[_][]const u8{ "foo", "bar", "baz" };
        const expect_0 = [_][]const u8{ "foo", "bar", "baz" };
        var result = try statementize(std.heap.c_allocator, input[0..]);
        try std.testing.expectEqual(@intCast(usize, 1), result.len);
        for (result[0]) |_, idx| {
        try std.testing.expectEqual(@as(usize, @intCast(1)), result.len);
        for (result[0], 0..) |_, idx| {
            try std.testing.expect(std.mem.eql(u8, expect_0[idx], result[0][idx]));
        }
    }
@@ -141,11 +141,11 @@ test "statementize" {
        const expect_0 = [_][]const u8{ "foo", "bar", "baz" };
        const expect_1 = [_][]const u8{ "bil", "nil" };
        var result = try statementize(std.heap.c_allocator, input[0..]);
        try std.testing.expectEqual(@intCast(usize, 2), result.len);
        for (result[0]) |_, idx| {
        try std.testing.expectEqual(@as(usize, @intCast(2)), result.len);
        for (result[0], 0..) |_, idx| {
            try std.testing.expect(std.mem.eql(u8, expect_0[idx], result[0][idx]));
        }
        for (result[1]) |_, idx| {
        for (result[1], 0..) |_, idx| {
            try std.testing.expect(std.mem.eql(u8, expect_1[idx], result[1][idx]));
        }
    }
@@ -156,11 +156,11 @@ test "statementize" {
        const expect_0 = [_][]const u8{ "foo", "[", "bar", "[", "gill", "]", "]", "baz" };
        const expect_1 = [_][]const u8{ "bil", "nil" };
        var result = try statementize(std.heap.c_allocator, input[0..]);
        try std.testing.expectEqual(@intCast(usize, 2), result.len);
        for (result[0]) |_, idx| {
        try std.testing.expectEqual(@as(usize, @intCast(2)), result.len);
        for (result[0], 0..) |_, idx| {
            try std.testing.expect(std.mem.eql(u8, expect_0[idx], result[0][idx]));
        }
        for (result[1]) |_, idx| {
        for (result[1], 0..) |_, idx| {
            try std.testing.expect(std.mem.eql(u8, expect_1[idx], result[1][idx]));
        }
    }
@@ -179,8 +179,8 @@ test "tokenize" {
        const input = "foo bar baz";
        var expect = [_][]const u8{ "foo", "bar", "baz" };
        var result = try tokenize(std.heap.c_allocator, input);
        try std.testing.expectEqual(@intCast(usize, 3), result.len);
        for (result) |_, idx| {
        try std.testing.expectEqual(@as(usize, @intCast(3)), result.len);
        for (result, 0..) |_, idx| {
            try std.testing.expect(std.mem.eql(u8, expect[idx], result[idx]));
        }
    }
@@ -190,8 +190,8 @@ test "tokenize" {
        const input = "foo bar [baz]";
        var expect = [_][]const u8{ "foo", "bar", "[", "baz", "]" };
        var result = try tokenize(std.heap.c_allocator, input);
        try std.testing.expectEqual(@intCast(usize, 5), result.len);
        for (result) |_, idx| {
        try std.testing.expectEqual(@as(usize, @intCast(5)), result.len);
        for (result, 0..) |_, idx| {
            try std.testing.expect(std.mem.eql(u8, expect[idx], result[idx]));
        }
    }
@@ -201,8 +201,8 @@ test "tokenize" {
        const input = "foo bar [ baz]";
        var expect = [_][]const u8{ "foo", "bar", "[", "baz", "]" };
        var result = try tokenize(std.heap.c_allocator, input);
        try std.testing.expectEqual(@intCast(usize, 5), result.len);
        for (result) |_, idx| {
        try std.testing.expectEqual(@as(usize, @intCast(5)), result.len);
        for (result, 0..) |_, idx| {
            try std.testing.expect(std.mem.eql(u8, expect[idx], result[idx]));
        }
    }
@@ -246,7 +246,7 @@ test "argize" {

    for (specs) |s| {
        const result = try argize(arena.allocator(), s.input[0..]);
        for (s.expect) |_, i| {
        for (s.expect, 0..) |_, i| {
            try std.testing.expectEqual(@TypeOf(s.expect[i]), @TypeOf(result[i]));
            switch (result[i]) {
                .Number => try std.testing.expectEqual(s.expect[i].Number, result[i].Number),
diff --git a/src/util/utilprefs.zig b/src/util/utilprefs.zig
index 0372f153533b..37a07ac8cdec 100644
--- a/src/util/utilprefs.zig
+++ b/src/util/utilprefs.zig
@@ -165,9 +165,9 @@ pub fn set_t(allocator: std.mem.Allocator, prefname: pref, value: []const u8) !v
}

pub fn lookup_pref_from_string(prefname: []const u8) ?pref {
    for (prefs_mapping) |p, i| {
        if (std.mem.eql(u8, p.name, prefname)) return @intToEnum(pref, i);
    }
    for (prefs_mapping, 0..) |p, i|
        if (std.mem.eql(u8, p.name, prefname))
            return @enumFromInt(i);
    return null;
}

-- 
2.41.0

[PATCH mepo 5/5] Port to Zig 0.11 builtins with type inference Export this patch

These incluse @min, @max and explicit casts.
---
 src/Mepo.zig                  |  54 +++++-----
 src/TileCache.zig             |  49 +++++-----
 src/api/bind_button.zig       |   2 +-
 src/api/bind_click.zig        |   5 +-
 src/api/bind_gesture.zig      |   2 +-
 src/api/bind_key.zig          |   2 +-
 src/api/bind_timer.zig        |   4 +-
 src/api/cache_dlbbox.zig      |  11 +--
 src/api/cache_dlradius.zig    |  10 +-
 src/api/filedump.zig          |   6 +-
 src/api/move_relative.zig     |   4 +-
 src/api/pin_activate.zig      |  10 +-
 src/api/pin_add.zig           |  10 +-
 src/api/pin_cycle.zig         |   9 +-
 src/api/pin_delete.zig        |   6 +-
 src/api/pin_groupactivate.zig |   4 +-
 src/api/pin_meta.zig          |   9 +-
 src/api/pin_transfer.zig      |  14 ++-
 src/api/prefinc.zig           |   2 +-
 src/api/prefset_t.zig         |   2 +-
 src/api/shellpipe_async.zig   |   8 +-
 src/api/zoom_relative.zig     |   5 +-
 src/blit/blit.zig             | 179 +++++++++++++++++-----------------
 src/types.zig                 |   8 +-
 src/util/utilconversion.zig   |   2 +-
 src/util/utilprefs.zig        |  98 +++++++++----------
 src/util/utilsdl.zig          |   4 +-
 27 files changed, 244 insertions(+), 275 deletions(-)

diff --git a/src/Mepo.zig b/src/Mepo.zig
index cbe53351605d..44b79902e574 100644
--- a/src/Mepo.zig
+++ b/src/Mepo.zig
@@ -50,8 +50,8 @@ quit_action: ?[]const u8 = null,

pub fn convert_latlon_to_xy(mepo: *@This(), lat_or_lon: enum { LonToX, LatToY }, lat_lon_value: f64) i32 {
    return switch (lat_or_lon) {
        .LonToX => -1 * (mepo.get_x() - @divTrunc(@intCast(i32, mepo.win_w), 2) - utilconversion.lon_to_px_x(lat_lon_value, p.get(p.pref.zoom).u)),
        .LatToY => -1 * (mepo.get_y() - @divTrunc(@intCast(i32, mepo.win_h), 2) - utilconversion.lat_to_px_y(lat_lon_value, p.get(p.pref.zoom).u)),
        .LonToX => -1 * (mepo.get_x() - @as(i32, @intCast(mepo.win_w / 2)) - utilconversion.lon_to_px_x(lat_lon_value, p.get(p.pref.zoom).u)),
        .LatToY => -1 * (mepo.get_y() - @as(i32, @intCast(mepo.win_h / 2)) - utilconversion.lat_to_px_y(lat_lon_value, p.get(p.pref.zoom).u)),
    };
}

@@ -99,13 +99,13 @@ fn event_multigesture(mepo: *@This(), e: sdl.SDL_Event) types.Pending {
    const delta_max = 2;
    const run_gesture_opt: ?types.GestureInput = run_gesture: {
        if (e.mgesture.dDist > threshold_pan_dist) {
            break :run_gesture .{ .action = .Pan, .direction = .In, .n_fingers = @intCast(u8, mepo.fingers.items.len) };
            break :run_gesture .{ .action = .Pan, .direction = .In, .n_fingers = @intCast(mepo.fingers.items.len) };
        } else if (e.mgesture.dDist < -threshold_pan_dist) {
            break :run_gesture .{ .action = .Pan, .direction = .Out, .n_fingers = @intCast(u8, mepo.fingers.items.len) };
            break :run_gesture .{ .action = .Pan, .direction = .Out, .n_fingers = @intCast(mepo.fingers.items.len) };
        } else if (e.mgesture.dTheta > threshold_rotate_radians) {
            break :run_gesture .{ .action = .Rotate, .direction = .In, .n_fingers = @intCast(u8, mepo.fingers.items.len) };
            break :run_gesture .{ .action = .Rotate, .direction = .In, .n_fingers = @intCast(mepo.fingers.items.len) };
        } else if (e.mgesture.dTheta < -threshold_rotate_radians) {
            break :run_gesture .{ .action = .Rotate, .direction = .Out, .n_fingers = @intCast(u8, mepo.fingers.items.len) };
            break :run_gesture .{ .action = .Rotate, .direction = .Out, .n_fingers = @intCast(mepo.fingers.items.len) };
        }
        break :run_gesture null;
    };
@@ -179,8 +179,8 @@ fn event_mousebuttonup(mepo: *@This(), e: sdl.SDL_Event) types.Pending {
                const delta = (std.math.absInt(pin_x - cursor.x) catch continue) + (std.math.absInt(pin_y - cursor.y) catch continue);
                if (delta < config.ClickPinMaxDelta and (closest_match_pin == null or closest_match_pin.?.delta_dist > delta)) {
                    closest_match_pin = .{
                        .pin_group_i = @intCast(u8, pin_group_i),
                        .pin_i = @intCast(u32, pin_i),
                        .pin_group_i = @intCast(pin_group_i),
                        .pin_i = @intCast(pin_i),
                        .delta_dist = delta,
                    };
                }
@@ -195,7 +195,7 @@ fn event_mousebuttonup(mepo: *@This(), e: sdl.SDL_Event) types.Pending {
    }

    // 4. Default back to whatever was bound on bind_click by user
    const key = .{ .button = e.button.button, .clicks = @intCast(i8, e.button.clicks) };
    const key = types.ClickInput{ .button = e.button.button, .clicks = @intCast(e.button.clicks) };
    if (mepo.table_clicks.get(key)) |run_expression| {
        utildbg.log("Got click table function {s} for {}\n", .{ run_expression, key });
        mepo.mepolang_execute(run_expression) catch unreachable;
@@ -269,7 +269,7 @@ fn event_keyup(mepo: *@This(), e: sdl.SDL_Event) types.Pending {
}

fn event_signal(mepo: *@This(), e: sdl.SDL_Event) types.Pending {
    if (mepo.table_signals.get(@intCast(u6, e.user.code))) |run_expression| {
    if (mepo.table_signals.get(@intCast(e.user.code))) |run_expression| {
        utildbg.log("Got signals table function {s}\n", .{run_expression});
        mepo.mepolang_execute(run_expression) catch unreachable;
        return .Redraw;
@@ -292,8 +292,8 @@ fn event_windowevent(mepo: *@This(), e: sdl.SDL_Event) types.Pending {
            utilsdl.errorcheck(sdl.SDL_RenderSetLogicalSize(mepo.renderer, gl_width, gl_height)) catch |err| {
                utildbg.log("Unable to set logical size: {}\n", .{err});
            };
            mepo.win_w = @intCast(u32, gl_width);
            mepo.win_h = @intCast(u32, gl_height);
            mepo.win_w = @intCast(gl_width);
            mepo.win_h = @intCast(gl_height);
        },
        else => {
            //utildbg.log("Unhandled SDL Window Event: {s}\n", .{@tagName(@intToEnum(sdl.SDL_WindowEventID, @intCast(c_int, e.window.event)))});
@@ -304,7 +304,7 @@ fn event_windowevent(mepo: *@This(), e: sdl.SDL_Event) types.Pending {
}

fn event_mepolangexecution(mepo: *@This(), e: sdl.SDL_Event) types.Pending {
    const heap_str = std.mem.sliceTo(@ptrCast([*c]u8, @alignCast(@alignOf([*c]u8), e.user.data1)), 0);
    const heap_str = std.mem.sliceTo(@as([*c]u8, @ptrCast(e.user.data1)), 0);
    utildbg.log("SDL mepolang event proccessing: <{s}>\n", .{heap_str});
    mepo.mepolang_execute(heap_str) catch |err| {
        utildbg.log("Error executing mepolang <{s}> caught from SDL event: {}\n", .{ heap_str, err });
@@ -485,7 +485,7 @@ pub fn init_video_and_sdl_stdin_loop(mepo: *@This(), enable_stdin_mepolang_repl:
}

fn mepo_sdl_loop_thread_boot(userdata: ?*anyopaque) callconv(.C) c_int {
    var mepo = @ptrCast(*@This(), @alignCast(@alignOf(*@This()), userdata.?));
    const mepo: *@This() = @alignCast(@ptrCast(userdata.?));
    video_init(mepo) catch unreachable;
    mepo.mepolang_execute(mepo.config) catch unreachable;
    sdl_event_loop(mepo) catch unreachable;
@@ -563,22 +563,22 @@ pub fn scaled_mouse_position(mepo: *@This()) sdl.SDL_Point {
    _ = sdl.SDL_GetMouseState(&cursor_x, &cursor_y);
    sdl.SDL_GL_GetDrawableSize(mepo.window, &gl_w, &gl_h);
    sdl.SDL_GetWindowSize(mepo.window, &win_w, &win_h);
    const scale_x = @intToFloat(f64, gl_w) / @intToFloat(f64, win_w);
    const scale_y = @intToFloat(f64, gl_h) / @intToFloat(f64, win_h);
    const scaled_x = @floatToInt(c_int, @intToFloat(f64, cursor_x) * scale_x);
    const scaled_y = @floatToInt(c_int, @intToFloat(f64, cursor_y) * scale_y);
    return .{ .x = scaled_x, .y = scaled_y };
    const scale_x = @as(f64, @floatFromInt(gl_w)) / @as(f64, @floatFromInt(win_w));
    const scale_y = @as(f64, @floatFromInt(gl_h)) / @as(f64, @floatFromInt(win_h));
    const scaled_x = @as(f64, @floatFromInt(cursor_x)) * scale_x;
    const scaled_y = @as(f64, @floatFromInt(cursor_y)) * scale_y;
    return .{ .x = @intFromFloat(scaled_x), .y = @intFromFloat(scaled_y) };
}

pub fn cursor_latlon(mepo: *@This()) struct { Lat: f64, Lon: f64 } {
    const cursor = mepo.scaled_mouse_position();

    const cursor_lat = utilconversion.px_y_to_lat(
        mepo.get_y() - @divTrunc(@intCast(i32, mepo.win_h), 2) + cursor.y,
        mepo.get_y() - @as(i32, @intCast(mepo.win_h / 2)) + cursor.y,
        p.get(p.pref.zoom).u,
    );
    const cursor_lon = utilconversion.px_x_to_lon(
        mepo.get_x() - @divTrunc(@intCast(i32, mepo.win_w), 2) + cursor.x,
        mepo.get_x() - @as(i32, @intCast(mepo.win_w / 2)) + cursor.x,
        p.get(p.pref.zoom).u,
    );

@@ -606,10 +606,10 @@ pub fn set_y(_: *@This(), y: i32) void {

pub fn bounding_box(mepo: *@This()) types.LatLonBox {
    return .{
        .topleft_lat = utilconversion.px_y_to_lat(mepo.get_y() - @divTrunc(@intCast(i32, mepo.win_h), @intCast(i32, 2)), p.get(p.pref.zoom).u),
        .topleft_lon = utilconversion.px_x_to_lon(mepo.get_x() - @divTrunc(@intCast(i32, mepo.win_w), @intCast(i32, 2)), p.get(p.pref.zoom).u),
        .bottomright_lat = utilconversion.px_y_to_lat(mepo.get_y() + @divTrunc(@intCast(i32, mepo.win_h), @intCast(i32, 2)), p.get(p.pref.zoom).u),
        .bottomright_lon = utilconversion.px_x_to_lon(mepo.get_x() + @divTrunc(@intCast(i32, mepo.win_w), @intCast(i32, 2)), p.get(p.pref.zoom).u),
        .topleft_lat = utilconversion.px_y_to_lat(mepo.get_y() - @as(i32, @intCast(mepo.win_h / 2)), p.get(p.pref.zoom).u),
        .topleft_lon = utilconversion.px_x_to_lon(mepo.get_x() - @as(i32, @intCast(mepo.win_w / 2)), p.get(p.pref.zoom).u),
        .bottomright_lat = utilconversion.px_y_to_lat(mepo.get_y() + @as(i32, @intCast(mepo.win_h / 2)), p.get(p.pref.zoom).u),
        .bottomright_lon = utilconversion.px_x_to_lon(mepo.get_x() + @as(i32, @intCast(mepo.win_w / 2)), p.get(p.pref.zoom).u),
    };
}

@@ -760,9 +760,9 @@ fn init_create_fonts_array(bold: bool) ![50]*sdl.TTF_Font {
        fonts[font_size] = try utilsdl.errorcheck_ptr(
            sdl.TTF_Font,
            sdl.TTF_OpenFontRW(
                sdl.SDL_RWFromConstMem(@ptrCast(*const anyopaque, &font_dat[0]), font_dat.len),
                sdl.SDL_RWFromConstMem(@ptrCast(&font_dat[0]), font_dat.len),
                1,
                @intCast(c_int, font_size),
                @intCast(font_size),
            ),
        );
        if (bold) sdl.TTF_SetFontStyle(fonts[font_size], sdl.TTF_STYLE_BOLD);
diff --git a/src/TileCache.zig b/src/TileCache.zig
index a01e00ea6738..9014589af38b 100644
--- a/src/TileCache.zig
+++ b/src/TileCache.zig
@@ -202,7 +202,7 @@ pub fn tile_bg_bbox_queue(tile_cache: *@This(), dl_req: DownloadBBoxRequest, can
    var n_cached: usize = 0;
    var n_queued: usize = 0;

    var z: u32 = @intCast(u32, dl_req.zoom_min);
    var z: u32 = @intCast(dl_req.zoom_min);
    while (z <= dl_req.zoom_max) : (z += 1) {
        const x_min = @divFloor(utilconversion.lon_to_px_x(lon_min, z), config.Tsize);
        const x_max = @divFloor(utilconversion.lon_to_px_x(lon_max, z), config.Tsize);
@@ -216,7 +216,7 @@ pub fn tile_bg_bbox_queue(tile_cache: *@This(), dl_req: DownloadBBoxRequest, can
                if (cancellable and tile_cache.thread_queuebbox == null) return error.EarlyQueueBboxTermination;

                if (x < 0 or y < 0 or z < 0) continue;
                const coords = .{ .x = @intCast(u32, x), .y = @intCast(u32, y), .z = @intCast(u8, z) };
                const coords = types.XYZ{ .x = @intCast(x), .y = @intCast(y), .z = @intCast(z) };
                if (try tile_cache.tile_exists_in_fs_and_non_expired(coords)) {
                    n_cached += 1;
                } else {
@@ -269,13 +269,12 @@ pub fn tile_ui_retreive_or_queue(tile_cache: *@This(), coords: types.XYZ) !TileD
        }
    }


    if (tile_cache.thread_download == null) {
        return TileData{ .error_type = .Offline };
    } else {
        try tile_cache.queue_lifo_ui.put(coords, void{});
        return TileData{
            .queued_position = @intCast(u32, if (tile_cache.queue_lifo_ui.getIndex(coords)) |index| index else 0),
            .queued_position = if (tile_cache.queue_lifo_ui.getIndex(coords)) |index| @intCast(index) else 0,
        };
    }
}
@@ -303,25 +302,25 @@ fn curl_add_to_multi_and_register_transfer(tile_cache: *@This(), coords: types.X
        if (cstdio.sprintf(
            &url[0],
            &p.get(p.pref.tile_cache_url).t.?[0],
            @intCast(c_int, coords.x),
            @intCast(c_int, coords.y),
            @intCast(c_int, coords.z),
            @as(c_int, @intCast(coords.x)),
            @as(c_int, @intCast(coords.y)),
            @as(c_int, @intCast(coords.z)),
        ) < 0) return error.TileURLSprintfFail;
        break :url url;
    };
    defer tile_cache.allocator.free(tile_url);

    try curl_setopt(transfer_datum.client, curl.CURLOPT_NOPROGRESS, @intCast(c_long, 0));
    try curl_setopt(transfer_datum.client, curl.CURLOPT_NOSIGNAL, @intCast(c_long, 1));
    try curl_setopt(transfer_datum.client, curl.CURLOPT_CONNECTTIMEOUT, @intCast(c_long, config.DownloadTimeoutSeconds));
    try curl_setopt(transfer_datum.client, curl.CURLOPT_TIMEOUT, @intCast(c_long, config.DownloadTimeoutSeconds));
    try curl_setopt(transfer_datum.client, curl.CURLOPT_URL, @ptrCast(*anyopaque, tile_url));
    try curl_setopt(transfer_datum.client, curl.CURLOPT_NOPROGRESS, @as(c_long, 0));
    try curl_setopt(transfer_datum.client, curl.CURLOPT_NOSIGNAL, @as(c_long, 1));
    try curl_setopt(transfer_datum.client, curl.CURLOPT_CONNECTTIMEOUT, @as(c_long, @intCast(config.DownloadTimeoutSeconds)));
    try curl_setopt(transfer_datum.client, curl.CURLOPT_TIMEOUT, @as(c_long, @intCast(config.DownloadTimeoutSeconds)));
    try curl_setopt(transfer_datum.client, curl.CURLOPT_URL, @as(*anyopaque, @ptrCast(tile_url)));
    try curl_setopt(transfer_datum.client, curl.CURLOPT_USERAGENT, config.DownloadUseragent);
    try curl_setopt(transfer_datum.client, curl.CURLOPT_VERBOSE, @intCast(c_long, 0));
    try curl_setopt(transfer_datum.client, curl.CURLOPT_VERBOSE, @as(c_long, 0));
    try curl_setopt(transfer_datum.client, curl.CURLOPT_WRITEDATA, transfer_datum);
    try curl_setopt(transfer_datum.client, curl.CURLOPT_WRITEFUNCTION, curl_callback_tile_write);
    try curl_setopt(transfer_datum.client, curl.CURLOPT_XFERINFODATA, transfer_datum);
    try curl_setopt(transfer_datum.client, curl.CURLOPT_VERBOSE, @intCast(c_long, 0));
    try curl_setopt(transfer_datum.client, curl.CURLOPT_VERBOSE, @as(c_long, 0));
    try curl_setopt(transfer_datum.client, curl.CURLOPT_STDERR, tile_cache.dev_null_fd);
    try curl_setopt(transfer_datum.client, curl.CURLOPT_XFERINFOFUNCTION, curl_callback_tile_xferinfo);
    try curl_errorcheck(curl.curl_multi_add_handle(tile_cache.curl_multi, transfer_datum.client));
@@ -338,7 +337,7 @@ fn curl_callback_tile_xferinfo(
    //utildbg.log("Progress: {} {}\n", .{ dl_now, dl_total });
    _ = ul_total;
    _ = ul_now;
    var tile_data = @intToPtr(*TransferDatum, @ptrToInt(user_data));
    const tile_data: *TransferDatum = @alignCast(@ptrCast(user_data));
    tile_data.progress_dl_now = dl_now;
    tile_data.progress_dl_total = dl_total;
    return curl.CURL_PROGRESSFUNC_CONTINUE;
@@ -351,8 +350,8 @@ fn curl_callback_tile_write(
    user_data: *anyopaque,
) callconv(.C) c_uint {
    //utildbg.log("Write!!: \n", .{});
    var transfer_datum = @intToPtr(*TransferDatum, @ptrToInt(user_data));
    var typed_data = @intToPtr([*]u8, @ptrToInt(data));
    const transfer_datum: *TransferDatum = @alignCast(@ptrCast(user_data));
    const typed_data: [*]u8 = @ptrCast(data);
    transfer_datum.data_arraylist.appendSlice(typed_data[0 .. nmemb * size]) catch return 0;
    return nmemb * size;
}
@@ -373,7 +372,7 @@ fn curl_client_to_coords(tile_cache: *@This(), client: ?*curl.CURL) ?types.XYZ {
}

fn curl_setopt(client: *curl.CURL, opt: c_int, value: anytype) !void {
    if (curl.curl_easy_setopt(client, @intCast(c_uint, opt), value) != curl.CURLE_OK)
    if (curl.curl_easy_setopt(client, @intCast(opt), value) != curl.CURLE_OK)
        return error.CurlEasySetOptFail;
}

@@ -388,8 +387,8 @@ fn download_loop_progress_indicator(tile_cache: *@This(), initial_queue_size: us
        \\
    ,
        .{
            100.0 * (@intToFloat(f32, initial_queue_size - tile_cache.queue_lifo_bg.count()) /
                @intToFloat(f32, initial_queue_size)),
            100.0 * @as(f32, @floatFromInt(initial_queue_size - tile_cache.queue_lifo_bg.count()))
                / @as(f32, @floatFromInt(initial_queue_size)),
            initial_queue_size - tile_cache.queue_lifo_bg.count(),
            initial_queue_size,
            tile_cache.byte_counter / 1024 / 1024,
@@ -419,7 +418,7 @@ fn download_loop_transfer_complete(tile_cache: *@This(), msg: *curl.CURLMsg) !vo
                if (tile_cache.cache_dir) |cache_dir| {
                    const memory = try utilsdl.errorcheck_ptr(
                        sdl.SDL_RWops,
                        sdl.SDL_RWFromConstMem(@ptrCast(*anyopaque, &datum_array[0]), @intCast(c_int, datum_array.len)),
                        sdl.SDL_RWFromConstMem(@ptrCast(&datum_array[0]), @intCast(datum_array.len)),
                    );
                    const is_valid_png_data = sdl.SDL_TRUE == sdl.IMG_isPNG(memory);
                    try utilsdl.errorcheck(sdl.SDL_RWclose(memory));
@@ -469,7 +468,7 @@ fn evict_texture(_: types.XYZ, texture: *sdl.SDL_Texture) void {
fn load_data_to_surface(_: *@This(), data: []u8) !*sdl.SDL_Surface {
    if (data.len == 0) return error.LoadToSurfaceFailEmptyData;

    const memory = try utilsdl.errorcheck_ptr(sdl.SDL_RWops, sdl.SDL_RWFromConstMem(@ptrCast(*anyopaque, &data[0]), @intCast(c_int, data.len)));
    const memory = try utilsdl.errorcheck_ptr(sdl.SDL_RWops, sdl.SDL_RWFromConstMem(@ptrCast(&data[0]), @intCast(data.len)));
    return try utilsdl.errorcheck_ptr(sdl.SDL_Surface, sdl.IMG_Load_RW(memory, 1));
}

@@ -491,7 +490,7 @@ fn tile_exists_in_fs_and_non_expired(tile_cache: *@This(), coords: types.XYZ) !b
        defer tile_cache.allocator.free(png);
        const tile_file = cache_dir.openFile(png, .{}) catch return false;
        defer tile_file.close();
        const expiry_seconds = @floatToInt(i32, p.get(p.pref.tile_cache_expiry_seconds).f);
        const expiry_seconds: i32 = @intFromFloat(p.get(p.pref.tile_cache_expiry_seconds).f);
        if (expiry_seconds < 0 or std.time.timestamp() < (try tile_file.stat()).ctime + expiry_seconds) {
            return true;
        }
@@ -500,7 +499,7 @@ fn tile_exists_in_fs_and_non_expired(tile_cache: *@This(), coords: types.XYZ) !b
}

fn threadable_tile_bg_bbox_queue(userdata: ?*anyopaque) callconv(.C) c_int {
    var tile_cache = @ptrCast(*@This(), @alignCast(@alignOf(*@This()), userdata.?));
    const tile_cache: *@This() = @alignCast(@ptrCast(userdata.?));
    if (tile_cache.tile_bg_bbox_queue(tile_cache.bbox_queue.?, true)) |q| {
        utildbg.log("Done: {}\n", .{q});
    } else |err| {
@@ -511,7 +510,7 @@ fn threadable_tile_bg_bbox_queue(userdata: ?*anyopaque) callconv(.C) c_int {
}

fn threadable_download_loop_sdl(userdata: ?*anyopaque) callconv(.C) c_int {
    var tile_cache = @ptrCast(*@This(), @alignCast(@alignOf(*@This()), userdata.?));
    const tile_cache: *@This() = @alignCast(@ptrCast(userdata.?));
    tile_cache.download_loop(true) catch |e| {
        utildbg.log("Error running download loop: {}\n", .{e});
    };
diff --git a/src/api/bind_button.zig b/src/api/bind_button.zig
index 579cafc616f6..fb1d7bd57aee 100644
--- a/src/api/bind_button.zig
+++ b/src/api/bind_button.zig
@@ -19,7 +19,7 @@ pub const spec = .{

fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const visible_only_when_pin_active = args[0].Number == 1;
    const group_number = if (args[1].Number < 0) null else @floatToInt(u8, args[1].Number);
    const group_number = if (args[1].Number < 0) null else @as(u8, @intFromFloat(args[1].Number));
    const text = args[2].Text;
    const expression_click_single = args[3].Text;
    const expression_click_hold = args[4].Text;
diff --git a/src/api/bind_click.zig b/src/api/bind_click.zig
index 17ad1320cb57..9934282a59e7 100644
--- a/src/api/bind_click.zig
+++ b/src/api/bind_click.zig
@@ -16,10 +16,7 @@ pub const spec = .{
};

fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const button = @floatToInt(u8, args[0].Number);
    const clicks = @floatToInt(i8, args[1].Number);
    const expression = args[2].Text;
    try bind_click(mepo, button, clicks, expression);
    try bind_click(mepo, @intFromFloat(args[0].Number), @intFromFloat(args[1].Number), args[2].Text);
}

fn bind_click(mepo: *Mepo, button: u8, clicks: i8, expression: []const u8) !void {
diff --git a/src/api/bind_gesture.zig b/src/api/bind_gesture.zig
index a4e322793418..72204654b564 100644
--- a/src/api/bind_gesture.zig
+++ b/src/api/bind_gesture.zig
@@ -18,7 +18,7 @@ pub const spec = .{

fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const action: types.GestureInputAction = if (std.mem.eql(u8, args[0].Text, "pan")) .Pan else .Rotate;
    const fingers: u8 = @floatToInt(u8, args[1].Number);
    const fingers: u8 = @intFromFloat(args[1].Number);
    const direction: types.GestureInputDirection = if (args[2].Number == 1) .In else .Out;
    const expression = args[3].Text;
    try bind_gesture(mepo, action, fingers, direction, expression);
diff --git a/src/api/bind_key.zig b/src/api/bind_key.zig
index da7ba9a9fe81..5212c8e2ee0c 100644
--- a/src/api/bind_key.zig
+++ b/src/api/bind_key.zig
@@ -40,7 +40,7 @@ fn bind_key(mepo: *Mepo, mod_str: [:0]const u8, key_str: [:0]const u8, expressio
    };

    const key: types.KeyInput = .{
        .keymod = @intCast(c_uint, keymod),
        .keymod = @intCast(keymod),
        .key = std.ascii.toLower(key_str[0]),
    };

diff --git a/src/api/bind_timer.zig b/src/api/bind_timer.zig
index 73a7bbf10c2e..7860389f32e7 100644
--- a/src/api/bind_timer.zig
+++ b/src/api/bind_timer.zig
@@ -16,9 +16,7 @@ pub const spec = .{
};

fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const interval_seconds = @floatToInt(u32, args[0].Number);
    const expression = args[1].Text;
    try bind_timer(mepo, interval_seconds, expression);
    try bind_timer(mepo, @intFromFloat(args[0].Number), args[1].Text);
}

fn bind_timer(mepo: *Mepo, interval_seconds: u32, expression: []const u8) !void {
diff --git a/src/api/cache_dlbbox.zig b/src/api/cache_dlbbox.zig
index 56b44e8adf26..b2478dc48d27 100644
--- a/src/api/cache_dlbbox.zig
+++ b/src/api/cache_dlbbox.zig
@@ -21,14 +21,9 @@ pub const spec = .{
};

fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const a_lat = args[0].Number;
    const a_lon = args[1].Number;
    const b_lat = args[2].Number;
    const b_lon = args[3].Number;
    const zoom_min = @floatToInt(i32, args[4].Number);
    const zoom_max = @floatToInt(i32, args[5].Number);

    try cache_dlbbox(mepo, a_lat, a_lon, b_lat, b_lon, zoom_min, zoom_max);
    try cache_dlbbox(mepo,
        args[0].Number, args[1].Number, args[2].Number, args[3].Number,
        @intFromFloat(args[4].Number), @intFromFloat(args[5].Number));
}

fn cache_dlbbox(mepo: *Mepo, a_lat: f64, a_lon: f64, b_lat: f64, b_lon: f64, zoom_min: i32, zoom_max: i32) !void {
diff --git a/src/api/cache_dlradius.zig b/src/api/cache_dlradius.zig
index 77be095bcf4e..47f498474dbf 100644
--- a/src/api/cache_dlradius.zig
+++ b/src/api/cache_dlradius.zig
@@ -20,13 +20,9 @@ pub const spec = .{
};

fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const lat = args[0].Number;
    const lon = args[1].Number;
    const zoom_min = @floatToInt(i32, args[2].Number);
    const zoom_max = @floatToInt(i32, args[3].Number);
    const km_radius = args[4].Number;

    try cache_dlradius(mepo, lat, lon, zoom_min, zoom_max, km_radius);
    try cache_dlradius(mepo, args[0].Number, args[1].Number,
        @intFromFloat(args[2].Number), @intFromFloat(args[3].Number),
        args[4].Number);
}

fn cache_dlradius(mepo: *Mepo, center_lat: f64, center_lon: f64, zoom_min: i32, zoom_max: i32, km_radius: f64) !void {
diff --git a/src/api/filedump.zig b/src/api/filedump.zig
index cd9045671f13..bbc892eee621 100644
--- a/src/api/filedump.zig
+++ b/src/api/filedump.zig
@@ -44,8 +44,8 @@ fn filedump(mepo: *Mepo, save_types: []const u8, filepath: []const u8) !void {
            var format_as: enum { Text, HexText, Number } = .Number;

            const numerical_val: f64 = switch (pref.value) {
                .b => @intToFloat(f64, @boolToInt(pref.value.b)),
                .u => @intToFloat(f64, pref.value.u),
                .b => @floatFromInt(@intFromBool(pref.value.b)),
                .u => @floatFromInt(pref.value.u),
                .u24 => v: {
                    format_as = .HexText;
                    break :v -1;
@@ -146,7 +146,7 @@ fn filedump(mepo: *Mepo, save_types: []const u8, filepath: []const u8) !void {
        // UI Buttons
        for (mepo.uibuttons.items) |btn| {
            const pins_only_btn: u8 = if (btn.only_visible_when_pin_active) 1 else 0;
            const group_number: i8 = if (btn.group_number) |g| @intCast(i8, g) else -1;
            const group_number: i8 = if (btn.group_number) |g| @intCast(g) else -1;
            const statement_button = try std.fmt.allocPrint(
                arena.allocator(),
                "bind_button {d} {d} [{s}] [{s}] [{s}];",
diff --git a/src/api/move_relative.zig b/src/api/move_relative.zig
index eb3244e6cb1e..d7522d2382b7 100644
--- a/src/api/move_relative.zig
+++ b/src/api/move_relative.zig
@@ -13,8 +13,8 @@ pub const spec = .{
};

fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const rel_x = @floatToInt(i32, args[0].Number);
    const rel_y = @floatToInt(i32, args[1].Number);
    const rel_x: i32 = @intFromFloat(args[0].Number);
    const rel_y: i32 = @intFromFloat(args[1].Number);
    mepo.set_x(mepo.get_x() + rel_x);
    mepo.set_y(mepo.get_y() + rel_y);
}
diff --git a/src/api/pin_activate.zig b/src/api/pin_activate.zig
index d41252d0febf..8ea5cadc0f9f 100644
--- a/src/api/pin_activate.zig
+++ b/src/api/pin_activate.zig
@@ -13,20 +13,18 @@ pub const spec = .{
};

fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const group = @floatToInt(i32, args[0].Number);
    const handle = args[1].Text;
    try pin_activate(mepo, group, handle);
    try pin_activate(mepo, @intFromFloat(args[0].Number), args[1].Text);
}

fn pin_activate(mepo: *Mepo, group: i32, handle: [:0]const u8) !void {
    const pin_group = if (group < 0) mepo.pin_group_active else @intCast(usize, group);
    const pin_group: usize = if (group < 0) mepo.pin_group_active else @intCast(group);
    defer mepo.blit_pinlayer_cache.clearAndFree();

    for (mepo.pin_groups[pin_group].items, 0..) |*pin, pin_i| {
        if (pin.handle == null) continue;
        if (std.mem.eql(u8, pin.handle.?, handle)) {
            mepo.pin_group_active = @intCast(u8, pin_group);
            mepo.pin_group_active_item = @intCast(u32, pin_i);
            mepo.pin_group_active = @intCast(pin_group);
            mepo.pin_group_active_item = @intCast(pin_i);
        }
    }
}
diff --git a/src/api/pin_add.zig b/src/api/pin_add.zig
index 0befdb3e554b..b3517f90ddd4 100644
--- a/src/api/pin_add.zig
+++ b/src/api/pin_add.zig
@@ -16,16 +16,12 @@ pub const spec = .{
};

fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const group = @floatToInt(i32, args[0].Number);
    const is_structural = args[1].Number == 1;
    const lat = args[2].Number;
    const lon = args[3].Number;
    const handle = args[4].Text;
    try pin_add(mepo, group, is_structural, lat, lon, handle);
    try pin_add(mepo, @intFromFloat(args[0].Number), args[1].Number == 1,
        args[2].Number, args[3].Number, args[4].Text);
}

fn pin_add(mepo: *Mepo, group: i32, is_structural: bool, lat: f64, lon: f64, handle: [:0]const u8) !void {
    const pin_group = if (group < 0) mepo.pin_group_active else @intCast(usize, group);
    const pin_group: usize = if (group < 0) mepo.pin_group_active else @intCast(group);
    defer mepo.blit_pinlayer_cache.clearAndFree();

    // E.g. nop if adding a pin_handle that already exists
diff --git a/src/api/pin_cycle.zig b/src/api/pin_cycle.zig
index 674ae34abdcb..f671c1b77343 100644
--- a/src/api/pin_cycle.zig
+++ b/src/api/pin_cycle.zig
@@ -14,9 +14,8 @@ pub const spec = .{
};

fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const viewport_only = @floatToInt(i32, args[0].Number) == 1;
    const delta = @floatToInt(i32, args[1].Number);
    try pin_cycle(mepo, viewport_only, delta);
    try pin_cycle(mepo, @as(i32, @intFromFloat(args[0].Number)) == 1,
        @intFromFloat(args[1].Number));
}

pub fn pin_cycle(mepo: *Mepo, viewport_only: bool, delta: i32) !void {
@@ -27,7 +26,7 @@ pub fn pin_cycle(mepo: *Mepo, viewport_only: bool, delta: i32) !void {
    const group_unordered = !p.get(p.pingroup_prop(mepo.pin_group_active, .Ordered)).b;
    const add: i32 = if (delta > 0) 1 else -1;
    for (0..std.math.absCast(delta)) |_| {
        var pin_i: i32 = if (mepo.pin_group_active_item) |active_item| @as(i32, @intCast(active_item)) + add else 0;
        var pin_i = if (mepo.pin_group_active_item) |active_item| @as(i32, @intCast(active_item)) + add else 0;

        // E.g. two conditions to skip and continually increase pin_i:
        // 1. Within an ordered group, structural pins should be skipped
@@ -45,7 +44,7 @@ pub fn pin_cycle(mepo: *Mepo, viewport_only: bool, delta: i32) !void {
}

fn pin_in_viewport(mepo: *Mepo, pin_index: i32) bool {
    const pin = mepo.pin_groups[mepo.pin_group_active].items[@intCast(usize, pin_index)];
    const pin = mepo.pin_groups[mepo.pin_group_active].items[@intCast(pin_index)];
    const x = mepo.convert_latlon_to_xy(.LonToX, pin.lon);
    const y = mepo.convert_latlon_to_xy(.LatToY, pin.lat);
    return x > 0 and x < mepo.win_w and y > 0 and y < mepo.win_h;
diff --git a/src/api/pin_delete.zig b/src/api/pin_delete.zig
index 80bc5b57c2e3..429cb4ef4c3d 100644
--- a/src/api/pin_delete.zig
+++ b/src/api/pin_delete.zig
@@ -13,15 +13,13 @@ pub const spec = .{
};

fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const group = @floatToInt(i32, args[0].Number);
    const handle = args[1].Text;
    try pin_delete(mepo, group, handle);
    try pin_delete(mepo, @intFromFloat(args[0].Number), args[1].Text);
}

fn pin_delete(mepo: *Mepo, group: i32, handle: [:0]const u8) !void {
    defer mepo.blit_pinlayer_cache.clearAndFree();

    const pin_group = if (group < 0) mepo.pin_group_active else @intCast(usize, group);
    const pin_group: usize = if (group < 0) mepo.pin_group_active else @intCast(group);
    const target_pin_i = target_pin_i: {
        if (handle.len == 0) {
            if (mepo.pin_group_active_item) |active_item| {
diff --git a/src/api/pin_groupactivate.zig b/src/api/pin_groupactivate.zig
index 98fe5b7e1729..6c2930e07b9e 100644
--- a/src/api/pin_groupactivate.zig
+++ b/src/api/pin_groupactivate.zig
@@ -13,10 +13,8 @@ pub const spec = .{
};

fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const group_number = @floatToInt(usize, args[0].Number);
    defer mepo.blit_pinlayer_cache.clearAndFree();

    mepo.pin_group_active_item = null;
    mepo.pin_group_active = @intCast(u8, group_number);
    mepo.pin_group_active = @intFromFloat(args[0].Number);
    try pin_cycle(mepo, false, 1);
}
diff --git a/src/api/pin_meta.zig b/src/api/pin_meta.zig
index da12c2807563..dbbae145f4ca 100644
--- a/src/api/pin_meta.zig
+++ b/src/api/pin_meta.zig
@@ -15,15 +15,12 @@ pub const spec = .{
};

fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const group = @floatToInt(i32, args[0].Number);
    const handle = args[1].Text;
    const key = args[2].Text;
    const value = args[3].Text;
    try pin_meta(mepo, group, handle, key, value);
    try pin_meta(mepo, @intFromFloat(args[0].Number),
        args[1].Text, args[2].Text, args[3].Text);
}

pub fn pin_meta(mepo: *Mepo, group: i32, handle: [:0]const u8, key: [:0]const u8, value: [:0]const u8) !void {
    const pin_group = if (group < 0) mepo.pin_group_active else @intCast(usize, group);
    const pin_group: usize = if (group < 0) mepo.pin_group_active else @intCast(group);

    var pin: *types.Pin = pin: {
        for (mepo.pin_groups[pin_group].items) |*pin| {
diff --git a/src/api/pin_transfer.zig b/src/api/pin_transfer.zig
index ce20855aa9c0..aa05f16df212 100644
--- a/src/api/pin_transfer.zig
+++ b/src/api/pin_transfer.zig
@@ -14,14 +14,12 @@ pub const spec = .{
};

fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const from_group = @floatToInt(i32, args[0].Number);
    const pin_handle = args[1].Text;
    const to_group = @floatToInt(usize, args[2].Number);
    try pin_transfer(mepo, from_group, pin_handle, to_group);
    try pin_transfer(mepo, @intFromFloat(args[0].Number),
        args[1].Text, @intFromFloat(args[2].Number));
}

fn pin_transfer(mepo: *Mepo, from_group_number: i32, target_pin_handle: [:0]const u8, to_group: usize) !void {
    const from_group = if (from_group_number < 0) mepo.pin_group_active else @intCast(usize, from_group_number);
    const from_group: usize = if (from_group_number < 0) mepo.pin_group_active else @intCast(from_group_number);
    if (from_group == to_group) return;
    defer mepo.blit_pinlayer_cache.clearAndFree();

@@ -54,8 +52,8 @@ fn pin_transfer(mepo: *Mepo, from_group_number: i32, target_pin_handle: [:0]cons
            mepo.pin_group_active_item != null and
            mepo.pin_group_active_item.? == from_pin_i)
        {
            mepo.pin_group_active = @intCast(u8, to_group);
            mepo.pin_group_active_item = @intCast(u32, mepo.pin_groups[mepo.pin_group_active].items.len - 1);
            mepo.pin_group_active = @intCast(to_group);
            mepo.pin_group_active_item = @intCast(mepo.pin_groups[mepo.pin_group_active].items.len - 1);
        }
    } else {
        for (mepo.pin_groups[from_group].items, 0..) |_, pin_i| {
@@ -65,7 +63,7 @@ fn pin_transfer(mepo: *Mepo, from_group_number: i32, target_pin_handle: [:0]cons
            _ = mepo.pin_groups[from_group].orderedRemove(0);
        }
        if (from_group == mepo.pin_group_active) {
            mepo.pin_group_active = @intCast(u8, to_group);
            mepo.pin_group_active = @intCast(to_group);
        }
    }
}
diff --git a/src/api/prefinc.zig b/src/api/prefinc.zig
index 0d6348141cc8..c1947c998f6f 100644
--- a/src/api/prefinc.zig
+++ b/src/api/prefinc.zig
@@ -20,6 +20,6 @@ fn execute(_: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {

    if (p.lookup_pref_from_string(name)) |key| {
        const val = p.get(key).u;
        p.set_n(key, @intToFloat(f64, val) + delta);
        p.set_n(key, @as(f64, @floatFromInt(val)) + delta);
    }
}
diff --git a/src/api/prefset_t.zig b/src/api/prefset_t.zig
index 0ce00c5a5d73..ac599323de93 100644
--- a/src/api/prefset_t.zig
+++ b/src/api/prefset_t.zig
@@ -28,7 +28,7 @@ fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
        if (value.len < 1 or value[0] != '#') return error.InvalidColor;
        const color = try std.fmt.parseUnsigned(u24, value[1..], 16);
        if (p.lookup_pref_from_string(name)) |prefname| {
            p.set_n(prefname, @intToFloat(f64, color));
            p.set_n(prefname, @floatFromInt(color));
        }
    }
}
diff --git a/src/api/shellpipe_async.zig b/src/api/shellpipe_async.zig
index afc41b5832c9..02d60c7012ae 100644
--- a/src/api/shellpipe_async.zig
+++ b/src/api/shellpipe_async.zig
@@ -26,9 +26,7 @@ pub const spec = .{
};

fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const unique_handle_id = @floatToInt(i8, args[0].Number);
    const cmd = args[1].Text;
    try async_shellpipe(mepo, unique_handle_id, cmd);
    try async_shellpipe(mepo, @intFromFloat(args[0].Number), args[1].Text);
}

fn async_shellpipe(mepo: *Mepo, unique_handle_id: i8, cmd: []const u8) !void {
@@ -40,7 +38,7 @@ fn async_shellpipe(mepo: *Mepo, unique_handle_id: i8, cmd: []const u8) !void {
}

fn async_shellpipe_run(userdata: ?*anyopaque) callconv(.C) c_int {
    var shellpipe_request: *AsyncShellpipeRequest = @ptrCast(*AsyncShellpipeRequest, @alignCast(@alignOf(*AsyncShellpipeRequest), userdata.?));
    var shellpipe_request: *AsyncShellpipeRequest = @alignCast(@ptrCast(userdata.?));
    async_shellpipe_run_catch_errors(shellpipe_request.mepo, shellpipe_request.unique_handle_id, shellpipe_request.cmd) catch |err| {
        utildbg.log("Error running async shellpipe: {}\n", .{err});
    };
@@ -80,7 +78,7 @@ fn async_shellpipe_run_catch_errors(mepo: *Mepo, unique_handle_id: i8, cmd: []co
        const events = try std.os.poll(&poll_fds, std.math.maxInt(i32));
        if (events == 0) continue;
        if (poll_fds[0].revents & std.os.POLL.IN != 0) {
            try stdout.ensureTotalCapacity(std.math.min(stdout.items.len + bump_amt, max_output_bytes));
            try stdout.ensureTotalCapacity(@min(stdout.items.len + bump_amt, max_output_bytes));
            if (stdout.unusedCapacitySlice().len == 0) return error.StdoutStreamTooLong;
            const nread = try std.os.read(poll_fds[0].fd, stdout.unusedCapacitySlice());
            if (nread == 0) {
diff --git a/src/api/zoom_relative.zig b/src/api/zoom_relative.zig
index 25bbd37f5aaf..79d655ca92c7 100644
--- a/src/api/zoom_relative.zig
+++ b/src/api/zoom_relative.zig
@@ -13,10 +13,9 @@ pub const spec = .{
};

fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const relative = @floatToInt(i32, args[0].Number);
    zoom_relative(mepo, relative);
    zoom_relative(mepo, @intFromFloat(args[0].Number));
}

pub fn zoom_relative(_: *Mepo, relative: i32) void {
    p.set_n(p.pref.zoom, @intToFloat(f64, @intCast(u8, std.math.min(19, std.math.max(0, @intCast(i32, p.get(p.pref.zoom).u) + relative)))));
    p.set_n(p.pref.zoom, @floatFromInt(@min(19, @max(0, p.get(p.pref.zoom).u)) + relative));
}
diff --git a/src/blit/blit.zig b/src/blit/blit.zig
index 9ed08d677831..62b7a7d8219c 100644
--- a/src/blit/blit.zig
+++ b/src/blit/blit.zig
@@ -14,39 +14,39 @@ fn blit_crosshair(mepo: *Mepo) errors.SDLError!void {
    try utilsdl.sdl_renderer_set_draw_color(mepo.renderer, .{ .value = 0x000000 });
    try utilsdl.errorcheck(sdl.SDL_RenderDrawLine(
        mepo.renderer,
        @intCast(c_int, mepo.win_w / 2 - (p.get(p.pref.crosshair_size).u / 2)),
        @intCast(c_int, mepo.win_h / 2),
        @intCast(c_int, mepo.win_w / 2 + (p.get(p.pref.crosshair_size).u / 2)),
        @intCast(c_int, mepo.win_h / 2),
        @intCast(mepo.win_w / 2 - (p.get(p.pref.crosshair_size).u / 2)),
        @intCast(mepo.win_h / 2),
        @intCast(mepo.win_w / 2 + (p.get(p.pref.crosshair_size).u / 2)),
        @intCast(mepo.win_h / 2),
    ));
    try utilsdl.errorcheck(sdl.SDL_RenderDrawLine(
        mepo.renderer,
        @intCast(c_int, mepo.win_w / 2),
        @intCast(c_int, mepo.win_h / 2 - (p.get(p.pref.crosshair_size).u / 2)),
        @intCast(c_int, mepo.win_w / 2),
        @intCast(c_int, mepo.win_h / 2 + (p.get(p.pref.crosshair_size).u / 2)),
        @intCast(mepo.win_w / 2),
        @intCast(mepo.win_h / 2 - (p.get(p.pref.crosshair_size).u / 2)),
        @intCast(mepo.win_w / 2),
        @intCast(mepo.win_h / 2 + (p.get(p.pref.crosshair_size).u / 2)),
    ));
}

fn blit_tiles_all(mepo: *Mepo, bbox: sdl.SDL_Rect, zoom: u8) !void {
    const vpx = utilconversion.lon_to_px_x(p.get(p.pref.lon).f, zoom) - @divTrunc(@intCast(i32, mepo.win_w), 2);
    const vpy = utilconversion.lat_to_px_y(p.get(p.pref.lat).f, zoom) - @divTrunc(@intCast(i32, mepo.win_h), 2);
    const vpx = utilconversion.lon_to_px_x(p.get(p.pref.lon).f, zoom) - @as(i32, @intCast(mepo.win_w / 2));
    const vpy = utilconversion.lat_to_px_y(p.get(p.pref.lat).f, zoom) - @as(i32, @intCast(mepo.win_h / 2));
    const begx = vpx - config.Tsize;
    const begy = vpy - config.Tsize;
    const endx = vpx + @intCast(i32, bbox.w) + config.Tsize;
    const endy = vpy + @intCast(i32, bbox.h) + config.Tsize;
    const endx = vpx + @as(i32, @intCast(bbox.w)) + config.Tsize;
    const endy = vpy + @as(i32, @intCast(bbox.h)) + config.Tsize;

    var tilex: i32 = @intCast(i32, @divFloor(begx, config.Tsize));
    var tiley: i32 = @intCast(i32, @divFloor(begy, config.Tsize));
    var tilex: i32 = @intCast(@divFloor(begx, config.Tsize));
    var tiley: i32 = @intCast(@divFloor(begy, config.Tsize));

    var x = begx;
    while (x < endx) {
        var y = begy;
        tiley = @intCast(i32, @divFloor(begy, config.Tsize));
        tiley = @intCast(@divFloor(begy, config.Tsize));
        while (y < endy) {
            if (tilex >= 0 and tiley >= 0 and tilex <= std.math.pow(u32, 2, zoom) - 1 and tiley <= std.math.pow(u32, 2, zoom) - 1) {
                const tilex_int = @intCast(u32, tilex);
                const tiley_int = @intCast(u32, tiley);
                const tilex_int: u32 = @intCast(tilex);
                const tiley_int: u32 = @intCast(tiley);
                try blit_tile_surface(mepo, tilex_int, tiley_int, zoom, vpx, vpy, bbox);
                try blit_tile_pinlayer(mepo, tilex_int, tiley_int, zoom, vpx, vpy, bbox);
            }
@@ -60,8 +60,8 @@ fn blit_tiles_all(mepo: *Mepo, bbox: sdl.SDL_Rect, zoom: u8) !void {

fn blit_tile_surface(mepo: *Mepo, tile_x: u32, tile_y: u32, zoom: u8, x_off: i32, y_off: i32, bbox: sdl.SDL_Rect) !void {
    const rect_dest = sdl.SDL_Rect{
        .x = @intCast(i32, tile_x * config.Tsize) - x_off,
        .y = @intCast(i32, tile_y * config.Tsize) - y_off,
        .x = @as(i32, @intCast(tile_x * config.Tsize)) - x_off,
        .y = @as(i32, @intCast(tile_y * config.Tsize)) - y_off,
        .w = config.Tsize,
        .h = config.Tsize,
    };
@@ -90,8 +90,8 @@ fn blit_tile_surface(mepo: *Mepo, tile_x: u32, tile_y: u32, zoom: u8, x_off: i32
                null,
                null,
                .{ .w = config.Tsize, .h = config.Tsize, .align_x = .Center, .align_y = .Center },
                @intCast(i32, tile_x * config.Tsize) - x_off,
                @intCast(i32, tile_y * config.Tsize) - y_off,
                @as(i32, @intCast(tile_x * config.Tsize)) - x_off,
                @as(i32, @intCast(tile_y * config.Tsize)) - y_off,
                20,
                "Queued",
                .{},
@@ -100,17 +100,17 @@ fn blit_tile_surface(mepo: *Mepo, tile_x: u32, tile_y: u32, zoom: u8, x_off: i32
        .transfer_datum => |transfer_datum| {
            var dl_now_kb: f64 = 0;
            var dl_total_kb: f64 = 0;
            if (transfer_datum.progress_dl_now) |dln| dl_now_kb = @intToFloat(f64, dln) / 1000;
            if (transfer_datum.progress_dl_total) |dlt| dl_total_kb = @intToFloat(f64, dlt) / 1000;
            if (transfer_datum.progress_dl_now) |dln| dl_now_kb = @as(f64, @floatFromInt(dln)) / 1000;
            if (transfer_datum.progress_dl_total) |dlt| dl_total_kb = @as(f64, @floatFromInt(dlt)) / 1000;
            var percent: f64 = 0;
            if (dl_total_kb != 0) {
                percent = dl_now_kb / dl_total_kb;
            }

            const color = c: {
                const r = @floatToInt(u24, 255 * percent);
                const g = @floatToInt(u24, 255 * percent);
                const b = @floatToInt(u24, 255 * percent);
                const r = @as(u24, @intFromFloat(255 * percent));
                const g = @as(u24, @intFromFloat(255 * percent));
                const b = @as(u24, @intFromFloat(255 * percent));
                const color: u24 = (r << 16) | (g << 8) | (b << 0);
                break :c color;
            };
@@ -121,8 +121,8 @@ fn blit_tile_surface(mepo: *Mepo, tile_x: u32, tile_y: u32, zoom: u8, x_off: i32
                null,
                null,
                .{ .w = config.Tsize, .h = config.Tsize, .align_x = .Center, .align_y = .Center },
                @intCast(i32, tile_x * config.Tsize) - x_off,
                @intCast(i32, tile_y * config.Tsize) - y_off,
                @as(i32, @intCast(tile_x * config.Tsize)) - x_off,
                @as(i32, @intCast(tile_y * config.Tsize)) - y_off,
                null,
                "{d:0.2}%\n{d:0.1}/{d:0.1}Kb",
                .{ percent * 100, dl_now_kb, dl_total_kb },
@@ -137,8 +137,8 @@ fn blit_tile_surface(mepo: *Mepo, tile_x: u32, tile_y: u32, zoom: u8, x_off: i32
                null,
                null,
                .{ .w = config.Tsize, .h = config.Tsize, .align_x = .Center, .align_y = .Center },
                @intCast(i32, tile_x * config.Tsize) - x_off,
                @intCast(i32, tile_y * config.Tsize) - y_off,
                @as(i32, @intCast(tile_x * config.Tsize)) - x_off,
                @as(i32, @intCast(tile_y * config.Tsize)) - y_off,
                20,
                "Offline",
                .{},
@@ -149,8 +149,8 @@ fn blit_tile_surface(mepo: *Mepo, tile_x: u32, tile_y: u32, zoom: u8, x_off: i32

fn blit_tile_pinlayer(mepo: *Mepo, tile_x: u32, tile_y: u32, zoom: u8, x_off: i32, y_off: i32, bbox: sdl.SDL_Rect) !void {
    const rect_dest = sdl.SDL_Rect{
        .x = @intCast(i32, tile_x * config.Tsize) - x_off,
        .y = @intCast(i32, tile_y * config.Tsize) - y_off,
        .x = @as(i32, @intCast(tile_x * config.Tsize)) - x_off,
        .y = @as(i32, @intCast(tile_y * config.Tsize)) - y_off,
        .w = config.Tsize,
        .h = config.Tsize,
    };
@@ -231,7 +231,7 @@ fn blit_tile_pinlayer(mepo: *Mepo, tile_x: u32, tile_y: u32, zoom: u8, x_off: i3
                        mepo,
                        pin,
                        if (prev_pin != null) prev_pin else null,
                        @intCast(u8, pin_group_i),
                        @intCast(pin_group_i),
                        is_active_path,
                        .{ .x = tile_x, .y = tile_y, .z = zoom }
                    );
@@ -329,13 +329,16 @@ fn blit_debugmessage(mepo: *Mepo) !void {
fn get_pin_xyz_and_offsets(pin: *types.Pin, for_zoom: u8) types.XYZAndOffsets {
    const pinx = utilconversion.lon_to_px_x(pin.lon, for_zoom);
    const piny = utilconversion.lat_to_px_y(pin.lat, for_zoom);
    const pintilex = @intCast(u32, @divFloor(pinx, config.Tsize));
    const pintiley = @intCast(u32, @divFloor(piny, config.Tsize));
    const tile_off_x = @intCast(u32, @rem(pinx, config.Tsize));
    const tile_off_y = @intCast(u32, @rem(piny, config.Tsize));
    return .{
        .xyz = .{ .x = pintilex, .y = pintiley, .z = for_zoom },
        .offset = .{ .x = tile_off_x, .y = tile_off_y },
        .xyz = .{
            .x = @intCast(@divFloor(pinx, config.Tsize)),
            .y = @intCast(@divFloor(piny, config.Tsize)),
            .z = for_zoom,
        },
        .offset = .{
            .x = @intCast(@rem(pinx, config.Tsize)),
            .y = @intCast(@rem(piny, config.Tsize)),
        },
    };
}

@@ -344,19 +347,19 @@ fn blit_pin(mepo: *Mepo, pin: *types.Pin, prev_pin: ?*types.Pin, pin_group: u8,
    const is_active = mepo.pin_group_active_item != null and &mepo.pin_groups[mepo.pin_group_active].items[mepo.pin_group_active_item.?] == pin;

    const pindest = get_pin_xyz_and_offsets(pin, for_tile.z);
    const pin_target_x = ((@intCast(c_int, pindest.xyz.x) - @intCast(c_int, for_tile.x)) * config.Tsize) + @intCast(c_int, pindest.offset.x);
    const pin_target_y = ((@intCast(c_int, pindest.xyz.y) - @intCast(c_int, for_tile.y)) * config.Tsize) + @intCast(c_int, pindest.offset.y);
    var prevpin_target_x : ?i32 = null;
    var prevpin_target_y : ?i32 = null;
    const pin_target_x = ((@as(c_int, @intCast(pindest.xyz.x)) - @as(c_int, @intCast(for_tile.x))) * config.Tsize) + @as(c_int, @intCast(pindest.offset.x));
    const pin_target_y = ((@as(c_int, @intCast(pindest.xyz.y)) - @as(c_int, @intCast(for_tile.y))) * config.Tsize) + @as(c_int, @intCast(pindest.offset.y));
    var prevpin_target_x: ?i32 = null;
    var prevpin_target_y: ?i32 = null;
    if (prev_pin != null) {
        const prevpindest = get_pin_xyz_and_offsets(prev_pin.?, for_tile.z);
        prevpin_target_x = ((@intCast(c_int, prevpindest.xyz.x) - @intCast(c_int, for_tile.x)) * config.Tsize) + @intCast(c_int, prevpindest.offset.x);
        prevpin_target_y = ((@intCast(c_int, prevpindest.xyz.y) - @intCast(c_int, for_tile.y)) * config.Tsize) + @intCast(c_int, prevpindest.offset.y);
        prevpin_target_x = ((@as(c_int, @intCast(prevpindest.xyz.x)) - @as(c_int, @intCast(for_tile.x))) * config.Tsize) + @as(c_int, @intCast(prevpindest.offset.x));
        prevpin_target_y = ((@as(c_int, @intCast(prevpindest.xyz.y)) - @as(c_int, @intCast(for_tile.y))) * config.Tsize) + @as(c_int, @intCast(prevpindest.offset.y));
    }

    // 0. Nop Check - ensure that rendered pin/connecting line within tile
    {
        const font_height = @intCast(i8, config.ZoomLevelToPinFontSize[p.get(p.pref.zoom).u]);
        const font_height: i8 = @intCast(config.ZoomLevelToPinFontSize[p.get(p.pref.zoom).u]);
        const n_tiles_font_overlap = 2;
        const pin_in_view = r: {
            const a = pin_target_x > (-config.Tsize * n_tiles_font_overlap)  and pin_target_x < (config.Tsize * n_tiles_font_overlap);
@@ -372,15 +375,15 @@ fn blit_pin(mepo: *Mepo, pin: *types.Pin, prev_pin: ?*types.Pin, pin_group: u8,
        const connecting_line_in_view = r: {
            if (prev_pin == null) break :r false;
            const prevpindest = get_pin_xyz_and_offsets(prev_pin.?, for_tile.z);
            var x1 = @intCast(c_int, pindest.xyz.x * config.Tsize + pindest.offset.x);
            var y1 = @intCast(c_int, pindest.xyz.y * config.Tsize + pindest.offset.y);
            var x2 = @intCast(c_int, prevpindest.xyz.x * config.Tsize + prevpindest.offset.x);
            var y2 = @intCast(c_int, prevpindest.xyz.y * config.Tsize + prevpindest.offset.y);
            var x1: c_int = @intCast(pindest.xyz.x * config.Tsize + pindest.offset.x);
            var y1: c_int = @intCast(pindest.xyz.y * config.Tsize + pindest.offset.y);
            var x2: c_int = @intCast(prevpindest.xyz.x * config.Tsize + prevpindest.offset.x);
            var y2: c_int = @intCast(prevpindest.xyz.y * config.Tsize + prevpindest.offset.y);
            const rect = sdl.SDL_Rect{
                  .x = @intCast(i32, for_tile.x * config.Tsize),
                  .y = @intCast(i32, for_tile.y * config.Tsize),
                  .w = config.Tsize,
                  .h = config.Tsize,
                .x = @intCast(for_tile.x * config.Tsize),
                .y = @intCast(for_tile.y * config.Tsize),
                .w = config.Tsize,
                .h = config.Tsize,
            };
            break :r sdl.SDL_IntersectRectAndLine(&rect, &x1, &y1, &x2, &y2) == sdl.SDL_TRUE;
        };
@@ -397,10 +400,10 @@ fn blit_pin(mepo: *Mepo, pin: *types.Pin, prev_pin: ?*types.Pin, pin_group: u8,
        const connecting_line_color = if (is_active_path) 0xff0000ff else pg_color.to_u32();
        try utilsdl.errorcheck(sdl.aalineColor(
            mepo.renderer,
            @intCast(i16, prevpin_target_x.?),
            @intCast(i16, prevpin_target_y.?),
            @intCast(i16, pin_target_x),
            @intCast(i16, pin_target_y),
            @intCast(prevpin_target_x.?),
            @intCast(prevpin_target_y.?),
            @intCast(pin_target_x),
            @intCast(pin_target_y),
            connecting_line_color,
        ));
    }
@@ -472,14 +475,14 @@ fn blit_overlay_debugbar(mepo: *Mepo) !void {

    try utilsdl.sdl_renderer_rect(mepo.renderer, bg, sdl.SDL_Rect{
        .x = 0,
        .y = @intCast(i32, mepo.win_h) - bottombar_height,
        .w = @intCast(i32, mepo.win_w),
        .y = @as(i32, @intCast(mepo.win_h)) - bottombar_height,
        .w = @intCast(mepo.win_w),
        .h = 200,
    }, .Fill);
    try utilsdl.sdl_renderer_rect(mepo.renderer, .{ .value = 0x000000 }, sdl.SDL_Rect{
        .x = 0,
        .y = @intCast(i32, mepo.win_h) - bottombar_height,
        .w = @intCast(i32, mepo.win_w),
        .y = @as(i32, @intCast(mepo.win_h)) - bottombar_height,
        .w = @intCast(mepo.win_w),
        .h = 1,
    }, .Fill);
    try blit_multiline_text(
@@ -489,10 +492,10 @@ fn blit_overlay_debugbar(mepo: *Mepo) !void {
        null,
        .{ .align_x = .Right },
        0,
        @intCast(i32, mepo.win_h) - bottombar_height + 3,
        @as(i32, @intCast(mepo.win_h)) - bottombar_height + 3,
        null,
        "Dl: {d:.2}Mb ",
        .{@intToFloat(f32, mepo.tile_cache.byte_counter) / 1024 / 1024},
        .{@as(f32, @floatFromInt(mepo.tile_cache.byte_counter)) / 1024 / 1024},
    );
    try blit_multiline_text(
        mepo,
@@ -501,14 +504,14 @@ fn blit_overlay_debugbar(mepo: *Mepo) !void {
        null,
        .{},
        5,
        @intCast(i32, mepo.win_h) - bottombar_height + 3,
        @as(i32, @intCast(mepo.win_h)) - bottombar_height + 3,
        null,
        "{d:.3} {d:.3} Z{d} O{d} P{d} Q{d} B{d} D{d} M{d} S{d}",
        .{
            p.get(p.pref.lat).f,
            p.get(p.pref.lon).f,
            p.get(p.pref.zoom).u,
            @boolToInt(mepo.tile_cache.thread_download != null),
            @intFromBool(mepo.tile_cache.thread_download != null),
            mepo.pin_group_active,
            mepo.tile_cache.queue_lifo_ui.count(),
            mepo.tile_cache.queue_lifo_bg.count(),
@@ -567,16 +570,16 @@ fn blit_table(mepo: *Mepo, x: i32, y: i32, padding: c_int, rows: []const [2][:0]
    for (rows) |row| {
        const surf_lab = try utilsdl.errorcheck_ptr(
            sdl.SDL_Surface,
            sdl.TTF_RenderUTF8_Blended(mepo.fonts_bold[p.get(p.pref.fontsize_ui).u], @ptrCast([*c]const u8, row[0]), (types.Color{ .value = 0x000000 }).to_sdl()),
            sdl.TTF_RenderUTF8_Blended(mepo.fonts_bold[p.get(p.pref.fontsize_ui).u], @ptrCast(row[0]), (types.Color{ .value = 0x000000 }).to_sdl()),
        );
        defer sdl.SDL_FreeSurface(surf_lab);
        const surf_val = try utilsdl.errorcheck_ptr(
            sdl.SDL_Surface,
            sdl.TTF_RenderUTF8_Blended(mepo.fonts_normal[p.get(p.pref.fontsize_ui).u], @ptrCast([*c]const u8, row[1]), (types.Color{ .value = 0x000000 }).to_sdl()),
            sdl.TTF_RenderUTF8_Blended(mepo.fonts_normal[p.get(p.pref.fontsize_ui).u], @ptrCast(row[1]), (types.Color{ .value = 0x000000 }).to_sdl()),
        );
        defer sdl.SDL_FreeSurface(surf_val);
        width_label = std.math.max(width_label, surf_lab.w);
        width_value = std.math.max(width_value, surf_val.w);
        width_label = @max(width_label, surf_lab.w);
        width_value = @max(width_value, surf_val.w);
        height_row = surf_lab.h;
    }

@@ -585,13 +588,13 @@ fn blit_table(mepo: *Mepo, x: i32, y: i32, padding: c_int, rows: []const [2][:0]
        .x = x,
        .y = y,
        .w = width_label + width_value + padding * 4,
        .h = height_row * @intCast(c_int, rows.len),
        .h = height_row * @as(c_int, @intCast(rows.len)),
    }, .Fill);
    try utilsdl.sdl_renderer_rect(mepo.renderer, border_color, sdl.SDL_Rect{
        .x = x,
        .y = y,
        .w = width_label + width_value + padding * 4,
        .h = height_row * @intCast(c_int, rows.len),
        .h = height_row * @as(c_int, @intCast(rows.len)),
    }, .Draw);

    // Column divider
@@ -599,20 +602,20 @@ fn blit_table(mepo: *Mepo, x: i32, y: i32, padding: c_int, rows: []const [2][:0]
        .x = x + width_label + padding * 2,
        .y = y,
        .w = 1,
        .h = height_row * @intCast(c_int, rows.len),
        .h = height_row * @as(c_int, @intCast(rows.len)),
    }, .Fill);

    for (rows, 0..) |row, row_i| {
        // Label
        const surf_lab = try utilsdl.errorcheck_ptr(
            sdl.SDL_Surface,
            sdl.TTF_RenderUTF8_Blended(mepo.fonts_bold[p.get(p.pref.fontsize_ui).u], @ptrCast([*c]const u8, row[0]), (types.Color{ .value = 0x000000 }).to_sdl()),
            sdl.TTF_RenderUTF8_Blended(mepo.fonts_bold[p.get(p.pref.fontsize_ui).u], @ptrCast(row[0]), (types.Color{ .value = 0x000000 }).to_sdl()),
        );
        const text_lab = try utilsdl.errorcheck_ptr(sdl.SDL_Texture, sdl.SDL_CreateTextureFromSurface(mepo.renderer, surf_lab));
        defer sdl.SDL_FreeSurface(surf_lab);
        var dest_rect_lab = sdl.SDL_Rect{
            .x = x + padding,
            .y = y + @intCast(c_int, row_i) * height_row,
            .y = y + @as(c_int, @intCast(row_i)) * height_row,
            .w = surf_lab.w,
            .h = surf_lab.h,
        };
@@ -621,13 +624,13 @@ fn blit_table(mepo: *Mepo, x: i32, y: i32, padding: c_int, rows: []const [2][:0]
        // Value
        const surf_value = try utilsdl.errorcheck_ptr(
            sdl.SDL_Surface,
            sdl.TTF_RenderUTF8_Blended(mepo.fonts_normal[p.get(p.pref.fontsize_ui).u], @ptrCast([*c]const u8, row[1]), (types.Color{ .value = 0x000000 }).to_sdl()),
            sdl.TTF_RenderUTF8_Blended(mepo.fonts_normal[p.get(p.pref.fontsize_ui).u], @ptrCast(row[1]), (types.Color{ .value = 0x000000 }).to_sdl()),
        );
        const text_value = try utilsdl.errorcheck_ptr(sdl.SDL_Texture, sdl.SDL_CreateTextureFromSurface(mepo.renderer, surf_value));
        defer sdl.SDL_FreeSurface(surf_value);
        var dest_rect_val = sdl.SDL_Rect{
            .x = x + width_label + padding * 3,
            .y = y + @intCast(c_int, row_i) * height_row,
            .y = y + @as(c_int, @intCast(row_i)) * height_row,
            .w = surf_value.w,
            .h = surf_value.h,
        };
@@ -636,7 +639,7 @@ fn blit_table(mepo: *Mepo, x: i32, y: i32, padding: c_int, rows: []const [2][:0]
        // Horizontal border between rows
        try utilsdl.sdl_renderer_rect(mepo.renderer, border_color, sdl.SDL_Rect{
            .x = x,
            .y = y + @intCast(c_int, row_i) * height_row,
            .y = y + @as(c_int, @intCast(row_i)) * height_row,
            .w = width_label + width_value + padding * 4,
            .h = 1,
        }, .Fill);
@@ -683,13 +686,13 @@ fn blit_multiline_text(

            const text_surf = try utilsdl.errorcheck_ptr(
                sdl.SDL_Surface,
                sdl.TTF_RenderUTF8_Blended(mepo.fonts_normal[font_size], @ptrCast([*c]const u8, line_sentinel_terminated), (types.Color{ .value = color }).to_sdl()),
                sdl.TTF_RenderUTF8_Blended(mepo.fonts_normal[font_size], @ptrCast(line_sentinel_terminated), (types.Color{ .value = color }).to_sdl()),
            );
            defer sdl.SDL_FreeSurface(text_surf);

            const text = try utilsdl.errorcheck_ptr(sdl.SDL_Texture, sdl.SDL_CreateTextureFromSurface(mepo.renderer, text_surf));
            textures_height += text_surf.h;
            textures_width = std.math.max(textures_width, text_surf.w);
            textures_width = @max(textures_width, text_surf.w);
            textures_array[textures_array_size] = text;
            textures_array_size += 1;
        }
@@ -701,7 +704,7 @@ fn blit_multiline_text(
    {
        // Calculate X/Y offsets
        const x_off: c_int = x_off: {
            const bbox_total_w = @intCast(c_int, if (alignment.w) |w| w else mepo.win_w);
            const bbox_total_w: c_int = @intCast(if (alignment.w) |w| w else mepo.win_w);
            const offset = switch (alignment.align_x) {
                .Left => 0,
                .Right => bbox_total_w - textures_width,
@@ -710,7 +713,7 @@ fn blit_multiline_text(
            break :x_off x + offset;
        };
        var y_off: c_int = y_off: {
            const bbox_total_h = @intCast(c_int, if (alignment.h) |h| h else mepo.win_h);
            const bbox_total_h: c_int = @intCast(if (alignment.h) |h| h else mepo.win_h);
            const offset = switch (alignment.align_y) {
                .Top => 0,
                .Bottom => bbox_total_h - textures_height,
@@ -758,7 +761,7 @@ pub fn blit_uibuttons(mepo: *Mepo, determine_click_target: ?sdl.SDL_Point) !?typ

        const surf = try utilsdl.errorcheck_ptr(
            sdl.SDL_Surface,
            sdl.TTF_RenderUTF8_Blended(mepo.fonts_bold[p.get(p.pref.fontsize_ui).u], @ptrCast([*c]const u8, mepo.uibuttons.items[btn_i].text), (types.Color{ .value = 0x000000 }).to_sdl()),
            sdl.TTF_RenderUTF8_Blended(mepo.fonts_bold[p.get(p.pref.fontsize_ui).u], @ptrCast(mepo.uibuttons.items[btn_i].text), (types.Color{ .value = 0x000000 }).to_sdl()),
        );
        defer sdl.SDL_FreeSurface(surf);

@@ -779,8 +782,8 @@ pub fn blit_uibuttons(mepo: *Mepo, determine_click_target: ?sdl.SDL_Point) !?typ
                defer pad_right += bg_w;

                break :target sdl.SDL_Rect{
                    .x = @intCast(c_int, mepo.win_w) - pad_right - bg_w,
                    .y = @intCast(c_int, mepo.win_h) - bg_h - pad_bottom,
                    .x = @as(c_int, @intCast(mepo.win_w)) - pad_right - bg_w,
                    .y = @as(c_int, @intCast(mepo.win_h)) - bg_h - pad_bottom,
                    .w = bg_w + 1,
                    .h = bg_h + 1,
                };
@@ -836,7 +839,7 @@ pub fn blit(mepo: *Mepo) !void {
    try utilsdl.sdl_renderer_rect(
        mepo.renderer,
        .{ .value = 0xffffff },
        .{ .x = 0, .y = 0, .w = @intCast(c_int, mepo.win_w), .h = @intCast(c_int, mepo.win_h) },
        .{ .x = 0, .y = 0, .w = @intCast(mepo.win_w), .h = @intCast(mepo.win_h) },
        .Fill,
    );
    try blit_tiles_all(
@@ -844,8 +847,8 @@ pub fn blit(mepo: *Mepo) !void {
        .{
            .x = 0,
            .y = 0,
            .w = @intCast(c_int, mepo.win_w),
            .h = @intCast(c_int, mepo.win_h),
            .w = @intCast(mepo.win_w),
            .h = @intCast(mepo.win_h),
        },
        p.get(p.pref.zoom).u,
    );
diff --git a/src/types.zig b/src/types.zig
index ae16b4fb59a5..7e2a47eb9ef7 100644
--- a/src/types.zig
+++ b/src/types.zig
@@ -66,9 +66,9 @@ pub const Color = struct {

    fn to_rgba(self: @This()) RGBA {
        return .{
            .r = @intCast(u8, self.value >> 16),
            .g = @intCast(u8, self.value >> 8 & 0xff),
            .b = @intCast(u8, self.value & 0xff),
            .r = @intCast(self.value >> 16),
            .g = @intCast(self.value >> 8 & 0xff),
            .b = @intCast(self.value & 0xff),
            .a = self.opacity,
        };
    }
@@ -79,7 +79,7 @@ pub const Color = struct {
    }

    pub fn to_u32(self: @This()) u32 {
        return @bitCast(u32, self.to_rgba());
        return @bitCast(self.to_rgba());
    }
};

diff --git a/src/util/utilconversion.zig b/src/util/utilconversion.zig
index 0623f63db4f4..52f2bd6815c7 100644
--- a/src/util/utilconversion.zig
+++ b/src/util/utilconversion.zig
@@ -41,7 +41,7 @@ pub fn distance_haversine(distance_unit: enum { Km, Mi }, lat_a: f64, lon_a: f64
}

fn zoom_bit(zoom: u32) f32 {
    return @intToFloat(f32, (lossyCastWithNan(i32, config.Tsize) << lossyCastWithNan(u5, zoom)));
    return @floatFromInt((lossyCastWithNan(i32, config.Tsize) << lossyCastWithNan(u5, zoom)));
}

fn lossyCastWithNan(comptime to_type: type, value: anytype) to_type {
diff --git a/src/util/utilprefs.zig b/src/util/utilprefs.zig
index 37a07ac8cdec..dc172b5feef7 100644
--- a/src/util/utilprefs.zig
+++ b/src/util/utilprefs.zig
@@ -57,80 +57,80 @@ const PrefMapping = struct {
const n_prefs = 38;
pub var prefs_mapping: [n_prefs]PrefMapping = mapping: {
    var mapping: [n_prefs]PrefMapping = undefined;
    mapping[@enumToInt(pref.lat)] = .{ .name = "lat", .value = .{ .f = 40.78392 }, .bounds = .{ .min = -90, .max = 90 }, .desc = "Latitude of the map" };
    mapping[@enumToInt(pref.lon)] = .{ .name = "lon", .value = .{ .f = -73.96442 }, .bounds = .{ .min = -180, .max = 180 }, .desc = "Longitude of the map" };
    mapping[@enumToInt(pref.zoom)] = .{ .name = "zoom", .value = .{
    mapping[@intFromEnum(pref.lat)] = .{ .name = "lat", .value = .{ .f = 40.78392 }, .bounds = .{ .min = -90, .max = 90 }, .desc = "Latitude of the map" };
    mapping[@intFromEnum(pref.lon)] = .{ .name = "lon", .value = .{ .f = -73.96442 }, .bounds = .{ .min = -180, .max = 180 }, .desc = "Longitude of the map" };
    mapping[@intFromEnum(pref.zoom)] = .{ .name = "zoom", .value = .{
        .u = 14,
    }, .bounds = .{ .min = 0, .max = 19 }, .desc = "Zoom level of the map (should be set between 0-19)" };
    mapping[@enumToInt(pref.debug_message_enabled)] = .{ .name = "debug_message_enabled", .value = .{ .b = true }, .desc = "Whether debug overlay message should be shown in the UI" };
    mapping[@enumToInt(pref.distance_unit_tf_km_mi)] = .{ .name = "distance_unit_tf_km_mi", .value = .{ .b = false }, .desc = "Whether to show distance in km (0) or mi (1)" };
    mapping[@enumToInt(pref.overlay_debugbar)] = .{ .name = "overlay_debugbar", .value = .{ .b = true }, .desc = "Whether to show the debug bar" };
    mapping[@enumToInt(pref.help)] = .{ .name = "help", .value = .{ .b = false }, .desc = "Whether to show the help overlay" };
    mapping[@enumToInt(pref.overlay_pindetails)] = .{ .name = "overlay_pindetails", .value = .{ .b = true }, .desc = "Whether to show the pin detail overlay" };
    mapping[@enumToInt(pref.overlay_pindetails_expanded)] = .{ .name = "overlay_pindetails_expanded", .value = .{ .b = true }, .desc = "Whether to show the pin detail overlay as expanded" };
    mapping[@enumToInt(pref.crosshair_size)] = .{ .name = "crosshair_size", .value = .{ .u = 15 }, .bounds = .{ .min = 0, .max = 300 }, .desc = "Pixel size of the crosshairs" };
    mapping[@enumToInt(pref.drag_scale)] = .{ .name = "drag_scale", .value = .{ .u = 2 }, .bounds = .{ .min = 1, .max = 200 }, .desc = "Scale for dragging" };
    mapping[@enumToInt(pref.fontsize_ui)] = .{ .name = "fontsize_ui", .value = .{ .u = 20 }, .bounds = .{ .min = 1, .max = 49 }, .desc = "Size of the font in the UI" };
    mapping[@enumToInt(pref.debug_stderr)] = .{ .name = "debug_stderr", .value = .{ .b = true }, .desc = "Send debug information to STDERR (note the commandline flag `-e` overrides this setting)" };
    mapping[@enumToInt(pref.tile_cache_max_n_transfers)] = .{ .name = "tile_cache_max_n_transfers", .value = .{ .u = 20 }, .bounds = .{ .min = 1, .max = 9999 }, .desc = "Maximum number of concurrent transfers for curl" };
    mapping[@enumToInt(pref.tile_cache_expiry_seconds)] = .{ .name = "tile_cache_expiry_seconds", .value = .{ .f = -1 }, .bounds = .{ .min = -1, .max = 99999999 }, .desc = "Number of seconds before a downloaded tiledata should be considered invalid" };
    mapping[@enumToInt(pref.tile_cache_network)] = .{ .name = "tile_cache_network", .value = .{ .b = true }, .desc = "Whether to download new tiledata from external servers; if 0 that means you're offline" };
    mapping[@enumToInt(pref.tile_cache_dir)] = .{ .name = "tile_cache_dir", .value = .{ .t = null }, .desc = "Path of directory to store the downloaded tiles" };
    mapping[@enumToInt(pref.tile_cache_url)] = .{ .name = "tile_cache_url", .value = .{ .t = null }, .desc = "URL source for the tiles, uses %1/%2/%3 to represent X/Y/Z" };
    mapping[@intFromEnum(pref.debug_message_enabled)] = .{ .name = "debug_message_enabled", .value = .{ .b = true }, .desc = "Whether debug overlay message should be shown in the UI" };
    mapping[@intFromEnum(pref.distance_unit_tf_km_mi)] = .{ .name = "distance_unit_tf_km_mi", .value = .{ .b = false }, .desc = "Whether to show distance in km (0) or mi (1)" };
    mapping[@intFromEnum(pref.overlay_debugbar)] = .{ .name = "overlay_debugbar", .value = .{ .b = true }, .desc = "Whether to show the debug bar" };
    mapping[@intFromEnum(pref.help)] = .{ .name = "help", .value = .{ .b = false }, .desc = "Whether to show the help overlay" };
    mapping[@intFromEnum(pref.overlay_pindetails)] = .{ .name = "overlay_pindetails", .value = .{ .b = true }, .desc = "Whether to show the pin detail overlay" };
    mapping[@intFromEnum(pref.overlay_pindetails_expanded)] = .{ .name = "overlay_pindetails_expanded", .value = .{ .b = true }, .desc = "Whether to show the pin detail overlay as expanded" };
    mapping[@intFromEnum(pref.crosshair_size)] = .{ .name = "crosshair_size", .value = .{ .u = 15 }, .bounds = .{ .min = 0, .max = 300 }, .desc = "Pixel size of the crosshairs" };
    mapping[@intFromEnum(pref.drag_scale)] = .{ .name = "drag_scale", .value = .{ .u = 2 }, .bounds = .{ .min = 1, .max = 200 }, .desc = "Scale for dragging" };
    mapping[@intFromEnum(pref.fontsize_ui)] = .{ .name = "fontsize_ui", .value = .{ .u = 20 }, .bounds = .{ .min = 1, .max = 49 }, .desc = "Size of the font in the UI" };
    mapping[@intFromEnum(pref.debug_stderr)] = .{ .name = "debug_stderr", .value = .{ .b = true }, .desc = "Send debug information to STDERR (note the commandline flag `-e` overrides this setting)" };
    mapping[@intFromEnum(pref.tile_cache_max_n_transfers)] = .{ .name = "tile_cache_max_n_transfers", .value = .{ .u = 20 }, .bounds = .{ .min = 1, .max = 9999 }, .desc = "Maximum number of concurrent transfers for curl" };
    mapping[@intFromEnum(pref.tile_cache_expiry_seconds)] = .{ .name = "tile_cache_expiry_seconds", .value = .{ .f = -1 }, .bounds = .{ .min = -1, .max = 99999999 }, .desc = "Number of seconds before a downloaded tiledata should be considered invalid" };
    mapping[@intFromEnum(pref.tile_cache_network)] = .{ .name = "tile_cache_network", .value = .{ .b = true }, .desc = "Whether to download new tiledata from external servers; if 0 that means you're offline" };
    mapping[@intFromEnum(pref.tile_cache_dir)] = .{ .name = "tile_cache_dir", .value = .{ .t = null }, .desc = "Path of directory to store the downloaded tiles" };
    mapping[@intFromEnum(pref.tile_cache_url)] = .{ .name = "tile_cache_url", .value = .{ .t = null }, .desc = "URL source for the tiles, uses %1/%2/%3 to represent X/Y/Z" };

    mapping[@enumToInt(pref.pingroup_0_ordered)] = .{ .name = "pingroup_0_ordered", .value = .{ .b = false }, .desc = "Whether pingroup 0 should be ordered" };
    mapping[@enumToInt(pref.pingroup_1_ordered)] = .{ .name = "pingroup_1_ordered", .value = .{ .b = false }, .desc = "Whether pingroup 1 should be ordered" };
    mapping[@enumToInt(pref.pingroup_2_ordered)] = .{ .name = "pingroup_2_ordered", .value = .{ .b = false }, .desc = "Whether pingroup 2 should be ordered" };
    mapping[@enumToInt(pref.pingroup_3_ordered)] = .{ .name = "pingroup_3_ordered", .value = .{ .b = false }, .desc = "Whether pingroup 3 should be ordered" };
    mapping[@enumToInt(pref.pingroup_4_ordered)] = .{ .name = "pingroup_4_ordered", .value = .{ .b = false }, .desc = "Whether pingroup 4 should be ordered" };
    mapping[@enumToInt(pref.pingroup_5_ordered)] = .{ .name = "pingroup_5_ordered", .value = .{ .b = false }, .desc = "Whether pingroup 5 should be ordered" };
    mapping[@enumToInt(pref.pingroup_6_ordered)] = .{ .name = "pingroup_6_ordered", .value = .{ .b = false }, .desc = "Whether pingroup 6 should be ordered" };
    mapping[@enumToInt(pref.pingroup_7_ordered)] = .{ .name = "pingroup_7_ordered", .value = .{ .b = false }, .desc = "Whether pingroup 7 should be ordered" };
    mapping[@enumToInt(pref.pingroup_8_ordered)] = .{ .name = "pingroup_8_ordered", .value = .{ .b = false }, .desc = "Whether pingroup 8 should be ordered" };
    mapping[@enumToInt(pref.pingroup_9_ordered)] = .{ .name = "pingroup_9_ordered", .value = .{ .b = false }, .desc = "Whether pingroup 9 should be ordered" };
    mapping[@intFromEnum(pref.pingroup_0_ordered)] = .{ .name = "pingroup_0_ordered", .value = .{ .b = false }, .desc = "Whether pingroup 0 should be ordered" };
    mapping[@intFromEnum(pref.pingroup_1_ordered)] = .{ .name = "pingroup_1_ordered", .value = .{ .b = false }, .desc = "Whether pingroup 1 should be ordered" };
    mapping[@intFromEnum(pref.pingroup_2_ordered)] = .{ .name = "pingroup_2_ordered", .value = .{ .b = false }, .desc = "Whether pingroup 2 should be ordered" };
    mapping[@intFromEnum(pref.pingroup_3_ordered)] = .{ .name = "pingroup_3_ordered", .value = .{ .b = false }, .desc = "Whether pingroup 3 should be ordered" };
    mapping[@intFromEnum(pref.pingroup_4_ordered)] = .{ .name = "pingroup_4_ordered", .value = .{ .b = false }, .desc = "Whether pingroup 4 should be ordered" };
    mapping[@intFromEnum(pref.pingroup_5_ordered)] = .{ .name = "pingroup_5_ordered", .value = .{ .b = false }, .desc = "Whether pingroup 5 should be ordered" };
    mapping[@intFromEnum(pref.pingroup_6_ordered)] = .{ .name = "pingroup_6_ordered", .value = .{ .b = false }, .desc = "Whether pingroup 6 should be ordered" };
    mapping[@intFromEnum(pref.pingroup_7_ordered)] = .{ .name = "pingroup_7_ordered", .value = .{ .b = false }, .desc = "Whether pingroup 7 should be ordered" };
    mapping[@intFromEnum(pref.pingroup_8_ordered)] = .{ .name = "pingroup_8_ordered", .value = .{ .b = false }, .desc = "Whether pingroup 8 should be ordered" };
    mapping[@intFromEnum(pref.pingroup_9_ordered)] = .{ .name = "pingroup_9_ordered", .value = .{ .b = false }, .desc = "Whether pingroup 9 should be ordered" };

    mapping[@enumToInt(pref.pingroup_0_color)] = .{ .name = "pingroup_0_color", .value = .{ .u24 = 0x0 }, .desc = "Color to indicate pingroup 0" };
    mapping[@enumToInt(pref.pingroup_1_color)] = .{ .name = "pingroup_1_color", .value = .{ .u24 = 0x0 }, .desc = "Color to indicate pingroup 1" };
    mapping[@enumToInt(pref.pingroup_2_color)] = .{ .name = "pingroup_2_color", .value = .{ .u24 = 0x0 }, .desc = "Color to indicate pingroup 2" };
    mapping[@enumToInt(pref.pingroup_3_color)] = .{ .name = "pingroup_3_color", .value = .{ .u24 = 0x0 }, .desc = "Color to indicate pingroup 3" };
    mapping[@enumToInt(pref.pingroup_4_color)] = .{ .name = "pingroup_4_color", .value = .{ .u24 = 0x0 }, .desc = "Color to indicate pingroup 4" };
    mapping[@enumToInt(pref.pingroup_5_color)] = .{ .name = "pingroup_5_color", .value = .{ .u24 = 0x0 }, .desc = "Color to indicate pingroup 5" };
    mapping[@enumToInt(pref.pingroup_6_color)] = .{ .name = "pingroup_6_color", .value = .{ .u24 = 0x0 }, .desc = "Color to indicate pingroup 6" };
    mapping[@enumToInt(pref.pingroup_7_color)] = .{ .name = "pingroup_7_color", .value = .{ .u24 = 0x0 }, .desc = "Color to indicate pingroup 7" };
    mapping[@enumToInt(pref.pingroup_8_color)] = .{ .name = "pingroup_8_color", .value = .{ .u24 = 0x0 }, .desc = "Color to indicate pingroup 8" };
    mapping[@enumToInt(pref.pingroup_9_color)] = .{ .name = "pingroup_9_color", .value = .{ .u24 = 0x0 }, .desc = "Color to indicate pingroup 9" };
    mapping[@intFromEnum(pref.pingroup_0_color)] = .{ .name = "pingroup_0_color", .value = .{ .u24 = 0x0 }, .desc = "Color to indicate pingroup 0" };
    mapping[@intFromEnum(pref.pingroup_1_color)] = .{ .name = "pingroup_1_color", .value = .{ .u24 = 0x0 }, .desc = "Color to indicate pingroup 1" };
    mapping[@intFromEnum(pref.pingroup_2_color)] = .{ .name = "pingroup_2_color", .value = .{ .u24 = 0x0 }, .desc = "Color to indicate pingroup 2" };
    mapping[@intFromEnum(pref.pingroup_3_color)] = .{ .name = "pingroup_3_color", .value = .{ .u24 = 0x0 }, .desc = "Color to indicate pingroup 3" };
    mapping[@intFromEnum(pref.pingroup_4_color)] = .{ .name = "pingroup_4_color", .value = .{ .u24 = 0x0 }, .desc = "Color to indicate pingroup 4" };
    mapping[@intFromEnum(pref.pingroup_5_color)] = .{ .name = "pingroup_5_color", .value = .{ .u24 = 0x0 }, .desc = "Color to indicate pingroup 5" };
    mapping[@intFromEnum(pref.pingroup_6_color)] = .{ .name = "pingroup_6_color", .value = .{ .u24 = 0x0 }, .desc = "Color to indicate pingroup 6" };
    mapping[@intFromEnum(pref.pingroup_7_color)] = .{ .name = "pingroup_7_color", .value = .{ .u24 = 0x0 }, .desc = "Color to indicate pingroup 7" };
    mapping[@intFromEnum(pref.pingroup_8_color)] = .{ .name = "pingroup_8_color", .value = .{ .u24 = 0x0 }, .desc = "Color to indicate pingroup 8" };
    mapping[@intFromEnum(pref.pingroup_9_color)] = .{ .name = "pingroup_9_color", .value = .{ .u24 = 0x0 }, .desc = "Color to indicate pingroup 9" };

    break :mapping mapping;
};

pub fn get(prefname: pref) PrefValueUnion {
    return prefs_mapping[@enumToInt(prefname)].value;
    return prefs_mapping[@intFromEnum(prefname)].value;
}

pub fn set_n(prefname: pref, value: f64) void {
    var bounded_value: f64 = value;
    if (prefs_mapping[@enumToInt(prefname)].bounds) |bounds| {
    if (prefs_mapping[@intFromEnum(prefname)].bounds) |bounds| {
        if (bounded_value > bounds.max) bounded_value = bounds.max;
        if (bounded_value < bounds.min) bounded_value = bounds.min;
    }

    switch (prefs_mapping[@enumToInt(prefname)].value) {
    switch (prefs_mapping[@intFromEnum(prefname)].value) {
        .b => {
            prefs_mapping[@enumToInt(prefname)].value = .{ .b = value == 1 };
            prefs_mapping[@intFromEnum(prefname)].value = .{ .b = value == 1 };
        },
        .u => {
            if (bounded_value > std.math.maxInt(u8)) bounded_value = std.math.maxInt(u8);
            if (bounded_value < std.math.minInt(u8)) bounded_value = std.math.minInt(u8);
            prefs_mapping[@enumToInt(prefname)].value = .{ .u = @floatToInt(u8, bounded_value) };
            prefs_mapping[@intFromEnum(prefname)].value = .{ .u = @intFromFloat(bounded_value) };
        },
        .f => {
            prefs_mapping[@enumToInt(prefname)].value = .{ .f = value };
            prefs_mapping[@intFromEnum(prefname)].value = .{ .f = value };
        },
        .t => {},
        .u24 => {
            if (bounded_value > std.math.maxInt(u24)) bounded_value = std.math.maxInt(u24);
            if (bounded_value < std.math.minInt(u24)) bounded_value = std.math.minInt(u24);
            prefs_mapping[@enumToInt(prefname)].value = .{ .u24 = @floatToInt(u24, value) };
            prefs_mapping[@intFromEnum(prefname)].value = .{ .u24 = @intFromFloat(value) };
        },
    }
}
@@ -160,8 +160,8 @@ pub fn pingroup_prop(pingroup: usize, prop: enum { Ordered, Color }) pref {
}

pub fn set_t(allocator: std.mem.Allocator, prefname: pref, value: []const u8) !void {
    if (prefs_mapping[@enumToInt(prefname)].value.t) |heap_allocated_text| allocator.free(heap_allocated_text);
    prefs_mapping[@enumToInt(prefname)].value.t = try allocator.dupeZ(u8, value);
    if (prefs_mapping[@intFromEnum(prefname)].value.t) |heap_allocated_text| allocator.free(heap_allocated_text);
    prefs_mapping[@intFromEnum(prefname)].value.t = try allocator.dupeZ(u8, value);
}

pub fn lookup_pref_from_string(prefname: []const u8) ?pref {
@@ -172,9 +172,9 @@ pub fn lookup_pref_from_string(prefname: []const u8) ?pref {
}

pub fn toggle_bool(prefname: pref) void {
    switch (prefs_mapping[@enumToInt(prefname)].value) {
    switch (prefs_mapping[@intFromEnum(prefname)].value) {
        .b => {
            prefs_mapping[@enumToInt(prefname)].value = .{ .b = !prefs_mapping[@enumToInt(prefname)].value.b };
            prefs_mapping[@intFromEnum(prefname)].value = .{ .b = !prefs_mapping[@intFromEnum(prefname)].value.b };
        },
        else => {},
    }
diff --git a/src/util/utilsdl.zig b/src/util/utilsdl.zig
index fce5d19748e9..0ff05014b58f 100644
--- a/src/util/utilsdl.zig
+++ b/src/util/utilsdl.zig
@@ -37,7 +37,7 @@ pub fn sdl_push_event_mepolang_execution(mepolang_text: [:0]const u8) void {
    const str_ptr = &mepolang_text[0];
    sdlevent.type = sdl_usereventtype(.Mepolang);
    sdlevent.user.code = 0;
    sdlevent.user.data1 = @intToPtr([*c]u8, @ptrToInt(str_ptr));
    sdlevent.user.data1 = @ptrFromInt(@intFromPtr(str_ptr));
    sdlevent.user.data2 = null;
    errorcheck(sdl.SDL_PushEvent(&sdlevent)) catch |err| {
        utildbg.log("Error pushing mepolang event to SDL queue: {any}\n", .{err});
@@ -54,7 +54,7 @@ pub fn sdl_push_event_signal(signal: c_int) callconv(.C) void {
pub fn sdl_renderer_set_draw_color(renderer: *sdl.SDL_Renderer, color: types.Color) errors.SDLError!void {
    var sdl_color = color.to_sdl();
    const blend_mode = if (sdl_color.a != sdl.SDL_ALPHA_OPAQUE) sdl.SDL_BLENDMODE_ADD else sdl.SDL_BLENDMODE_NONE;
    try errorcheck(sdl.SDL_SetRenderDrawBlendMode(renderer, @intCast(c_uint, blend_mode)));
    try errorcheck(sdl.SDL_SetRenderDrawBlendMode(renderer, @intCast(blend_mode)));
    try errorcheck(sdl.SDL_SetRenderDrawColor(renderer, sdl_color.r, sdl_color.g, sdl_color.b, color.opacity));
}

-- 
2.41.0