Skip to content

TurtleTools

Ryan Srichai edited this page Nov 6, 2025 · 42 revisions

Turtle Tools builds on turtle and turtleText to create pre-built UI elements which would be tedious to implement manually. The library is developed on-demand in that whenever I need some kind of interface element for one of my projects, I add it to turtleTools. The current list of elements include:

Initialising

Unlike turtle and turtleText, turtleTools doesn't have to be explicitly initialised. Instead each of the different components must be individually initialised. Each element has different requirements for what it needs.

Updating

turtleTools also has its own update function which must be called before turtleUpdate(). turtleToolsUpdate() will update all of the buttons, switches, and textboxes on the screen every tick. You'll also need to get the mouse coordinates and clear the screen at the start of the loop for this module to work correctly. So the basic loop will look like this:

while (turtle.popupClose == 0) {
    start = clock();
    turtleGetMouseCoords(); // required for mouse functionality of turtleTools
    turtleClear(); // clear the screen
    ...
    turtleToolsUpdate(); // update turtleTools
    turtleUpdate(); // update the screen
    end = clock();
    while ((double) (end - start) / CLOCKS_PER_SEC < (1.0 / tps)) {
        end = clock();
    }
}

See the turtle.c included example for more details on how turtleTools is used.

Themes

turtleTools has themes! (write later)

Color

The default colors of an element is defined by the turtleTools theme. It will change if the theme is changed.

To change the colors of an element, override the color indices in the element's color field. A table of default colors can be found in turtleTools.c:

int32_t tt_color_default[] = {
    /*       button                         switch                            dial                           slider                          scrollbar                      context                       dropdown                     textbox  */
    TT_COLOR_TEXT_ALTERNATE,       TT_COLOR_TEXT_BASE,             TT_COLOR_TEXT_BASE,             TT_COLOR_TEXT_BASE,             0,                              TT_COLOR_TEXT_ALTERNATE,      TT_COLOR_TEXT_BASE,           TT_COLOR_TEXT_ALTERNATE,
    TT_COLOR_COMPONENT,            TT_COLOR_TEXT_ALTERNATE,        TT_COLOR_BACKGROUND_COMPLEMENT, TT_COLOR_COMPONENT_BASE,        TT_COLOR_COMPONENT_BASE,        TT_COLOR_COMPONENT_BASE,      TT_COLOR_TEXT_ALTERNATE,      TT_COLOR_COMPONENT_BASE,
    TT_COLOR_COMPONENT_HIGHLIGHT,  TT_COLOR_COMPONENT_BASE,        TT_COLOR_BACKGROUND_BASE,       TT_COLOR_BACKGROUND_ALTERNATE,  TT_COLOR_COMPONENT_COMPLEMENT,  TT_COLOR_COMPONENT_HIGHLIGHT, TT_COLOR_COMPONENT_BASE,      TT_COLOR_TEXT_HIGHLIGHT,
    TT_COLOR_TEXT_BASE,            TT_COLOR_COMPONENT_HIGHLIGHT,   0,                              0,                              TT_COLOR_BACKGROUND_ALTERNATE,  0,                            TT_COLOR_COMPONENT_HIGHLIGHT, TT_COLOR_TEXT_ALTERNATE,
    TT_COLOR_COMPONENT_COMPLEMENT, TT_COLOR_BACKGROUND_ALTERNATE,  0,                              0,                              TT_COLOR_BACKGROUND_HIGHLIGHT,  0,                            TT_COLOR_COMPONENT_HIGHLIGHT, TT_COLOR_BLUE,
    0,                             TT_COLOR_TERTIARY_BASE,         0,                              0,                              0,                              0,                            TT_COLOR_TEXT_ALTERNATE,      0,
    0,                             0,                              0,                              0,                              0,                              0,                            0,                            0,
    0,                             0,                              0,                              0,                              0,                              0,                            0,                            0,
};

FINISH THIS SECTION

Ribbon

The top ribbon is this set of dropdowns that are common in editors.

image

To activate this element, use ribbonInit():

ribbonInit("config/ribbonConfig.txt");

It requires a configuration file which looks like this:

File, New, Save, Save As..., Open
Edit, Undo, Redo, Cut, Copy, Paste
View, Change Theme, GLFW

Each row corresponds to a column in the ribbon, with name of the column being the first item of the row. There is only support for one dropdown layer (for example, some applications let you do File -> Preferences -> Key Settings. turtleTools ribbon does not support that extra sideways layer). If the ribbon configuration file is not found or NULL is passed in to ribbonInit(), then a default ribbon configuration will be used which is identical to the above example.

In order to detect within the program when the user selects one of these options, there is an example function written in turtle.c called parseRibbonOutput:

void parseRibbonOutput() {
    if (tt_ribbon.output[0] == 0) {
        return;
    }
    tt_ribbon.output[0] = 0;
    if (tt_ribbon.output[1] == 0) { // File
        if (tt_ribbon.output[2] == 1) { // New
            printf("New\n");
        }
        if (tt_ribbon.output[2] == 2) { // Save
            if (strcmp(osToolsFileDialog.selectedFilename, "null") == 0) {
                if (osToolsFileDialogPrompt(1, "") != -1) {
                    printf("Saved to: %s\n", osToolsFileDialog.selectedFilename);
                }
            } else {
                printf("Saved to: %s\n", osToolsFileDialog.selectedFilename);
            }
        }
        if (tt_ribbon.output[2] == 3) { // Save As...
            if (osToolsFileDialogPrompt(1, "") != -1) {
                printf("Saved to: %s\n", osToolsFileDialog.selectedFilename);
            }
        }
        if (tt_ribbon.output[2] == 4) { // Open
            if (osToolsFileDialogPrompt(0, "") != -1) {
                printf("Loaded data from: %s\n", osToolsFileDialog.selectedFilename);
            }
        }
    }
    if (tt_ribbon.output[1] == 1) { // Edit
        if (tt_ribbon.output[2] == 1) { // Undo
            printf("Undo\n");
        }
        if (tt_ribbon.output[2] == 2) { // Redo
            printf("Redo\n");
        }
        if (tt_ribbon.output[2] == 3) { // Cut
            osToolsClipboardSetText("test123");
            printf("Cut \"test123\" to clipboard!\n");
        }
        if (tt_ribbon.output[2] == 4) { // Copy
            osToolsClipboardSetText("test345");
            printf("Copied \"test345\" to clipboard!\n");
        }
        if (tt_ribbon.output[2] == 5) { // Paste
            osToolsClipboardGetText();
            printf("Pasted \"%s\" from clipboard!\n", osToolsClipboard.text);
        }
    }
    if (tt_ribbon.output[1] == 2) { // View
        if (tt_ribbon.output[2] == 1) { // Change theme
            printf("Change theme\n");
            if (tt_theme == TT_THEME_DARK) {
                turtleBgColor(36, 30, 32);
                turtleToolsSetTheme(TT_THEME_COLT);
            } else if (tt_theme == TT_THEME_COLT) {
                turtleBgColor(212, 201, 190);
                turtleToolsSetTheme(TT_THEME_NAVY);
            } else if (tt_theme == TT_THEME_NAVY) {
                turtleBgColor(255, 255, 255);
                turtleToolsSetTheme(TT_THEME_LIGHT);
            } else if (tt_theme == TT_THEME_LIGHT) {
                turtleBgColor(30, 30, 30);
                turtleToolsSetTheme(TT_THEME_DARK);
            }
        } 
        if (tt_ribbon.output[2] == 2) { // GLFW
            printf("GLFW settings\n");
        } 
    }
}

Use this code as a template to run arbitrary segments of code when the user clicks on different ribbon options.

Alternate initialisation

If the configuration file is not found, the program will use a default ribbon configuration which is identical to the ribbonConfig.txt example.

For more portable executables that don't rely on extra configuration files, the ribbon can also be initialised with a list from within the program

list_t *ribbonConfig = list_init();
list_append(ribbonConfig, (unitype) "File, New, Save, Save As..., Open", 's');
list_append(ribbonConfig, (unitype) "Edit, Undo, Redo, Cut, Copy, Paste", 's');
list_append(ribbonConfig, (unitype) "View, Change Theme, GLFW", 's');
ribbonInitList(ribbonConfig);

The list must contain only strings, each item in the list substitutes a line in a configuration file.

Popup

image

The turtleTools popup element is not a general purpose popup (those can be easily constructed using existing turtleTools elements), but is instead a special popup that automatically activates when the user presses the [X] to close the window. The popup also requires a configuration file to initialise.

popupInit("config/popupConfig.txt");

popupConfig.txt:

Are you sure you want to close?
Cancel
Close

You can have an arbitrary number of options - I typically use three with this configuration for editors that have saving features:

Warning: Unsaved changes
Save
Cancel
Close

Like the ribbon, the popup has a parsing function to call arbitrary code when the options are selected. See turtle.c for the example.

void parsePopupOutput(GLFWwindow *window) {
    if (tt_popup.output[0] == 0) {
        return;
    }
    tt_popup.output[0] = 0; // untoggle
    if (tt_popup.output[1] == 0) { // cancel
        turtle.close = 0;
        glfwSetWindowShouldClose(window, 0);
    }
    if (tt_popup.output[1] == 1) { // close
        turtle.popupClose = 1;
    }
}

One more thing to note: when creating your main loop, using while (turtle.close) will break out of the loop when the [X] is clicked. If you want the popup to intercept this break, use while (turtle.popupClose) and then bind one of your popup buttons to toggle turtle.popupClose to 1, like in the example above.

Alternate initialisation

If the configuration file is not found, the program will use a default popup configuration which is identical to the popupConfig.txt example.

For more portable executables that don't rely on extra configuration files, the popup can also be initialised with a list from within the program

list_t *popupConfig = list_init();
list_append(popupConfig, (unitype) "Are you sure you want to close?", 's');
list_append(popupConfig, (unitype) "Cancel", 's');
list_append(popupConfig, (unitype) "Close", 's');
popupInitList(popupConfig);

The list must contain only strings, each item in the list substitutes a line in a configuration file.

Button

The turtleTools button comes in four forms:

Default Rounded Rectangle Circle Text
image image image image

The button is always center-aligned, and there is currently no way to change this.

Creating a button requires the following parameters:

  • A label (string) (can be NULL for no label)
  • A pointer to a variable of type int8_t, which is used as the output of the button
  • X coordinate (center)
  • Y coordinate (center)
  • Size (in coordinates)
int8_t buttonVar = 0;
tt_button_t *button = buttonInit("button", &buttonVar, 150, 20, 10);

To use the button, check the button variable every cycle in the main loop. The variable's value will be 1 when the button is being pressed and 0 otherwise. A typical application of a button is to run a function when it's clicked but not continuously if it's held down. The turtleTools button has a simple solution for both use cases of one-time presses and continuous functions. If you want to run a function only one time, simply use code like this:

if (buttonVar) {
    printf("button clicked\n");
    buttonVar = 0;
}

If you want to run a function continuously as the button is pressed, use code like this:

if (buttonVar) {
    yposition += 5;
}

In general, writing to the buttonVar won't allow you to "phantom" click the button (the button won't change appearance if you set the buttonVar to 1 while the button isn't being clicked). Rather, the mechanism is that the variable is "pulled down" continuously if the button isn't clicked, and when you click the button it is "set high" and "left floating" - so it can be set back low if desired.

Additional Button Options

There are four distinct button shapes that any given button can morph between. Those shapes are:

typedef enum {
    TT_BUTTON_SHAPE_RECTANGLE = 0,
    TT_BUTTON_SHAPE_ROUNDED_RECTANGLE = 1,
    TT_BUTTON_SHAPE_CIRCLE = 2,
    TT_BUTTON_SHAPE_TEXT = 3,
} tt_button_shape_t;

When you create a button, it defaults to TT_BUTTON_SHAPE_RECTANGLE. To change the button shape, set the shape field of the button to a new form:

int8_t buttonVar = 0;
tt_button_t *button = buttonInit("button", &buttonVar, 150, 20, 10);
button -> shape = TT_BUTTON_SHAPE_ROUNDED_RECTANGLE;

You can also disable or hide the button without deleting it by setting the button's enabled field:

Disable the functionality of the button:

int8_t buttonVar = 0;
tt_button_t *button = buttonInit("button", &buttonVar, 150, 20, 10);
button -> enabled = TT_ELEMENT_NO_MOUSE;

Hide the button:

int8_t buttonVar = 0;
tt_button_t *button = buttonInit("button", &buttonVar, 150, 20, 10);
button -> enabled = TT_ELEMENT_HIDE;

To delete a button, use buttonFree():

int8_t buttonVar = 0;
tt_button_t *button = buttonInit("button", &buttonVar, 150, 20, 10);
buttonFree(button);

Additional Button Outputs

The button's output can also be read from the button's status field. The button's status is 0 when it is idle, -1 if the mouse is hovering over the button, 2 for one tick when the button is pressed, and 1 when the button is held down.

int8_t buttonVar = 0;
tt_button_t *button = buttonInit("button", &buttonVar, 150, 20, 10);
if (button -> status == 0) {
    // button is idle
}
if (button -> status == -1) {
    // mouse is touching button
}
if (button -> status == 2) {
    // button just got clicked
}
if (button -> status == 1) {
    // button is being held down
}

Switch

The turtleTools switch comes in four forms:

Default Sideswipe Checkbox Xbox
image image image image

Default switch is center-aligned. The other forms are left-aligned. There is not currently a way to change this.

Creating a switch requires the following parameters:

  • A label (string) (can be NULL for no label)
  • A pointer to a variable of type int8_t, which is used as the output of the switch
  • X coordinate
  • Y coordinate
  • Size (in coordinates)
int8_t switchVar = 0;
tt_switch_t *switch = switchInit("Switch", &switchVar, 0, 30, 10);

This will create a switch of the default style. To change the style to sideswipe, checkbox, or xbox, edit the switch's style field.

int8_t sideswipe = 0, checkbox = 1, xbox = 1;
tt_switch_t *switch_sideswipe = switchInit("Side Swipe", &sideswipe, 5, 15, 10);
tt_switch_t *switch_checkbox = switchInit("Checkbox", &checkbox, 0, 0, 10);
tt_switch_t *switch_xbox = switchInit("Xbox", &xbox, 0, -15, 10);
switch_sideswipe -> style = TT_SWITCH_STYLE_SIDESWIPE_LEFT;
switch_checkbox -> style = TT_SWITCH_STYLE_CHECKBOX;
switch_xbox -> style = TT_SWITCH_STYLE_XBOX;

The value of the int8_t pointed to by the variable parameter determines whether the switch appears flipped on initialisation. A value of 0 will cause the switch to be initialised with the switch to the left, and a non-zero value will cause the switch to be initialised with the switch to the right.

When the switch is clicked, the variable toggles between 0 and 1, which correspond to left and right states of the switch. The variable can be read to determine the state of the switch. Additionally, the variable can be set to change the state of the switch.

Additionally, setting the value of the variable to 0 in the program by any means will toggle the switch to the left, and setting the value to a non-zero number will toggle the switch to the right.

Dial

There is only one style for the dial.

Default
image

Creating a dial requires the following parameters:

  • A label (string) (can be NULL for no label)
  • A pointer to a variable of type double, which is used as the output of the dial
  • A scaling scheme, either TT_DIAL_LINEAR, TT_DIAL_LOG, or TT_DIAL_EXP
  • X coordinate (center)
  • Y coordinate (center)
  • Size (in coordinates)
  • Lower bound (minimum value of the variable)
  • Upper bound (maximum value of the variable)
  • Render Factor (scales the output variable for the display of the value of the dial)
double dialVar = 0.0;
dialInit("Dial", &dialVar, TT_DIAL_LINEAR, 0, 0, 10, 0, 1000, 1);

The dial reflects the value of the variable parameter. If you change the value through code, the dial will update to reflect the new value. The value of the variable is also changed by the user interacting with the dial itself.

The dial has a full 360 degree range, where the minimum and maximum positions are at the top of the dial. This cannot be changed currently.

The scaling scheme of the dial determines the change in the variable to dial theta function. Linear scaling has a constant theta to value ratio, exponential scaling has a lower theta to value near zero and a higher one near the max, and logarithmic scaling has a higher theta to value near zero and a lower one near the max.

The dial can also be right clicked to reset it to a default value which is the value of the variable when dialInit() was called.

Despite the dial's minimum and maximum values being of the double type, the dial will always round this variable to the nearest whole number, and will snap the dial's angle accordingly. If you want to avoid snapping, you can use the Render Factor parameter, which multiplies the value of the variable parameter before displaying it. The displayed number will always be a whole number though. You can disable the displayed number by setting the Render Factor parameter to 0, then if you want to you can write the text manually with a turtleTextWriteString() call:

/* this line of code renders the dial's variable (divided by 1000) with two decimal places */
turtleTextWriteStringf(dial -> x + dial -> size + 3, dial -> y, dial -> size / 2.5, 0, "%.2lf", *(dialp -> variable) / 1000);

Slider

The slider comes in two main forms: horizontal and vertical

Horizontal Vertical
image image

Creating a slider requires the following parameters:

  • A label (string) (can be NULL for no label)
  • A pointer to a variable of type double, which is used as the output of the slider
  • A slider type of either TT_SLIDER_HORIZONTAL or TT_SLIDER_VERTICAL
  • An alignment of either TT_SLIDER_ALIGN_LEFT, TT_SLIDER_ALIGN_CENTER, or TT_SLIDER_ALIGN_RIGHT
  • X coordinate
  • Y coordinate
  • Size (in coordinates)
  • Length (in coordinates)
  • Lower bound (minimum value of the variable)
  • Upper bound (maximum value of the variable)
  • Render Factor (scales the output variable for the display of the value of the slider)
double sliderVar = 0.0;
sliderInit("Slider", &sliderVar, TT_SLIDER_HORIZONTAL, TT_SLIDER_ALIGN_CENTER, 0, 0, 10, 50, 0, 255, 1);

The slider works very similarly to the dial. The value of the variable determines where along the length of the slider the dot resides. The slider also always rounds its variable to the nearest whole number and only displays whole numbers. Displaying of the numbers can be deactivated by setting the Render Factor parameter to 0.

The slider can also be right clicked to reset the value to the value given to it on initialisation.

Scrollbar

The scrollbar can be though of as a special type of slider where the slider dot is replaced with a bar. The scrollbar variable also always ranges from 0 to 100.

Horizontal Vertical
image image

Creating a scrollbar requires the following parameters:

  • A pointer to a variable of type double, which is used as the output of the scrollbar
  • X coordinate
  • Y coordinate
  • Size (in coordinates)
  • Length (in coordinates)
  • Bar Percentage (a value between 0 and 100 which determines the percentage of the total length taken by the scrollbar bar)
double scrollbarVarY = 0.0;
scrollbarInit(&scrollbarVarY, TT_SCROLLBAR_VERTICAL, 310, 0, 10, 320, 33);

There's probably some formula to determine the Bar Percentage for typical scrollbars, but I don't know what it is so it's just a user-set parameter.

By default the scrollbar doesn't do anything except change the value of the output variable. You are responsible for turning that value into a scrolling mechanic. The scrollbar also does not by default interact with the scroll wheel. See the turtle.c example for code on how to make a working scrolling mechanic with scrollbar and scroll wheel integration.

Context

The context menu is a unique element. It works like a static standalone dropdown and it's typically used as a menu that pops up by right clicking a space or element.

Creating a context requires the following parameters:

  • A list of options (a list of all strings)
  • A pointer to a variable of type int32_t, which is used as the output of the context
  • X coordinate
  • Y coordinate
  • size (in coordinates)
list_t *contextOptions = list_init();
list_append(contextOptions, (unitype) "Button", 's');
list_append(contextOptions, (unitype) "Switch", 's');
list_append(contextOptions, (unitype) "Dial", 's');
list_append(contextOptions, (unitype) "Slider", 's');
list_append(contextOptions, (unitype) "Scrollbar", 's');
list_append(contextOptions, (unitype) "Context", 's');
list_append(contextOptions, (unitype) "Dropdown", 's');
list_append(contextOptions, (unitype) "Textbox", 's');
int32_t contextVar = -1;
tt_context_t *context = contextInit(contextOptions, &contextVar, 0, 0, 10);
context -> enabled = TT_ELEMENT_HIDE;
...
// in main loop
if (turtleMouseRight()) {
    if (keys[1] == 0) {
        keys[1] = 1;
        context -> enabled = TT_ELEMENT_ENABLED;
        context -> x = turtle.mouseX;
        context -> y = turtle.mouseY;
    }
} else {
    keys[1] = 0;
}
if (contextVar >= 0) {
    if (contextVar == 0) {
        // "Button" selected
    }
    if (contextVar == 0) {
        // "Switch" selected
    }
    ...
    contextVar = -1;
}

See turtle.c for an example of how a right click context is implemented.

Dropdown

The dropdown has only one style

Default
image

Creating a dropdown requires the following parameters:

  • A label (string) (can be NULL for no label)
  • A list of options (a list of all strings)
  • A pointer to a variable of type int32_t, which is used as the output of the dropdown
  • An alignment of either TT_DROPDOWN_ALIGN_LEFT, TT_DROPDOWN_ALIGN_CENTER, or TT_DROPDOWN_ALIGN_RIGHT
  • X coordinate
  • Y coordinate
  • Size (in coordinates)
list_t *sources = list_init();
int32_t sourceIndex = 0;
list_append(sources, (unitype) "None", 's');
list_append(sources, (unitype) "SP932", 's');
list_append(sources, (unitype) "SP932U", 's');
list_append(sources, (unitype) "SP928", 's');
list_append(sources, (unitype) "SP1203", 's');
list_append(sources, (unitype) "SP-1550M", 's');
dropdownInit("Source", sources, &sourceIndex, TT_DROPDOWN_ALIGN_LEFT, 0, 0, 10);

The dropdown allows the user to pick between the strings in the list parameter. The output of the user's selection is an index of the list in the variable parameter. The variable parameter can also be changed by the code to change which item of the dropdown is displayed.

Textbox

The textbox comes in one style

Default
image

Creating a dropdown requires the following parameters:

  • A label (string) (can be NULL for no label)
  • A buffer for the text (can be NULL for automatic malloc), must exceed maximum number of characters
  • A maximum number of characters allowed in the textbox
  • X coordinate
  • Y coordinate
  • Size (in coordinates)
  • Length (in coordinates)
tt_textbox_t *textbox = textboxInit("Textbox", NULL, 128, -50, -110, 10, 100);

To access the text from the textbox, read from the character array textbox -> text, which is a standard null-terminated C string.

The textbox does not yet support highlighting, copy pasting, or cursor changing. I plan to add highlighting but the others are not planned as they would require osTools integration with turtleTools and I would like to keep those modules separate.

Clone this wiki locally