Selaa lähdekoodia

Add multi_converter from https://github.com/xMasterX/all-the-plugins

git-subtree-dir: multi_converter
git-subtree-mainline: eb9e72b2c6f496dfd0f62bf40ca9e83c2c5e2556
git-subtree-split: 8ff2e4f3af2eb0ddd7979b7e5537851e7ad64b5d
Willy-JL 2 vuotta sitten
vanhempi
commit
a998c03b16

+ 1 - 0
multi_converter/.gitsubtree

@@ -0,0 +1 @@
+https://github.com/xMasterX/all-the-plugins dev base_pack/multi_converter

+ 14 - 0
multi_converter/application.fam

@@ -0,0 +1,14 @@
+App(
+    appid="multi_converter",
+    name="Multi Converter",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="multi_converter_app",
+    requires=["gui"],
+    stack_size=1 * 1024,
+    order=19,
+    fap_icon="converter_10px.png",
+    fap_category="Tools",
+    fap_author="@theisolinearchip",
+    fap_version="1.1",
+    fap_description="A multi-unit converter written with an easy and expandable system for adding new units and conversion methods",
+)

BIN
multi_converter/converter_10px.png


BIN
multi_converter/img/1.png


BIN
multi_converter/img/2.png


BIN
multi_converter/img/3.png


BIN
multi_converter/img/4.png


+ 164 - 0
multi_converter/multi_converter.c

@@ -0,0 +1,164 @@
+#include <furi.h>
+#include <gui/gui.h>
+#include <input/input.h>
+#include <stdlib.h>
+
+#include "multi_converter_definitions.h"
+#include "multi_converter_mode_display.h"
+#include "multi_converter_mode_select.h"
+
+static void multi_converter_render_callback(Canvas* const canvas, void* ctx) {
+    furi_assert(ctx);
+    const MultiConverterState* multi_converter_state = ctx;
+    furi_mutex_acquire(multi_converter_state->mutex, FuriWaitForever);
+
+    if(multi_converter_state->mode == ModeDisplay) {
+        multi_converter_mode_display_draw(canvas, multi_converter_state);
+    } else {
+        multi_converter_mode_select_draw(canvas, multi_converter_state);
+    }
+
+    furi_mutex_release(multi_converter_state->mutex);
+}
+
+static void
+    multi_converter_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
+    furi_assert(event_queue);
+
+    MultiConverterEvent event = {.type = EventTypeKey, .input = *input_event};
+    furi_message_queue_put(event_queue, &event, FuriWaitForever);
+}
+
+static void multi_converter_init(MultiConverterState* const multi_converter_state) {
+    // initial default values
+
+    multi_converter_state->buffer_orig[MULTI_CONVERTER_NUMBER_DIGITS] = '\0';
+    multi_converter_state->buffer_dest[MULTI_CONVERTER_NUMBER_DIGITS] = '\0'; // null terminators
+
+    multi_converter_state->unit_type_orig = UnitTypeDec;
+    multi_converter_state->unit_type_dest = UnitTypeHex;
+
+    multi_converter_state->keyboard_lock = 0;
+
+    // init the display view
+    multi_converter_mode_display_reset(multi_converter_state);
+
+    // init the select view
+    multi_converter_mode_select_reset(multi_converter_state);
+
+    // set ModeDisplay as the current mode
+    multi_converter_state->mode = ModeDisplay;
+}
+
+// main entry point
+int32_t multi_converter_app(void* p) {
+    UNUSED(p);
+
+    // get event queue
+    FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(MultiConverterEvent));
+
+    // allocate state
+    MultiConverterState* multi_converter_state = malloc(sizeof(MultiConverterState));
+
+    // set mutex for plugin state (different threads can access it)
+    multi_converter_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+    if(!multi_converter_state->mutex) {
+        FURI_LOG_E("MultiConverter", "cannot create mutex\r\n");
+        furi_message_queue_free(event_queue);
+        free(multi_converter_state);
+        return 255;
+    }
+
+    // register callbacks for drawing and input processing
+    ViewPort* view_port = view_port_alloc();
+    view_port_draw_callback_set(view_port, multi_converter_render_callback, multi_converter_state);
+    view_port_input_callback_set(view_port, multi_converter_input_callback, event_queue);
+
+    // open GUI and register view_port
+    Gui* gui = furi_record_open(RECORD_GUI);
+    gui_add_view_port(gui, view_port, GuiLayerFullscreen);
+
+    multi_converter_init(multi_converter_state);
+
+    // main loop
+    MultiConverterEvent event;
+    for(bool processing = true; processing;) {
+        FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
+        furi_mutex_acquire(multi_converter_state->mutex, FuriWaitForever);
+
+        if(event_status == FuriStatusOk) {
+            // press events
+            if(event.type == EventTypeKey && !multi_converter_state->keyboard_lock) {
+                if(multi_converter_state->mode == ModeDisplay) {
+                    if(event.input.key == InputKeyBack) {
+                        if(event.input.type == InputTypePress) processing = false;
+                    } else if(event.input.key == InputKeyOk) { // the "ok" press can be short or long
+                        MultiConverterModeTrigger t = None;
+
+                        if(event.input.type == InputTypeLong)
+                            t = multi_converter_mode_display_ok(1, multi_converter_state);
+                        else if(event.input.type == InputTypeShort)
+                            t = multi_converter_mode_display_ok(0, multi_converter_state);
+
+                        if(t == Reset) {
+                            multi_converter_mode_select_reset(multi_converter_state);
+                            multi_converter_state->mode = ModeSelector;
+                        }
+                    } else {
+                        if(event.input.type == InputTypePress)
+                            multi_converter_mode_display_navigation(
+                                event.input.key, multi_converter_state);
+                    }
+
+                } else { // ModeSelect
+                    if(event.input.type == InputTypePress) {
+                        switch(event.input.key) {
+                        default:
+                            break;
+                        case InputKeyBack:
+                        case InputKeyOk: {
+                            MultiConverterModeTrigger t = multi_converter_mode_select_exit(
+                                event.input.key == InputKeyOk ? 1 : 0, multi_converter_state);
+
+                            if(t == Reset) {
+                                multi_converter_mode_display_reset(multi_converter_state);
+                            } else if(t == Convert) {
+                                multi_converter_mode_display_convert(multi_converter_state);
+                            }
+
+                            multi_converter_state->keyboard_lock = 1;
+                            multi_converter_state->mode = ModeDisplay;
+                            break;
+                        }
+                        case InputKeyLeft:
+                        case InputKeyRight:
+                            multi_converter_mode_select_switch(multi_converter_state);
+                            break;
+                        case InputKeyUp:
+                            multi_converter_mode_select_change_unit(-1, multi_converter_state);
+                            break;
+                        case InputKeyDown:
+                            multi_converter_mode_select_change_unit(1, multi_converter_state);
+                            break;
+                        }
+                    }
+                }
+            } else if(multi_converter_state->keyboard_lock) {
+                multi_converter_state->keyboard_lock = 0;
+            }
+        }
+
+        furi_mutex_release(multi_converter_state->mutex);
+        view_port_update(view_port);
+    }
+
+    view_port_enabled_set(view_port, false);
+    gui_remove_view_port(gui, view_port);
+    furi_record_close(RECORD_GUI);
+    view_port_free(view_port);
+    furi_message_queue_free(event_queue);
+    furi_mutex_free(multi_converter_state->mutex);
+    free(multi_converter_state);
+
+    return 0;
+}

+ 83 - 0
multi_converter/multi_converter_definitions.h

@@ -0,0 +1,83 @@
+#pragma once
+
+#define MULTI_CONVERTER_NUMBER_DIGITS 9
+
+typedef enum {
+    EventTypeKey,
+} EventType;
+
+typedef struct {
+    InputEvent input;
+    EventType type;
+} MultiConverterEvent;
+
+typedef enum {
+    ModeDisplay,
+    ModeSelector,
+} MultiConverterMode;
+
+typedef enum {
+    None,
+    Reset,
+    Convert,
+} MultiConverterModeTrigger;
+
+// new units goes here, used as index to the main multi_converter_available_units array (multi_converter_units.h)
+typedef enum {
+    UnitTypeDec,
+    UnitTypeHex,
+    UnitTypeBin,
+
+    UnitTypeCelsius,
+    UnitTypeFahernheit,
+    UnitTypeKelvin,
+
+    UnitTypeKilometers,
+    UnitTypeMeters,
+    UnitTypeCentimeters,
+    UnitTypeMiles,
+    UnitTypeFeet,
+    UnitTypeInches,
+
+    UnitTypeDegree,
+    UnitTypeRadian,
+} MultiConverterUnitType;
+
+typedef struct {
+    MultiConverterUnitType selected_unit_type_orig;
+    MultiConverterUnitType selected_unit_type_dest;
+    uint8_t select_orig;
+} MultiConverterModeSelect;
+
+typedef struct {
+    uint8_t cursor; // cursor position when typing
+    int8_t key; // hover key
+    uint8_t comma; // comma already added? (only one comma allowed)
+    uint8_t negative; // is negative?
+} MultiConverterModeDisplay;
+
+typedef struct MultiConverterUnit MultiConverterUnit;
+typedef struct MultiConverterState MultiConverterState;
+
+struct MultiConverterUnit {
+    uint8_t allow_comma;
+    uint8_t allow_negative;
+    uint8_t max_number_keys;
+    char mini_name[4];
+    char name[12];
+    void (*convert_function)(MultiConverterState* const);
+    uint8_t (*allowed_function)(MultiConverterUnitType);
+};
+
+struct MultiConverterState {
+    FuriMutex* mutex;
+    char buffer_orig[MULTI_CONVERTER_NUMBER_DIGITS + 1];
+    char buffer_dest[MULTI_CONVERTER_NUMBER_DIGITS + 1];
+    MultiConverterUnitType unit_type_orig;
+    MultiConverterUnitType unit_type_dest;
+    MultiConverterMode mode;
+    MultiConverterModeDisplay display;
+    MultiConverterModeSelect select;
+    uint8_t keyboard_lock; // used to create a small lock when switching from SELECT to DISPLAY modes
+        // (debouncing, basically; otherwise it switch modes twice 'cause it's too fast!)
+};

+ 326 - 0
multi_converter/multi_converter_mode_display.c

@@ -0,0 +1,326 @@
+#include "multi_converter_mode_display.h"
+
+#define MULTI_CONVERTER_DISPLAY_KEYS 18 // [0] to [F] + [BACK] + [SELECT]
+
+#define MULTI_CONVERTER_DISPLAY_KEY_NEGATIVE 0 // long press
+#define MULTI_CONVERTER_DISPLAY_KEY_COMMA 1 // long press
+#define MULTI_CONVERTER_DISPLAY_KEY_DEL 16
+#define MULTI_CONVERTER_DISPLAY_KEY_SELECT 17
+
+#define MULTI_CONVERTER_DISPLAY_CHAR_COMMA '.'
+#define MULTI_CONVERTER_DISPLAY_CHAR_NEGATIVE '-'
+#define MULTI_CONVERTER_DISPLAY_CHAR_DEL '<'
+#define MULTI_CONVERTER_DISPLAY_CHAR_SELECT '#'
+#define MULTI_CONVERTER_DISPLAY_CHAR_BLANK ' '
+
+#define MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN 3
+#define MULTI_CONVERTER_DISPLAY_KEY_CHAR_HEIGHT 8
+
+void multi_converter_mode_display_convert(MultiConverterState* const multi_converter_state) {
+    // 1.- if origin == destination (in theory user won't be allowed to choose the same options, but it's kinda "valid"...)
+    // just copy buffer_orig to buffer_dest and that's it
+
+    if(multi_converter_state->unit_type_orig == multi_converter_state->unit_type_dest) {
+        memcpy(
+            multi_converter_state->buffer_dest,
+            multi_converter_state->buffer_orig,
+            MULTI_CONVERTER_NUMBER_DIGITS);
+        return;
+    }
+
+    // 2.- origin_buffer has not null functions
+    if(multi_converter_get_unit(multi_converter_state->unit_type_orig).convert_function == NULL ||
+       multi_converter_get_unit(multi_converter_state->unit_type_orig).allowed_function == NULL)
+        return;
+
+    // 3.- valid destination type (using allowed_destinations function)
+    if(!multi_converter_get_unit(multi_converter_state->unit_type_orig)
+            .allowed_function(multi_converter_state->unit_type_dest))
+        return;
+
+    multi_converter_get_unit(multi_converter_state->unit_type_orig)
+        .convert_function(multi_converter_state);
+}
+
+void multi_converter_mode_display_draw(
+    Canvas* const canvas,
+    const MultiConverterState* multi_converter_state) {
+    canvas_set_color(canvas, ColorBlack);
+
+    // ORIGIN
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(
+        canvas, 2, 10, multi_converter_get_unit(multi_converter_state->unit_type_orig).mini_name);
+
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(canvas, 2 + 30, 10, multi_converter_state->buffer_orig);
+
+    // DESTINATION
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(
+        canvas,
+        2,
+        10 + 12,
+        multi_converter_get_unit(multi_converter_state->unit_type_dest).mini_name);
+
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(canvas, 2 + 30, 10 + 12, multi_converter_state->buffer_dest);
+
+    // SEPARATOR_LINE
+    canvas_draw_line(canvas, 2, 25, 128 - 3, 25);
+
+    // KEYBOARD
+    uint8_t _x = 5;
+    uint8_t _y = 25 + 15; // line + 10
+
+    for(int i = 0; i < MULTI_CONVERTER_DISPLAY_KEYS; i++) {
+        char g;
+        if(i < 10)
+            g = (i + '0');
+        else if(i < 16)
+            g = ((i - 10) + 'A');
+        else if(i == MULTI_CONVERTER_DISPLAY_KEY_DEL)
+            g = MULTI_CONVERTER_DISPLAY_CHAR_DEL;
+        else
+            g = MULTI_CONVERTER_DISPLAY_CHAR_SELECT;
+
+        uint8_t g_w = canvas_glyph_width(canvas, g);
+
+        if(i < 16 &&
+           i > multi_converter_get_unit(multi_converter_state->unit_type_orig).max_number_keys -
+                   1) {
+            // some units won't use the full [0] - [F] keyboard, in those situations just hide the char
+            // (won't be selectable anyway, so no worries here; this is just about drawing stuff)
+            g = MULTI_CONVERTER_DISPLAY_CHAR_BLANK;
+        }
+
+        // currently hover key is highlighted
+        if((multi_converter_state->display).key == i) {
+            canvas_draw_box(
+                canvas,
+                _x - MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN,
+                _y - (MULTI_CONVERTER_DISPLAY_KEY_CHAR_HEIGHT +
+                      MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN),
+                MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN + g_w +
+                    MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN,
+                MULTI_CONVERTER_DISPLAY_KEY_CHAR_HEIGHT +
+                    MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN * 2);
+            canvas_set_color(canvas, ColorWhite);
+        } else {
+            canvas_draw_frame(
+                canvas,
+                _x - MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN,
+                _y - (MULTI_CONVERTER_DISPLAY_KEY_CHAR_HEIGHT +
+                      MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN),
+                MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN + g_w +
+                    MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN,
+                MULTI_CONVERTER_DISPLAY_KEY_CHAR_HEIGHT +
+                    MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN * 2);
+        }
+
+        // draw key
+        canvas_draw_glyph(canvas, _x, _y, g);
+
+        // certain keys have long_press features, draw whatever they're using there too
+        if(i == MULTI_CONVERTER_DISPLAY_KEY_NEGATIVE) {
+            canvas_draw_box(
+                canvas,
+                _x + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN + g_w - 4,
+                _y + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN - 2,
+                4,
+                2);
+        } else if(i == MULTI_CONVERTER_DISPLAY_KEY_COMMA) {
+            canvas_draw_box(
+                canvas,
+                _x + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN + g_w - 2,
+                _y + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN - 2,
+                2,
+                2);
+        }
+
+        // back to black
+        canvas_set_color(canvas, ColorBlack);
+
+        if(i < 8) {
+            _x += g_w + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN * 2 + 2;
+        } else if(i == 8) {
+            _y += (MULTI_CONVERTER_DISPLAY_KEY_CHAR_HEIGHT +
+                   MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN * 2) +
+                  3;
+            _x = 8; // some padding at the beginning on second line
+        } else {
+            _x += g_w + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN * 2 + 1;
+        }
+    }
+}
+
+void multi_converter_mode_display_navigation(
+    InputKey key,
+    MultiConverterState* const multi_converter_state) {
+    // first move to keyboard position, then check if the ORIGIN allows that specific key, if not jump to the "closest one"
+    switch(key) {
+    default:
+        break;
+
+    case InputKeyUp:
+    case InputKeyDown:
+        if((multi_converter_state->display).key >= 9)
+            (multi_converter_state->display).key -= 9;
+        else
+            (multi_converter_state->display).key += 9;
+        break;
+
+    case InputKeyLeft:
+    case InputKeyRight:
+
+        (multi_converter_state->display).key += (key == InputKeyLeft ? -1 : 1);
+
+        if((multi_converter_state->display).key > MULTI_CONVERTER_DISPLAY_KEYS - 1)
+            (multi_converter_state->display).key = 0;
+        else if((multi_converter_state->display).key < 0)
+            (multi_converter_state->display).key = MULTI_CONVERTER_DISPLAY_KEYS - 1;
+        break;
+    }
+
+    // if destination key is disabled by max_number_keys, move to the closest one
+    // (this could be improved with more accurate keys movements, probably...)
+    if(multi_converter_get_unit(multi_converter_state->unit_type_orig).max_number_keys >= 16)
+        return; // weird, since this means "do not show any number on the keyboard, but just in case..."
+
+    int8_t i = -1;
+    if(key == InputKeyRight || key == InputKeyDown) i = 1;
+
+    while((multi_converter_state->display).key < 16 &&
+          (multi_converter_state->display).key >
+              multi_converter_get_unit(multi_converter_state->unit_type_orig).max_number_keys -
+                  1) {
+        (multi_converter_state->display).key += i;
+        if((multi_converter_state->display).key > MULTI_CONVERTER_DISPLAY_KEYS - 1)
+            (multi_converter_state->display).key = 0;
+        else if((multi_converter_state->display).key < 0)
+            (multi_converter_state->display).key = MULTI_CONVERTER_DISPLAY_KEYS - 1;
+    }
+}
+
+void multi_converter_mode_display_reset(MultiConverterState* const multi_converter_state) {
+    // clean the buffers
+    for(int i = 0; i < MULTI_CONVERTER_NUMBER_DIGITS; i++) {
+        multi_converter_state->buffer_orig[i] = MULTI_CONVERTER_DISPLAY_CHAR_BLANK;
+        multi_converter_state->buffer_dest[i] = MULTI_CONVERTER_DISPLAY_CHAR_BLANK;
+    }
+
+    // reset the display flags and index
+    multi_converter_state->display.cursor = 0;
+    multi_converter_state->display.key = 0;
+    multi_converter_state->display.comma = 0;
+    multi_converter_state->display.negative = 0;
+}
+
+void multi_converter_mode_display_toggle_negative(
+    MultiConverterState* const multi_converter_state) {
+    if(multi_converter_get_unit(multi_converter_state->unit_type_orig).allow_negative) {
+        if(!(multi_converter_state->display).negative) {
+            // shift origin buffer one to right + add the "-" sign (last digit will be lost)
+            for(int i = MULTI_CONVERTER_NUMBER_DIGITS - 1; i > 0; i--) {
+                // we could avoid the blanks, but nevermind
+                multi_converter_state->buffer_orig[i] = multi_converter_state->buffer_orig[i - 1];
+            }
+            multi_converter_state->buffer_orig[0] = MULTI_CONVERTER_DISPLAY_CHAR_NEGATIVE;
+
+            // only increment cursor if we're not out of bound
+            if((multi_converter_state->display).cursor < MULTI_CONVERTER_NUMBER_DIGITS)
+                (multi_converter_state->display).cursor++;
+        } else {
+            // shift origin buffer one to left, append ' ' on the end
+            for(int i = 0; i < MULTI_CONVERTER_NUMBER_DIGITS - 1; i++) {
+                if(multi_converter_state->buffer_orig[i] == MULTI_CONVERTER_DISPLAY_CHAR_BLANK)
+                    break;
+
+                multi_converter_state->buffer_orig[i] = multi_converter_state->buffer_orig[i + 1];
+            }
+            multi_converter_state->buffer_orig[MULTI_CONVERTER_NUMBER_DIGITS - 1] =
+                MULTI_CONVERTER_DISPLAY_CHAR_BLANK;
+
+            (multi_converter_state->display).cursor--;
+        }
+
+        // toggle flag
+        (multi_converter_state->display).negative ^= 1;
+    }
+}
+
+void multi_converter_mode_display_add_comma(MultiConverterState* const multi_converter_state) {
+    if(!multi_converter_get_unit(multi_converter_state->unit_type_orig).allow_comma ||
+       (multi_converter_state->display).comma || !(multi_converter_state->display).cursor ||
+       ((multi_converter_state->display).cursor == (MULTI_CONVERTER_NUMBER_DIGITS - 1)))
+        return; // maybe not allowerd; or one comma already in place; also cannot add commas as first or last chars
+
+    // set flag to one
+    (multi_converter_state->display).comma = 1;
+
+    multi_converter_state->buffer_orig[(multi_converter_state->display).cursor] =
+        MULTI_CONVERTER_DISPLAY_CHAR_COMMA;
+    (multi_converter_state->display).cursor++;
+}
+
+void multi_converter_mode_display_add_number(MultiConverterState* const multi_converter_state) {
+    if((multi_converter_state->display).key >
+       multi_converter_get_unit(multi_converter_state->unit_type_orig).max_number_keys - 1)
+        return;
+
+    if((multi_converter_state->display).key < 10) {
+        multi_converter_state->buffer_orig[(multi_converter_state->display).cursor] =
+            (multi_converter_state->display).key + '0';
+    } else {
+        multi_converter_state->buffer_orig[(multi_converter_state->display).cursor] =
+            ((multi_converter_state->display).key - 10) + 'A';
+    }
+
+    (multi_converter_state->display).cursor++;
+}
+
+MultiConverterModeTrigger multi_converter_mode_display_ok(
+    uint8_t long_press,
+    MultiConverterState* const multi_converter_state) {
+    if((multi_converter_state->display).key < MULTI_CONVERTER_DISPLAY_KEY_DEL) {
+        if((multi_converter_state->display).cursor >= MULTI_CONVERTER_NUMBER_DIGITS)
+            return None; // limit reached, ignore
+
+        // long press on 0 toggle NEGATIVE if allowed, on 1 adds COMMA if allowed
+        if(long_press) {
+            if((multi_converter_state->display).key == MULTI_CONVERTER_DISPLAY_KEY_NEGATIVE) {
+                // toggle negative
+                multi_converter_mode_display_toggle_negative(multi_converter_state);
+            } else if((multi_converter_state->display).key == MULTI_CONVERTER_DISPLAY_KEY_COMMA) {
+                // add comma
+                multi_converter_mode_display_add_comma(multi_converter_state);
+            }
+
+        } else {
+            // regular keys
+            multi_converter_mode_display_add_number(multi_converter_state);
+        }
+
+        multi_converter_mode_display_convert(multi_converter_state);
+
+    } else if((multi_converter_state->display).key == MULTI_CONVERTER_DISPLAY_KEY_DEL) {
+        if((multi_converter_state->display).cursor > 0) (multi_converter_state->display).cursor--;
+
+        if(multi_converter_state->buffer_orig[(multi_converter_state->display).cursor] ==
+           MULTI_CONVERTER_DISPLAY_CHAR_COMMA)
+            (multi_converter_state->display).comma = 0;
+        if(multi_converter_state->buffer_orig[(multi_converter_state->display).cursor] ==
+           MULTI_CONVERTER_DISPLAY_CHAR_NEGATIVE)
+            (multi_converter_state->display).negative = 0;
+
+        multi_converter_state->buffer_orig[(multi_converter_state->display).cursor] =
+            MULTI_CONVERTER_DISPLAY_CHAR_BLANK;
+
+        multi_converter_mode_display_convert(multi_converter_state);
+
+    } else { // MULTI_CONVERTER_DISPLAY_KEY_SELECT
+        return Reset;
+    }
+
+    return None;
+}

+ 61 - 0
multi_converter/multi_converter_mode_display.h

@@ -0,0 +1,61 @@
+#pragma once
+
+#include <input/input.h>
+#include <gui/gui.h>
+
+#include "multi_converter_definitions.h"
+#include "multi_converter_units.h"
+
+//
+// performs a unit conversion from origin to source buffers, if there's any error, overflow or
+// non-compatible format (which shouldn't happen, but just in case) abort conversion and outputs
+// some "?" strings on the buffer or something similar
+//
+void multi_converter_mode_display_convert(MultiConverterState* const multi_converter_state);
+
+//
+// draw the main DISPLAY view with the current multi_converter_state values
+//
+void multi_converter_mode_display_draw(
+    Canvas* const canvas,
+    const MultiConverterState* multi_converter_state);
+
+//
+// keyboard navigation on DISPLAY mode (NAVIGATION only, no BACK nor OK - InputKey guaranteed to be left/right/up/down)
+//
+void multi_converter_mode_display_navigation(
+    InputKey key,
+    MultiConverterState* const multi_converter_state);
+
+//
+// reset the DISPLAY mode with the current units, cleaning the buffers and different flags;
+// call this when exiting the SELECT mode / changing the units
+//
+void multi_converter_mode_display_reset(MultiConverterState* const multi_converter_state);
+
+//
+// toggle the negative flag on current selected buffer ONLY if the unit allows negative numbers
+// (adding negative number may crop the last char on the buffer; it cannot be recovered)
+//
+void multi_converter_mode_display_toggle_negative(MultiConverterState* const multi_converter_state);
+
+//
+// add a comma/dot/decimal separator/whatever on current selected buffer ONLY if the unit allows it
+// (only ONE comma allowed, not in the beginning nor end)
+//
+void multi_converter_mode_display_add_comma(MultiConverterState* const multi_converter_state);
+
+//
+// add a regular number to the buffer if it's <= the max_number_keys from the unit (not necessary
+// since the draw and navigation functions won't allow a trigger for an invalid number, but still
+// to keep the "checks" policy on each "add key" function...)
+//
+void multi_converter_mode_display_add_number(MultiConverterState* const multi_converter_state);
+
+//
+// handle the OK action when selecting a specific key on the keyboard (add a number, a symbol, change mode...)
+// returns a ModeTrigger enum value: may or may not let to a mode change on the main loop (WON'T change the mode here)
+//
+MultiConverterModeTrigger multi_converter_mode_display_ok(
+    uint8_t long_press,
+    MultiConverterState* const multi_converter_state);

+ 210 - 0
multi_converter/multi_converter_mode_select.c

@@ -0,0 +1,210 @@
+#include "multi_converter_mode_select.h"
+
+#define MULTI_CONVERTER_LIST_ENTRIES_COUNT 3
+
+#define MULTI_CONVERTER_INFO_STRING_FROM "FROM:"
+#define MULTI_CONVERTER_INFO_STRING_TO "TO:"
+#define MULTI_CONVERTER_INFO_STRING_OK "OK: Change"
+#define MULTI_CONVERTER_INFO_STRING_BACK "BACK: Cancel"
+
+void multi_converter_mode_select_draw_destination_offset(
+    uint8_t x,
+    uint8_t y,
+    int8_t d,
+    Canvas* const canvas,
+    const MultiConverterState* multi_converter_state) {
+    int i = 1;
+    while(
+        i <
+        MULTI_CONVERTER_AVAILABLE_UNITS) { // in case there's no match, to avoid an endless loop (in theory shouldn't happen, but...)
+        int ut = multi_converter_get_unit_type_offset(
+            (multi_converter_state->select).selected_unit_type_dest, i * d);
+        if(multi_converter_available_units[(multi_converter_state->select).selected_unit_type_orig]
+               .allowed_function(ut) &&
+           (multi_converter_state->select).selected_unit_type_orig != ut) {
+            canvas_draw_str(canvas, x, y, multi_converter_available_units[ut].name);
+            break;
+        }
+        i++;
+    }
+}
+
+void multi_converter_mode_select_draw_selected_unit(
+    uint8_t x,
+    uint8_t y,
+    MultiConverterUnitType unit_type,
+    Canvas* const canvas) {
+    canvas_draw_box(
+        canvas,
+        x - 2,
+        y - 10,
+        canvas_string_width(canvas, multi_converter_available_units[unit_type].name) + 4,
+        13);
+    canvas_set_color(canvas, ColorWhite);
+    canvas_draw_str(canvas, x, y, multi_converter_available_units[unit_type].name);
+    canvas_set_color(canvas, ColorBlack);
+}
+
+void multi_converter_mode_select_draw(
+    Canvas* const canvas,
+    const MultiConverterState* multi_converter_state) {
+    int y = 10;
+    int x = 10;
+
+    canvas_set_color(canvas, ColorBlack);
+
+    // FROM
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(canvas, x, y, MULTI_CONVERTER_INFO_STRING_FROM);
+
+    canvas_set_font(canvas, FontSecondary);
+
+    // offset -1
+    y += 12;
+
+    canvas_draw_str(
+        canvas,
+        x,
+        y,
+        multi_converter_available_units[multi_converter_get_unit_type_offset(
+                                            (multi_converter_state->select).selected_unit_type_orig,
+                                            -1)]
+            .name);
+
+    // current selected element
+    y += 12;
+
+    multi_converter_mode_select_draw_selected_unit(
+        x, y, (multi_converter_state->select).selected_unit_type_orig, canvas);
+
+    if((multi_converter_state->select).select_orig) canvas_draw_str(canvas, x - 6, y, ">");
+
+    // offset +1
+    y += 12;
+
+    canvas_draw_str(
+        canvas,
+        x,
+        y,
+        multi_converter_available_units[multi_converter_get_unit_type_offset(
+                                            (multi_converter_state->select).selected_unit_type_orig,
+                                            1)]
+            .name);
+
+    // TO
+    y = 10;
+    x = 70;
+
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(canvas, x, y, MULTI_CONVERTER_INFO_STRING_TO);
+
+    canvas_set_font(canvas, FontSecondary);
+
+    // offset -1: go back from current selected destination and find the first one valid (even if it's itself)
+    y += 12;
+
+    multi_converter_mode_select_draw_destination_offset(x, y, -1, canvas, multi_converter_state);
+
+    // current selected element
+    y += 12;
+
+    multi_converter_mode_select_draw_selected_unit(
+        x, y, (multi_converter_state->select).selected_unit_type_dest, canvas);
+
+    if(!(multi_converter_state->select).select_orig) canvas_draw_str(canvas, x - 6, y, ">");
+
+    // offset +1: same but on the opposite direction
+    y += 12;
+
+    multi_converter_mode_select_draw_destination_offset(x, y, 1, canvas, multi_converter_state);
+
+    // OK / CANCEL
+
+    canvas_set_color(canvas, ColorBlack);
+    canvas_draw_box(
+        canvas, 0, 64 - 12, canvas_string_width(canvas, MULTI_CONVERTER_INFO_STRING_OK) + 4, 12);
+    canvas_draw_box(
+        canvas,
+        128 - 4 - canvas_string_width(canvas, MULTI_CONVERTER_INFO_STRING_BACK),
+        64 - 12,
+        canvas_string_width(canvas, "BACK: Cancel") + 4,
+        12);
+
+    canvas_set_color(canvas, ColorWhite);
+    canvas_draw_str(canvas, 2, 64 - 3, MULTI_CONVERTER_INFO_STRING_OK);
+    canvas_draw_str(
+        canvas,
+        128 - 2 - canvas_string_width(canvas, MULTI_CONVERTER_INFO_STRING_BACK),
+        64 - 3,
+        MULTI_CONVERTER_INFO_STRING_BACK);
+}
+
+void multi_converter_mode_select_reset(MultiConverterState* const multi_converter_state) {
+    // initial pre-selected values are equal to the current selected values
+    (multi_converter_state->select).selected_unit_type_orig =
+        multi_converter_state->unit_type_orig;
+    (multi_converter_state->select).selected_unit_type_dest =
+        multi_converter_state->unit_type_dest;
+
+    (multi_converter_state->select).select_orig = 1;
+}
+
+MultiConverterModeTrigger multi_converter_mode_select_exit(
+    uint8_t save_changes,
+    MultiConverterState* const multi_converter_state) {
+    if(save_changes) {
+        multi_converter_state->unit_type_dest =
+            (multi_converter_state->select).selected_unit_type_dest;
+
+        if(multi_converter_state->unit_type_orig ==
+           (multi_converter_state->select).selected_unit_type_orig) {
+            // if the ORIGIN unit didn't changed, just trigger the convert
+
+            return Convert;
+        } else {
+            multi_converter_state->unit_type_orig =
+                (multi_converter_state->select).selected_unit_type_orig;
+            multi_converter_state->unit_type_dest =
+                (multi_converter_state->select).selected_unit_type_dest;
+
+            return Reset;
+        }
+    }
+
+    return None;
+}
+
+void multi_converter_mode_select_switch(MultiConverterState* const multi_converter_state) {
+    (multi_converter_state->select).select_orig ^= 1;
+}
+
+void multi_converter_mode_select_change_unit(
+    int8_t direction,
+    MultiConverterState* const multi_converter_state) {
+    MultiConverterUnitType d;
+    if((multi_converter_state->select).select_orig) {
+        (multi_converter_state->select).selected_unit_type_orig =
+            multi_converter_get_unit_type_offset(
+                (multi_converter_state->select).selected_unit_type_orig, direction);
+        d = (multi_converter_state->select).selected_unit_type_dest;
+    } else {
+        d = ((multi_converter_state->select).selected_unit_type_dest + direction) %
+            MULTI_CONVERTER_AVAILABLE_UNITS;
+    }
+
+    // check each unit with the ORIGIN allowed_function() to make sure we're selecting a valid DESTINATION
+    // (when changing the ORIGIN unit the DIRECTION in which we'll switch the DESTINATION will be the SAME);
+    // also notice that ORIGIN must be DIFFERENT than DESTINATION
+    int i = 0;
+    while(i < MULTI_CONVERTER_AVAILABLE_UNITS) {
+        if(multi_converter_available_units[(multi_converter_state->select).selected_unit_type_orig]
+               .allowed_function(d) &&
+           (multi_converter_state->select).selected_unit_type_orig != d) {
+            (multi_converter_state->select).selected_unit_type_dest = d;
+            break;
+        }
+
+        d = multi_converter_get_unit_type_offset(d, direction);
+        i++;
+    }
+}

+ 73 - 0
multi_converter/multi_converter_mode_select.h

@@ -0,0 +1,73 @@
+#pragma once
+
+#include <stdlib.h>
+#include <input/input.h>
+#include <gui/gui.h>
+
+#include "multi_converter_definitions.h"
+#include "multi_converter_units.h"
+
+//
+// aux draw function for units offsets and draw stuff
+//
+void multi_converter_mode_select_draw_destination_offset(
+    uint8_t x,
+    uint8_t y,
+    int8_t d,
+    Canvas* const canvas,
+    const MultiConverterState* multi_converter_state);
+
+void multi_converter_mode_select_draw_selected_unit(
+    uint8_t x,
+    uint8_t y,
+    MultiConverterUnitType unit_type,
+    Canvas* const canvas);
+
+//
+// draw the main SELECT view with the current multi_converter_state values
+//
+void multi_converter_mode_select_draw(
+    Canvas* const canvas,
+    const MultiConverterState* multi_converter_state);
+
+//
+// reset the SELECT mode view, showing as "pre-selected" the current working units
+//
+void multi_converter_mode_select_reset(MultiConverterState* const multi_converter_state);
+
+//
+// exit from SELECT mode and go back to display view, if save_changes == 1 use the current SELECT view info
+// to modify the current selected units and reset the views properly (usually if the ORIGIN unit has been
+// changed, reset everything; otherwise just trigger the convert function with a new DESTINATION)
+//
+// currently this function DON'T CHECK invalid unit relations (the navigation and display functions will
+// prevent weird behaviours, so for now we're trusting the selected_unit_orig/dest_type values)
+//
+// returns an enum code MultiConverterDisplayTrigger based on doing nothing (cancel), triggering the display
+// convert method or reseting the whole display mode (when fully changing the units)
+//
+// notice the MODE CHANGE itself is not done here but in the main loop (outside the call) via the ModeTrigger enum element
+//
+MultiConverterModeTrigger multi_converter_mode_select_exit(
+    uint8_t save_changes,
+    MultiConverterState* const multi_converter_state);
+
+//
+// switch between selecting the ORIGIN or the DESTINATION unit on DISPLAY mode (since there're only
+// two options, both left/right arrow keys acts as toggles, no "direction" required)
+//
+void multi_converter_mode_select_switch(MultiConverterState* const multi_converter_state);
+
+//
+// change the selected unit on SELECTED mode, using the select_orig flag to check if we're switching the
+// ORIGIN or the DESTINATION unit; the DIRECTION (up or down to travel the array) is set as a param
+//
+// when switching the ORIGIN one, reset the DESTINATION to the first valid unit (if the current one is not
+// valid anymore); when switching the DESTINATION one, an allowed_function() check is performed in order to
+// properly set a valid destination unit.
+//
+// (notice the draw step also perform which units are valid to display, so no worries about that here)
+//
+void multi_converter_mode_select_change_unit(
+    int8_t direction,
+    MultiConverterState* const multi_converter_state);

+ 261 - 0
multi_converter/multi_converter_units.c

@@ -0,0 +1,261 @@
+#include "multi_converter_units.h"
+
+#define MULTI_CONVERTER_CHAR_OVERFLOW '#'
+#define MULTI_CONVERTER_MAX_SUPORTED_INT 999999999
+
+#define multi_converter_unit_set_overflow(b)                  \
+    for(int _i = 0; _i < MULTI_CONVERTER_NUMBER_DIGITS; _i++) \
+        b[_i] = MULTI_CONVERTER_CHAR_OVERFLOW;
+
+//
+// DEC / HEX / BIN conversion
+//
+void multi_converter_unit_dec_hex_bin_convert(MultiConverterState* const multi_converter_state) {
+    char dest[MULTI_CONVERTER_NUMBER_DIGITS];
+
+    int i = 0;
+    uint8_t overflow = 0;
+
+    int a = 0;
+    int r = 0;
+    uint8_t f = 1;
+
+    switch(multi_converter_state->unit_type_orig) {
+    default:
+        break;
+    case UnitTypeDec: {
+        a = atoi(multi_converter_state->buffer_orig);
+        f = (multi_converter_state->unit_type_dest == UnitTypeHex ? 16 : 2);
+
+        break;
+    }
+    case UnitTypeHex:
+        a = strtol(multi_converter_state->buffer_orig, NULL, 16);
+        f = (multi_converter_state->unit_type_dest == UnitTypeDec ? 10 : 2);
+
+        break;
+    case UnitTypeBin:
+        a = strtol(multi_converter_state->buffer_orig, NULL, 2);
+        f = (multi_converter_state->unit_type_dest == UnitTypeDec ? 10 : 16);
+
+        break;
+    }
+
+    while(a > 0) {
+        r = a % f;
+        dest[i] = r + (r < 10 ? '0' : ('A' - 10));
+        a /= f;
+        if(i++ >= MULTI_CONVERTER_NUMBER_DIGITS) {
+            overflow = 1;
+            break;
+        }
+    }
+
+    if(overflow) {
+        multi_converter_unit_set_overflow(multi_converter_state->buffer_dest);
+    } else {
+        // copy DEST (reversed) to destination and append empty chars at the end
+        for(int j = 0; j < MULTI_CONVERTER_NUMBER_DIGITS; j++) {
+            if(i >= 1)
+                multi_converter_state->buffer_dest[j] = dest[--i];
+            else
+                multi_converter_state->buffer_dest[j] = ' ';
+        }
+    }
+}
+
+uint8_t multi_converter_unit_dec_hex_bin_allowed(MultiConverterUnitType unit_type) {
+    return (unit_type == UnitTypeDec || unit_type == UnitTypeHex || unit_type == UnitTypeBin);
+}
+
+//
+// CEL / FAR / KEL
+//
+void multi_converter_unit_temperature_convert(MultiConverterState* const multi_converter_state) {
+    double a = strtof(multi_converter_state->buffer_orig, NULL);
+    uint8_t overflow = 0;
+
+    switch(multi_converter_state->unit_type_orig) {
+    default:
+        break;
+    case UnitTypeCelsius:
+        if(multi_converter_state->unit_type_dest == UnitTypeFahernheit) {
+            // celsius to fahrenheit
+            a = (a * ((double)1.8)) + 32;
+        } else { // UnitTypeKelvin
+            a += ((double)273.15);
+        }
+
+        break;
+    case UnitTypeFahernheit:
+        // fahrenheit to celsius, always
+        a = (a - 32) / ((double)1.8);
+        if(multi_converter_state->unit_type_dest == UnitTypeKelvin) {
+            // if kelvin, add
+            a += ((double)273.15);
+        }
+
+        break;
+    case UnitTypeKelvin:
+        // kelvin to celsius, always
+        a -= ((double)273.15);
+        if(multi_converter_state->unit_type_dest == UnitTypeFahernheit) {
+            // if fahernheit, convert
+            a = (a * ((double)1.8)) + 32;
+        }
+
+        break;
+    }
+
+    if(overflow) {
+        multi_converter_unit_set_overflow(multi_converter_state->buffer_dest);
+    } else {
+        int ret = snprintf(
+            multi_converter_state->buffer_dest, MULTI_CONVERTER_NUMBER_DIGITS + 1, "%.3lf", a);
+
+        if(ret < 0) multi_converter_unit_set_overflow(multi_converter_state->buffer_dest);
+    }
+}
+
+uint8_t multi_converter_unit_temperature_allowed(MultiConverterUnitType unit_type) {
+    return (
+        unit_type == UnitTypeCelsius || unit_type == UnitTypeFahernheit ||
+        unit_type == UnitTypeKelvin);
+}
+
+//
+// KM / M / CM / MILES / FEET / INCHES
+//
+
+void multi_converter_unit_distance_convert(MultiConverterState* const multi_converter_state) {
+    double a = strtof(multi_converter_state->buffer_orig, NULL);
+    uint8_t overflow = 0;
+
+    switch(multi_converter_state->unit_type_orig) {
+    default:
+        break;
+    case UnitTypeKilometers:
+        if(multi_converter_state->unit_type_dest == UnitTypeMeters)
+            a *= ((double)1000);
+        else if(multi_converter_state->unit_type_dest == UnitTypeCentimeters)
+            a *= ((double)100000);
+        else if(multi_converter_state->unit_type_dest == UnitTypeMiles)
+            a *= ((double)0.6213711);
+        else if(multi_converter_state->unit_type_dest == UnitTypeFeet)
+            a *= ((double)3280.839895013);
+        else if(multi_converter_state->unit_type_dest == UnitTypeInches)
+            a *= ((double)39370.078740157);
+        break;
+    case UnitTypeMeters:
+        if(multi_converter_state->unit_type_dest == UnitTypeKilometers)
+            a /= ((double)1000);
+        else if(multi_converter_state->unit_type_dest == UnitTypeCentimeters)
+            a *= ((double)100);
+        else if(multi_converter_state->unit_type_dest == UnitTypeMiles)
+            a *= ((double)0.0006213711);
+        else if(multi_converter_state->unit_type_dest == UnitTypeFeet)
+            a *= ((double)3.280839895013);
+        else if(multi_converter_state->unit_type_dest == UnitTypeInches)
+            a *= ((double)39.370078740157);
+        break;
+    case UnitTypeCentimeters:
+        if(multi_converter_state->unit_type_dest == UnitTypeKilometers)
+            a /= ((double)100000);
+        else if(multi_converter_state->unit_type_dest == UnitTypeMeters)
+            a /= ((double)100);
+        else if(multi_converter_state->unit_type_dest == UnitTypeMiles)
+            a *= ((double)0.000006213711);
+        else if(multi_converter_state->unit_type_dest == UnitTypeFeet)
+            a *= ((double)0.03280839895013);
+        else if(multi_converter_state->unit_type_dest == UnitTypeInches)
+            a *= ((double)0.39370078740157);
+        break;
+
+    case UnitTypeMiles:
+        if(multi_converter_state->unit_type_dest == UnitTypeKilometers)
+            a *= ((double)1.609344);
+        else if(multi_converter_state->unit_type_dest == UnitTypeMeters)
+            a *= ((double)1609.344);
+        else if(multi_converter_state->unit_type_dest == UnitTypeCentimeters)
+            a *= ((double)160934.4);
+        else if(multi_converter_state->unit_type_dest == UnitTypeFeet)
+            a *= ((double)5280);
+        else if(multi_converter_state->unit_type_dest == UnitTypeInches)
+            a *= ((double)63360);
+        break;
+    case UnitTypeFeet:
+        if(multi_converter_state->unit_type_dest == UnitTypeKilometers)
+            a *= ((double)0.0003048);
+        else if(multi_converter_state->unit_type_dest == UnitTypeMeters)
+            a *= ((double)0.3048);
+        else if(multi_converter_state->unit_type_dest == UnitTypeCentimeters)
+            a *= ((double)30.48);
+        else if(multi_converter_state->unit_type_dest == UnitTypeMiles)
+            a *= ((double)0.000189393939394);
+        else if(multi_converter_state->unit_type_dest == UnitTypeInches)
+            a *= ((double)12);
+        break;
+    case UnitTypeInches:
+        if(multi_converter_state->unit_type_dest == UnitTypeKilometers)
+            a *= ((double)0.0000254);
+        else if(multi_converter_state->unit_type_dest == UnitTypeMeters)
+            a *= ((double)0.0254);
+        else if(multi_converter_state->unit_type_dest == UnitTypeCentimeters)
+            a *= ((double)2.54);
+        else if(multi_converter_state->unit_type_dest == UnitTypeMiles)
+            a *= ((double)0.0000157828282828);
+        else if(multi_converter_state->unit_type_dest == UnitTypeFeet)
+            a *= ((double)0.0833333333333);
+        break;
+    }
+
+    if(overflow) {
+        multi_converter_unit_set_overflow(multi_converter_state->buffer_dest);
+    } else {
+        int ret = snprintf(
+            multi_converter_state->buffer_dest, MULTI_CONVERTER_NUMBER_DIGITS + 1, "%lf", a);
+
+        if(ret < 0) multi_converter_unit_set_overflow(multi_converter_state->buffer_dest);
+    }
+}
+
+uint8_t multi_converter_unit_distance_allowed(MultiConverterUnitType unit_type) {
+    return (
+        unit_type == UnitTypeKilometers || unit_type == UnitTypeMeters ||
+        unit_type == UnitTypeCentimeters || unit_type == UnitTypeMiles ||
+        unit_type == UnitTypeFeet || unit_type == UnitTypeInches);
+}
+
+//
+// DEG / RAD
+//
+
+void multi_converter_unit_angle_convert(MultiConverterState* const multi_converter_state) {
+    double a = strtof(multi_converter_state->buffer_orig, NULL);
+    uint8_t overflow = 0;
+
+    switch(multi_converter_state->unit_type_orig) {
+    default:
+        break;
+    case UnitTypeDegree:
+        if(multi_converter_state->unit_type_dest == UnitTypeRadian) a *= ((double)0.0174532925199);
+        break;
+
+    case UnitTypeRadian:
+        if(multi_converter_state->unit_type_dest == UnitTypeDegree) a *= ((double)57.2957795131);
+        break;
+    }
+
+    if(overflow) {
+        multi_converter_unit_set_overflow(multi_converter_state->buffer_dest);
+    } else {
+        int ret = snprintf(
+            multi_converter_state->buffer_dest, MULTI_CONVERTER_NUMBER_DIGITS + 1, "%lf", a);
+
+        if(ret < 0) multi_converter_unit_set_overflow(multi_converter_state->buffer_dest);
+    }
+}
+
+uint8_t multi_converter_unit_angle_allowed(MultiConverterUnitType unit_type) {
+    return (unit_type == UnitTypeDegree || unit_type == UnitTypeRadian);
+}

+ 171 - 0
multi_converter/multi_converter_units.h

@@ -0,0 +1,171 @@
+#pragma once
+
+#include <input/input.h>
+#include <gui/gui.h>
+
+#include "multi_converter_definitions.h"
+
+#define MULTI_CONVERTER_AVAILABLE_UNITS 14
+
+#define multi_converter_get_unit(unit_type) multi_converter_available_units[unit_type]
+#define multi_converter_get_unit_type_offset(unit_type, offset)                                   \
+    (((unit_type + offset) % MULTI_CONVERTER_AVAILABLE_UNITS + MULTI_CONVERTER_AVAILABLE_UNITS) % \
+     MULTI_CONVERTER_AVAILABLE_UNITS)
+// the modulo operation will fail with extremely large values on the units array
+
+// DEC / HEX / BIN
+void multi_converter_unit_dec_hex_bin_convert(MultiConverterState* const multi_converter_state);
+uint8_t multi_converter_unit_dec_hex_bin_allowed(MultiConverterUnitType);
+
+// CEL / FAR / KEL
+void multi_converter_unit_temperature_convert(MultiConverterState* const multi_converter_state);
+uint8_t multi_converter_unit_temperature_allowed(MultiConverterUnitType);
+
+// KM / M / CM / MILES / FEET / INCHES
+void multi_converter_unit_distance_convert(MultiConverterState* const multi_converter_state);
+uint8_t multi_converter_unit_distance_allowed(MultiConverterUnitType);
+
+// DEG / RAD
+void multi_converter_unit_angle_convert(MultiConverterState* const multi_converter_state);
+uint8_t multi_converter_unit_angle_allowed(MultiConverterUnitType unit_type);
+
+//
+// each unit is made of comma? + negative? + keyboard_length + mini_name + name + convert function + allowed function
+// (setting functions as NULL will cause convert / select options to be ignored)
+//
+static const MultiConverterUnit multi_converter_unit_dec = {
+    0,
+    0,
+    10,
+    "DEC\0",
+    "Decimal\0",
+    multi_converter_unit_dec_hex_bin_convert,
+    multi_converter_unit_dec_hex_bin_allowed};
+static const MultiConverterUnit multi_converter_unit_hex = {
+    0,
+    0,
+    16,
+    "HEX\0",
+    "Hexadecimal\0",
+    multi_converter_unit_dec_hex_bin_convert,
+    multi_converter_unit_dec_hex_bin_allowed};
+static const MultiConverterUnit multi_converter_unit_bin = {
+    0,
+    0,
+    2,
+    "BIN\0",
+    "Binary\0",
+    multi_converter_unit_dec_hex_bin_convert,
+    multi_converter_unit_dec_hex_bin_allowed};
+
+static const MultiConverterUnit multi_converter_unit_cel = {
+    1,
+    1,
+    10,
+    "CEL\0",
+    "Celsius\0",
+    multi_converter_unit_temperature_convert,
+    multi_converter_unit_temperature_allowed};
+static const MultiConverterUnit multi_converter_unit_far = {
+    1,
+    1,
+    10,
+    "FAR\0",
+    "Fahernheit\0",
+    multi_converter_unit_temperature_convert,
+    multi_converter_unit_temperature_allowed};
+static const MultiConverterUnit multi_converter_unit_kel = {
+    1,
+    1,
+    10,
+    "KEL\0",
+    "Kelvin\0",
+    multi_converter_unit_temperature_convert,
+    multi_converter_unit_temperature_allowed};
+
+static const MultiConverterUnit multi_converter_unit_km = {
+    1,
+    0,
+    10,
+    "KM\0",
+    "Kilometers\0",
+    multi_converter_unit_distance_convert,
+    multi_converter_unit_distance_allowed};
+static const MultiConverterUnit multi_converter_unit_m = {
+    1,
+    0,
+    10,
+    "M\0",
+    "Meters\0",
+    multi_converter_unit_distance_convert,
+    multi_converter_unit_distance_allowed};
+static const MultiConverterUnit multi_converter_unit_cm = {
+    1,
+    0,
+    10,
+    "CM\0",
+    "Centimeters\0",
+    multi_converter_unit_distance_convert,
+    multi_converter_unit_distance_allowed};
+static const MultiConverterUnit multi_converter_unit_mi = {
+    1,
+    0,
+    10,
+    "MI\0",
+    "Miles\0",
+    multi_converter_unit_distance_convert,
+    multi_converter_unit_distance_allowed};
+static const MultiConverterUnit multi_converter_unit_ft = {
+    1,
+    0,
+    10,
+    "FT\0",
+    "Feet\0",
+    multi_converter_unit_distance_convert,
+    multi_converter_unit_distance_allowed};
+static const MultiConverterUnit multi_converter_unit_in = {
+    1,
+    0,
+    10,
+    " \"\0",
+    "Inches\0",
+    multi_converter_unit_distance_convert,
+    multi_converter_unit_distance_allowed};
+
+static const MultiConverterUnit multi_converter_unit_deg = {
+    1,
+    0,
+    10,
+    "DEG\0",
+    "Degree\0",
+    multi_converter_unit_angle_convert,
+    multi_converter_unit_angle_allowed};
+static const MultiConverterUnit multi_converter_unit_rad = {
+    1,
+    0,
+    10,
+    "RAD\0",
+    "Radian\0",
+    multi_converter_unit_angle_convert,
+    multi_converter_unit_angle_allowed};
+
+// index order set by the MultiConverterUnitType enum element (multi_converter_definitions.h)
+static const MultiConverterUnit multi_converter_available_units[MULTI_CONVERTER_AVAILABLE_UNITS] = {
+    [UnitTypeDec] = multi_converter_unit_dec,
+    [UnitTypeHex] = multi_converter_unit_hex,
+    [UnitTypeBin] = multi_converter_unit_bin,
+
+    [UnitTypeCelsius] = multi_converter_unit_cel,
+    [UnitTypeFahernheit] = multi_converter_unit_far,
+    [UnitTypeKelvin] = multi_converter_unit_kel,
+
+    [UnitTypeKilometers] = multi_converter_unit_km,
+    [UnitTypeMeters] = multi_converter_unit_m,
+    [UnitTypeCentimeters] = multi_converter_unit_cm,
+    [UnitTypeMiles] = multi_converter_unit_mi,
+    [UnitTypeFeet] = multi_converter_unit_ft,
+    [UnitTypeInches] = multi_converter_unit_in,
+
+    [UnitTypeDegree] = multi_converter_unit_deg,
+    [UnitTypeRadian] = multi_converter_unit_rad,
+};