~mil/mepo

67ca3f3b0a52e85b1ac22543680807574ace7603 — Miles Alan a month ago 0231127
Migrate pingroup properties (color/ordered) to preferences system

For now hardcoding 10 pingroups; anything more then that is probably a bit
excessive in anycase and is there actually a valid usecase (?). Doing things
with single preferences system simplies things for save state rather then
implmenting extra functions (pingroup set t/n) and extra save/restore state
logic.
M src/Mepo.zig => src/Mepo.zig +15 -16
@@ 31,7 31,7 @@ fonts_bold: [50]*sdl.TTF_Font,
idle_mutex: std.Thread.Mutex,
pin_group_active: u8 = 0,
pin_group_active_item: ?u32 = null,
pin_groups: [10]types.PinGroup = undefined,
pin_groups: [10]std.ArrayList(types.Pin) = undefined,
renderer: *sdl.SDL_Renderer = undefined,
renderer_sw: bool,
table_gestures: std.array_hash_map.AutoArrayHashMap(types.GestureInput, []const u8),


@@ 198,7 198,7 @@ fn blit_overlay_pindetails(mepo: *@This()) !void {

    var al = std.ArrayList([2][:0]const u8).init(arena.allocator());
    if (mepo.pin_group_active_item) |_| {
        const pin = mepo.pin_groups[mepo.pin_group_active].pins.items[mepo.pin_group_active_item.?];
        const pin = mepo.pin_groups[mepo.pin_group_active].items[mepo.pin_group_active_item.?];
        const pin_coords_str = try std.fmt.allocPrintZ(arena.allocator(), "{d:.5} lat / {d:.5} lon", .{ pin.lat, pin.lon });
        const distance_str = try std.fmt.allocPrintZ(arena.allocator(), "{d:.2}{s} away", .{
            utilconversion.distance_haversine(


@@ 225,7 225,7 @@ fn blit_overlay_pindetails(mepo: *@This()) !void {
            "{d} of {d} ",
            .{
                mepo.pin_group_active_item.? + 1,
                mepo.pin_groups[mepo.pin_group_active].pins.items.len,
                mepo.pin_groups[mepo.pin_group_active].items.len,
            },
        ) })[0..].*);
        try al.append(([_][:0]const u8{ "Dist:", distance_str })[0..].*);


@@ 291,7 291,7 @@ fn blit_pin(mepo: *@This(), pin: types.Pin, prev_pin: ?types.Pin, pin_group: u8,
    }

    // Draw connecting line for ordered pin group
    if (mepo.pin_groups[pin_group].ordered and prev_pin != null) {
    if (p.get(p.pingroup_prop(pin_group, .Ordered)).b and prev_pin != null) {
        try utilsdl.errorcheck(sdl.aalineColor(
            mepo.renderer,
            @intCast(i16, prev_pin_x),


@@ 307,19 307,21 @@ fn blit_pins(mepo: *@This()) !void {
    for (mepo.pin_groups) |pin_group, pin_group_i| {
        var ordered_group_active_path = false;
        var prev_pin: ?*types.Pin = null;
        for (pin_group.pins.items) |*pin, pin_i| {
        for (pin_group.items) |*pin, pin_i| {
            defer prev_pin = pin;

            const is_ordered = p.get(p.pingroup_prop(pin_group_i, .Ordered)).b;
            const pg_color = p.get(p.pingroup_prop(pin_group_i, .Color)).u24;
            const color = if (pin_group_i == mepo.pin_group_active and mepo.pin_group_active_item != null and
                ((mepo.pin_groups[pin_group_i].ordered and ordered_group_active_path) or
                (!mepo.pin_groups[pin_group_i].ordered and mepo.pin_group_active_item.? == pin_i))) 0xff0000 else pin_group.color;
                ((is_ordered and ordered_group_active_path) or
                (!is_ordered and mepo.pin_group_active_item.? == pin_i))) 0xff0000 else pg_color;

            // E.g. ordered_group_active_path just tracks every time
            // an instructive pin is hit so subsequent structural pins
            // highlighted in "active"/red color until next structural pin
            // hit and full "path" of the active segment of an ordered pin
            // group shows continuously as the active color
            if (mepo.pin_group_active_item != null and mepo.pin_groups[pin_group_i].pins.items[pin_i].category == .Instructive)
            if (mepo.pin_group_active_item != null and mepo.pin_groups[pin_group_i].items[pin_i].category == .Instructive)
                ordered_group_active_path = mepo.pin_group_active_item.? == pin_i;

            try mepo.blit_pin(


@@ 334,7 336,7 @@ fn blit_pins(mepo: *@This()) !void {

    if (mepo.pin_group_active_item) |active_pin_i| {
        try mepo.blit_pin(
            mepo.pin_groups[mepo.pin_group_active].pins.items[active_pin_i],
            mepo.pin_groups[mepo.pin_group_active].items[active_pin_i],
            null,
            mepo.pin_group_active,
            .{ .value = 0xff0000 },


@@ 456,7 458,7 @@ fn blit_uibuttons(mepo: *@This(), determine_click_target: ?sdl.SDL_MouseButtonEv
    while (btn_i > 0) {
        btn_i -= 1;

        if (mepo.uibuttons.items[btn_i].only_visible_when_has_pins and mepo.pin_groups[mepo.pin_group_active].pins.items.len == 0)
        if (mepo.uibuttons.items[btn_i].only_visible_when_has_pins and mepo.pin_groups[mepo.pin_group_active].items.len == 0)
            continue;

        const surf = try utilsdl.errorcheck_ptr(


@@ 667,7 669,7 @@ fn event_mousebuttonup(mepo: *@This(), e: sdl.SDL_Event) types.Pending {
            delta_dist: i32,
        } = null;
        for (mepo.pin_groups) |pin_group, pin_group_i| {
            for (pin_group.pins.items) |*item, pin_i| {
            for (pin_group.items) |*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);


@@ 1382,11 1384,8 @@ pub fn init(allocator: std.mem.Allocator, tile_cache: *TileCache, use_config: []
        .table_clicks = std.array_hash_map.AutoArrayHashMap(types.ClickInput, []const u8).init(allocator),
        .table_signals = std.array_hash_map.AutoArrayHashMap(u6, []const u8).init(allocator),
        .pin_groups = pin_groups: {
            var pgs: [10]types.PinGroup = undefined;
            for (pgs) |*g| {
                g.ordered = true;
                g.pins = std.ArrayList(types.Pin).init(allocator);
            }
            var pgs: [10]std.ArrayList(types.Pin) = undefined;
            for (pgs) |_, i| pgs[i] = std.ArrayList(types.Pin).init(allocator);
            break :pin_groups pgs;
        },
        .tile_cache = tile_cache,

M src/api/_FnTable.zig => src/api/_FnTable.zig +1 -2
@@ 21,9 21,8 @@ pub const fn_specs = &[_]types.MepoFnSpec{
    @import("pin_add.zig").spec,
    @import("pin_cycle.zig").spec,
    @import("pin_deactivate.zig").spec,
    @import("pin_groupactivate.zig").spec,
    @import("pin_delete.zig").spec,
    @import("pin_groupprefset_n.zig").spec,
    @import("pin_groupprefset_t.zig").spec,
    @import("pin_meta.zig").spec,
    @import("pin_purge.zig").spec,
    @import("prefinc.zig").spec,

M src/api/center_on_pin.zig => src/api/center_on_pin.zig +1 -1
@@ 12,7 12,7 @@ pub const spec = .{

fn execute(mepo: *Mepo, _: [types.MepoFnNargs]types.MepoArg) !void {
    if (mepo.pin_group_active_item != null) {
        const pin = mepo.pin_groups[mepo.pin_group_active].pins.items[mepo.pin_group_active_item.?];
        const pin = mepo.pin_groups[mepo.pin_group_active].items[mepo.pin_group_active_item.?];
        p.set_n(p.pref.lat, pin.lat);
        p.set_n(p.pref.lon, pin.lon);
    }

M src/api/filedump.zig => src/api/filedump.zig +12 -5
@@ 31,18 31,25 @@ fn filedump(mepo: *Mepo, filepath: []const u8) !void {

    // 1. Preferences
    for (utilprefs.prefs_mapping) |pref| {
        var is_text = false;
        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),
          .u24 => v: { format_as = .HexText; break :v -1; },
          .f => pref.value.f,
          .t => v: { is_text = true; break :v -1; },
          .t => v: { format_as = .Text; break :v -1; },
        };
        const prefset_statement = statement: {
            if (is_text) {
            if (format_as == .Text) {
                break :statement try std.fmt.allocPrint(arena.allocator(), "prefset_t {s} [{s}];", .{pref.name, pref.value.t});
            } else {
            } else if (format_as == .Number) {
                break :statement try std.fmt.allocPrint(arena.allocator(), "prefset_n {s} {d:0.02};", .{pref.name, numerical_val});
            } else if (format_as == .HexText) {
                // TODO
                break :statement "#000000";
            } else {
                break :statement "";
            }
        };
        try lines.append(prefset_statement);


@@ 56,7 63,7 @@ fn filedump(mepo: *Mepo, filepath: []const u8) !void {

    // 7. Pins data
    for (mepo.pin_groups) |pg, pg_i| {
        for (pg.pins.items) |pin| {
        for (pg.items) |pin| {
            const statement_pin_add = try std.fmt.allocPrint(
                arena.allocator(),
                "pin_add {d} {d} {d:.4} {d:.4} [{s}] [{s}];",

M src/api/pin_add.zig => src/api/pin_add.zig +1 -1
@@ 30,7 30,7 @@ fn pin_add(mepo: *Mepo, group: i32, is_structural: bool, lat: f64, lon: f64, han
    const pin_group = if (group < 0) mepo.pin_group_active else @intCast(usize, group);
    if (name.len == 0) return;

    try mepo.pin_groups[pin_group].pins.append(.{
    try mepo.pin_groups[pin_group].append(.{
        .lat = lat,
        .lon = lon,
        .handle = if (handle.len == 0) null else try mepo.allocator.dupeZ(u8, handle),

M src/api/pin_cycle.zig => src/api/pin_cycle.zig +6 -5
@@ 1,6 1,7 @@
const Mepo = @import("../Mepo.zig");
const types = @import("../types.zig");
const std = @import("std");
const p = @import("../util/utilprefs.zig");

pub const spec = .{
    .name = "pin_cycle",


@@ 19,7 20,7 @@ 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].pins.items.len == 0) return;
    if (mepo.pin_groups[mepo.pin_group_active].items.len == 0) return;

    const add: i32 = if (delta > 0) 1 else -1;
    var delta_current = delta;


@@ 31,20 32,20 @@ pub fn pin_cycle(mepo: *Mepo, viewport_only: bool, delta: i32) !void {
        // 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].pins.items.len and
            ((mepo.pin_groups[mepo.pin_group_active].ordered and mepo.pin_groups[mepo.pin_group_active].pins.items[@intCast(usize, pin_i)].category == .Structural) or
        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))))
            pin_i += add;

        if (pin_i > -1 and
            mepo.pin_groups[mepo.pin_group_active].pins.items.len > pin_i 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);
    }
}

fn pin_in_viewport(mepo: *Mepo, pin_index: i32) bool {
    const pin = mepo.pin_groups[mepo.pin_group_active].pins.items[@intCast(usize, pin_index)];
    const pin = mepo.pin_groups[mepo.pin_group_active].items[@intCast(usize, 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;

M src/api/pin_delete.zig => src/api/pin_delete.zig +2 -2
@@ 17,13 17,13 @@ fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
}

fn pin_delete(mepo: *Mepo, handle: [:0]const u8) !void {
    for (mepo.pin_groups[mepo.pin_group_active].pins.items) |pin, pin_i| {
    for (mepo.pin_groups[mepo.pin_group_active].items) |pin, pin_i| {
        if (pin.handle == null) continue;

        if (std.mem.eql(u8, pin.handle.?, handle)) {
            mepo.allocator.free(pin.name);
            mepo.allocator.free(pin.handle.?);
            _ = mepo.pin_groups[mepo.pin_group_active].pins.orderedRemove(pin_i);
            _ = mepo.pin_groups[mepo.pin_group_active].orderedRemove(pin_i);
            return;
        }
    }

D src/api/pin_groupprefset_n.zig => src/api/pin_groupprefset_n.zig +0 -30
@@ 1,30 0,0 @@
const Mepo = @import("../Mepo.zig");
const types = @import("../types.zig");
const std = @import("std");
const pin_cycle = @import("pin_cycle.zig").pin_cycle;

pub const spec = .{
    .name = "pin_groupprefset_n",
    .desc = "Set a pin group's number preference",
    // TODO: figure out doc table/mapping here..
    .args = (&[_]types.MepoFnSpecArg{
        .{ .tag = .Number, .name = "group_number", .desc = "The pin group number" },
        .{ .tag = .Text, .name = "prefrence_name", .desc = "Preference name" },
        .{ .tag = .Number, .name = "prefrence_value", .desc = "Preference number value" },
    })[0..],
    .execute = execute,
};

fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const group_number = @floatToInt(usize, args[0].Number);
    const prefname = args[1].Text;
    const value = args[2].Number;

    if (std.mem.eql(u8, prefname, "ordered")) {
        mepo.pin_groups[group_number].ordered = value == 1;
    } else if (std.mem.eql(u8, prefname, "active") and value == 1) {
        mepo.pin_group_active_item = null;
        mepo.pin_group_active = @intCast(u8, group_number);
        try pin_cycle(mepo, false, 1);
    }
}

D src/api/pin_groupprefset_t.zig => src/api/pin_groupprefset_t.zig +0 -28
@@ 1,28 0,0 @@
const Mepo = @import("../Mepo.zig");
const types = @import("../types.zig");
const std = @import("std");
const pin_cycle = @import("pin_cycle.zig").pin_cycle;

pub const spec = .{
    .name = "pin_groupprefset_t",
    .desc = "Set a pin group's text preference",
    // TODO: figure out doc table/mapping here..
    .args = (&[_]types.MepoFnSpecArg{
        .{ .tag = .Number, .name = "group_number", .desc = "The pin group number" },
        .{ .tag = .Text, .name = "prefrence_name", .desc = "Preference name" },
        .{ .tag = .Text, .name = "prefrence_value", .desc = "Preference text value" },
    })[0..],
    .execute = execute,
};

fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const group_number = @floatToInt(usize, args[0].Number);
    const prefname = args[1].Text;
    const value = args[2].Text;

    if (std.mem.eql(u8, prefname, "color")) {
        if (value.len < 1 or value[0] != '#') return error.InvalidColor;
        const color = try std.fmt.parseUnsigned(u24, value[1..], 16);
        mepo.pin_groups[group_number].color = color;
    }
}

M src/api/pin_meta.zig => src/api/pin_meta.zig +1 -1
@@ 26,7 26,7 @@ pub fn pin_meta(mepo: *Mepo, group: i32, handle: [:0]const u8, key: [:0]const u8
    const pin_group = if (group < 0) mepo.pin_group_active else @intCast(usize, group);

    var pin: *types.Pin = pin: {
        for (mepo.pin_groups[pin_group].pins.items) |*pin| {
        for (mepo.pin_groups[pin_group].items) |*pin| {
            if (pin.handle != null and std.mem.eql(u8, pin.handle.?, handle)) {
                break :pin pin;
            }

M src/api/pin_purge.zig => src/api/pin_purge.zig +2 -2
@@ 12,9 12,9 @@ pub const spec = .{
fn execute(mepo: *Mepo, _: [types.MepoFnNargs]types.MepoArg) !void {
    mepo.pin_group_active_item = null;

    for (mepo.pin_groups[mepo.pin_group_active].pins.items) |p| {
    for (mepo.pin_groups[mepo.pin_group_active].items) |p| {
        mepo.allocator.free(p.name);
        if (p.handle != null) mepo.allocator.free(p.handle.?);
    }
    mepo.pin_groups[mepo.pin_group_active].pins.clearAndFree();
    mepo.pin_groups[mepo.pin_group_active].clearAndFree();
}

M src/api/prefset_t.zig => src/api/prefset_t.zig +5 -0
@@ 25,5 25,10 @@ fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
        try mepo.tile_cache.set_cache_dir(value);
    } else if (std.mem.eql(u8, "tile_cache_url", name)) {
        try mepo.tile_cache.set_cache_url(value);
    } else if (std.mem.startsWith(u8, name, "pingroup_") and std.mem.endsWith(u8, name, "_color")) {
        //if (value.len < 1 or value[0] != '#') return error.InvalidColor;
        //const color = try std.fmt.parseUnsigned(u24, value[1..], 16);
        //mepo.pin_groups[group_number].color = color;
    }

}

M src/config.zig => src/config.zig +11 -8
@@ 120,14 120,17 @@ pub const DefaultBaseConfig =
    \\ prefset_n tile_cache_expiry_seconds 2592000;
    \\ prefset_t tile_cache_dir $XDG_CACHE_HOME/mepo/tiles;
    \\ prefset_t tile_cache_url https://tile.openstreetmap.org/%3$d/%1$d/%2$d.png;
    \\
    \\ # Pins;
    \\ pin_groupprefset_t 0 color #0000ff;
    \\ pin_groupprefset_t 1 color #116e0e;
    \\ pin_groupprefset_t 2 color #7502ab;
    \\ pin_groupprefset_t 3 color #cf0064;
    \\ pin_groupprefset_t 4 color #b89600;
    \\
    \\ prefset_t pingroup_0_color #0000ff;
    \\ prefset_t pingroup_1_color #116e0e;
    \\ prefset_t pingroup_2_color #7502ab;
    \\ prefset_t pingroup_3_color #cf0064;
    \\ prefset_t pingroup_4_color #b89600;
    \\ prefset_n pingroup_0_ordered 0;
    \\ prefset_n pingroup_1_ordered 0;
    \\ prefset_n pingroup_2_ordered 0;
    \\ prefset_n pingroup_3_ordered 0;
    \\ prefset_n pingroup_4_ordered 0;
    \\ 
    \\ # User-customizable bookmarks / arbitrary mepolang file;
    \\ fileload $XDG_CONFIG_HOME/mepo/config;
;

M src/util/utilprefs.zig => src/util/utilprefs.zig +71 -3
@@ 1,7 1,7 @@
const std = @import("std");

const PrefValueEnum = enum { b, t, f, u };
const PrefValueUnion = union(PrefValueEnum) { b: bool, t: ?[:0]const u8, f: f64, u: u8 };
const PrefValueEnum = enum { b, t, f, u, u24 };
const PrefValueUnion = union(PrefValueEnum) { b: bool, t: ?[:0]const u8, f: f64, u: u8, u24: u24 };

pub const pref = enum(u8) {
    lat,


@@ 22,6 22,26 @@ pub const pref = enum(u8) {
    tile_cache_network,
    tile_cache_dir,
    tile_cache_url,
    pingroup_0_ordered,
    pingroup_1_ordered,
    pingroup_2_ordered,
    pingroup_3_ordered,
    pingroup_4_ordered,
    pingroup_5_ordered,
    pingroup_6_ordered,
    pingroup_7_ordered,
    pingroup_8_ordered,
    pingroup_9_ordered,
    pingroup_0_color,
    pingroup_1_color,
    pingroup_2_color,
    pingroup_3_color,
    pingroup_4_color,
    pingroup_5_color,
    pingroup_6_color,
    pingroup_7_color,
    pingroup_8_color,
    pingroup_9_color,
};

const PrefMapping = struct {


@@ 30,7 50,7 @@ const PrefMapping = struct {
    desc: []const u8,
};

const n_prefs = 18;
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 }, .desc = "TODO" };


@@ 51,6 71,29 @@ pub var prefs_mapping: [n_prefs]PrefMapping = mapping: {
    mapping[@enumToInt(pref.tile_cache_network)] = .{ .name = "tile_cache_network", .value = .{ .b = true }, .desc = "TODO" };
    mapping[@enumToInt(pref.tile_cache_dir)] = .{ .name = "tile_cache_dir", .value = .{ .t = null }, .desc = "TODO" };
    mapping[@enumToInt(pref.tile_cache_url)] = .{ .name = "tile_cache_url", .value = .{ .t = null }, .desc = "TODO" };

    mapping[@enumToInt(pref.pingroup_0_ordered)] = .{ .name = "pingroup_0_ordered", .value = .{ .b = false }, .desc = "TODO" };
    mapping[@enumToInt(pref.pingroup_1_ordered)] = .{ .name = "pingroup_1_ordered", .value = .{ .b = false }, .desc = "TODO" };
    mapping[@enumToInt(pref.pingroup_2_ordered)] = .{ .name = "pingroup_2_ordered", .value = .{ .b = false }, .desc = "TODO" };
    mapping[@enumToInt(pref.pingroup_3_ordered)] = .{ .name = "pingroup_3_ordered", .value = .{ .b = false }, .desc = "TODO" };
    mapping[@enumToInt(pref.pingroup_4_ordered)] = .{ .name = "pingroup_4_ordered", .value = .{ .b = false }, .desc = "TODO" };
    mapping[@enumToInt(pref.pingroup_5_ordered)] = .{ .name = "pingroup_5_ordered", .value = .{ .b = false }, .desc = "TODO" };
    mapping[@enumToInt(pref.pingroup_6_ordered)] = .{ .name = "pingroup_6_ordered", .value = .{ .b = false }, .desc = "TODO" };
    mapping[@enumToInt(pref.pingroup_7_ordered)] = .{ .name = "pingroup_7_ordered", .value = .{ .b = false }, .desc = "TODO" };
    mapping[@enumToInt(pref.pingroup_8_ordered)] = .{ .name = "pingroup_8_ordered", .value = .{ .b = false }, .desc = "TODO" };
    mapping[@enumToInt(pref.pingroup_9_ordered)] = .{ .name = "pingroup_9_ordered", .value = .{ .b = false }, .desc = "TODO" };

    mapping[@enumToInt(pref.pingroup_0_color)] = .{ .name = "pingroup_0_color", .value = .{ .u24 = 0x0 }, .desc = "TODO" };
    mapping[@enumToInt(pref.pingroup_1_color)] = .{ .name = "pingroup_1_color", .value = .{ .u24 = 0x0 }, .desc = "TODO" };
    mapping[@enumToInt(pref.pingroup_2_color)] = .{ .name = "pingroup_2_color", .value = .{ .u24 = 0x0 }, .desc = "TODO" };
    mapping[@enumToInt(pref.pingroup_3_color)] = .{ .name = "pingroup_3_color", .value = .{ .u24 = 0x0 }, .desc = "TODO" };
    mapping[@enumToInt(pref.pingroup_4_color)] = .{ .name = "pingroup_4_color", .value = .{ .u24 = 0x0 }, .desc = "TODO" };
    mapping[@enumToInt(pref.pingroup_5_color)] = .{ .name = "pingroup_5_color", .value = .{ .u24 = 0x0 }, .desc = "TODO" };
    mapping[@enumToInt(pref.pingroup_6_color)] = .{ .name = "pingroup_6_color", .value = .{ .u24 = 0x0 }, .desc = "TODO" };
    mapping[@enumToInt(pref.pingroup_7_color)] = .{ .name = "pingroup_7_color", .value = .{ .u24 = 0x0 }, .desc = "TODO" };
    mapping[@enumToInt(pref.pingroup_8_color)] = .{ .name = "pingroup_8_color", .value = .{ .u24 = 0x0 }, .desc = "TODO" };
    mapping[@enumToInt(pref.pingroup_9_color)] = .{ .name = "pingroup_9_color", .value = .{ .u24 = 0x0 }, .desc = "TODO" };

    break :mapping mapping;
};



@@ 70,9 113,34 @@ pub fn set_n(prefname: pref, value: f64) void {
            prefs_mapping[@enumToInt(prefname)].value = .{ .f = value };
        },
        .t => {},
        .u24 => {},
    }
}

pub fn pingroup_prop(pingroup: usize, prop: enum{Ordered, Color}) pref {
    if (pingroup == 0 and prop == .Ordered) return pref.pingroup_0_ordered;
    if (pingroup == 1 and prop == .Ordered) return pref.pingroup_1_ordered;
    if (pingroup == 2 and prop == .Ordered) return pref.pingroup_2_ordered;
    if (pingroup == 3 and prop == .Ordered) return pref.pingroup_3_ordered;
    if (pingroup == 4 and prop == .Ordered) return pref.pingroup_4_ordered;
    if (pingroup == 5 and prop == .Ordered) return pref.pingroup_5_ordered;
    if (pingroup == 6 and prop == .Ordered) return pref.pingroup_6_ordered;
    if (pingroup == 7 and prop == .Ordered) return pref.pingroup_7_ordered;
    if (pingroup == 8 and prop == .Ordered) return pref.pingroup_8_ordered;
    if (pingroup == 9 and prop == .Ordered) return pref.pingroup_9_ordered;
    if (pingroup == 0 and prop == .Color) return pref.pingroup_0_color;
    if (pingroup == 1 and prop == .Color) return pref.pingroup_1_color;
    if (pingroup == 2 and prop == .Color) return pref.pingroup_2_color;
    if (pingroup == 3 and prop == .Color) return pref.pingroup_3_color;
    if (pingroup == 4 and prop == .Color) return pref.pingroup_4_color;
    if (pingroup == 5 and prop == .Color) return pref.pingroup_5_color;
    if (pingroup == 6 and prop == .Color) return pref.pingroup_6_color;
    if (pingroup == 7 and prop == .Color) return pref.pingroup_7_color;
    if (pingroup == 8 and prop == .Color) return pref.pingroup_8_color;
    if (pingroup == 9 and prop == .Color) return pref.pingroup_9_color;
    return pref.lat;
}

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);