<- back to docs

UI toolkit

themes, widgets, menus, images and embedded files.

UI toolkit

Themes (mydear/themes.hpp)

Eighteen palettes: One Dark and One Light, Ayu, Gruvbox, VS Code, Catppuccin, Tokyo Night, Nord, Dracula, Solarized and GitHub. Each is a Theme::Colors set of named colours (text, subtext1, base, blue, red and so on).

mydear::Theme::Current() = &mydear::Theme::themeById("tokyo-night");
mydear::Theme::ApplyTheme(mydear::Theme::Current()->colors);

ApplyTheme sets the ImGui style. Theme::Current() is the theme mydear’s widgets draw with; set it whenever the theme changes. Spacing constants: Theme::Spacing::XS (2), S (4), M (8), L (16), XL (20).

Widgets

Widget Header Notes
Button, SmallButton button.hpp variants: Primary, Secondary, Danger, Ghost and so on
IconButton, IconLabelButton button.hpp an icon font glyph, with or without a label
SegmentedTabs button.hpp a row of mutually exclusive buttons
Select select.hpp a dropdown over a string array
ToggleButton toggle_button.hpp a switch bound to a bool
Splitter splitter.hpp a draggable divider between two panes
Spinner, SpinnerOverlay spinner.hpp the overlay draws on a draw list without taking layout space

Open menus through these so every menu gets the same padding (kMenuPadding):

Instead of Use
ImGui::BeginPopup mydear::BeginMenuPopup(id)
ImGui::BeginPopupContextItem mydear::BeginContextMenu(id)
ImGui::BeginMenu mydear::BeginSubMenu(label)
ImGui::BeginCombo / EndCombo mydear::BeginSelect(id, preview) / EndSelect()

Close them with ImGui’s usual EndPopup() / EndMenu(). FitScreen(size) and CapToScreen() keep dialogs inside small screens (iPhone, split view).

Alerts and prompts (mydear/alert.hpp)

Alert::show(title, message, buttons) queues a modal with a message and buttons (one OK when there are none); InputDialog::show(title, label, initial, confirmText, onConfirm, onCancel, validator) queues a one-line text prompt, kept open while validator or onConfirm returns an error message. Both can be called from any thread and wake the loop. Call Alert::render() once a frame after the rest of the UI: it draws the front entry over every popup open that frame, so an alert raised from inside a dialog stacks on top of it instead of replacing it. Buttons sit cancel first, then destructive, then default; escape answers the cancel button (or the only one), return the default one, never a destructive one. The popup’s ID is <title>###mydear_alert, so a test engine reaches a button as //<title>###mydear_alert/<button>.

Fonts (mydear/fonts.hpp)

Call AddFont from Delegate::setup(). The first file is the base font and the rest merge into it: icon fonts (icons = true: no oversampling, snapped to pixels, with the icon font’s glyph ranges) and other scripts. With systemCjk (the default) the system’s Chinese, Japanese and Korean font merges in last, so names and text in those scripts render; SystemCjkFontPath() says which one, or nothing when none is installed. Without FreeType it skips variable fonts (CFF2 outlines, which stb_truetype cannot read): Android 13 and later ship Noto Sans CJK as one, so Android gets Noto Serif CJK. That file is mapped rather than read (they are 20 MB and more), and the OS loads only the glyphs drawn. Font data is not copied, so files from mydear_embed_fonts work as they are:

const EmbeddedFont* text = findEmbeddedFont("Inter-Regular");
const EmbeddedFont* icons = findEmbeddedFont("fa-solid-900");
static const ImWchar kIcons[] = {ICON_MIN_FA, ICON_MAX_16_FA, 0};
mydear::AddFont({{text->data, text->size}, {icons->data, icons->size, kIcons, true}});

File dialogs (mydear/file_dialog.hpp)

OpenFileDialog(filters, done), SaveFileDialog(defaultName, filters, done) and PickFolderDialog(done) call done with the chosen path, and not at all when the user cancels. Filters are a name and extensions without dots ({"CSV files", "csv,tsv"}); "*" allows any file.

Desktops iOS and Android
Open Native File Dialog, modal: done runs before the call returns the system document picker; done runs a moment later. Another app’s file is copied into copyInto (DocumentsDir() when empty), because it can’t be opened by path
Save the system save dialog a free name in DocumentsDir() (name 2.ext when taken), at once
Folder the system folder picker DocumentsDir(), at once

On iOS the documents folder shows in the Files app under the app’s name. Filters become content types (iOS) or MIME types (Android, from a short table; anything else allows any file). On the desktops, link Native File Dialog Extended (vcpkg nativefiledialog-extended); mydear finds it with find_package(nfd).

Haptics (mydear/haptics.hpp)

PlayHaptic(kind) gives a short vibration, from the UI thread. Kinds: Tap (a button or toggle), Selection (a value ticks over), LongPress, Success, Warning, Error. iOS uses UIKit’s feedback generators, Android performHapticFeedback (success and error are their own patterns from Android 11), and macOS the trackpad’s feedback performer, felt only on a Force Touch trackpad while the finger is down. Windows and Linux do nothing. The system setting wins: with haptics turned off, nothing plays. The shells already play LongPress when a long press takes hold.

Images (mydear/image.hpp)

LoadTexture(bytes, size, create) decodes a PNG with stb_image and hands the pixels to create, normally the app’s createTexture.

Embedded files (cmake/MydearEmbed.cmake, mydear/embedded.hpp)

mydear_embed_fonts(${CMAKE_CURRENT_SOURCE_DIR}/assets/fonts)   # sets EMBEDDED_FONTS_FILE
mydear_embed_images(${CMAKE_CURRENT_SOURCE_DIR}/assets/images) # sets EMBEDDED_IMAGES_FILE

Every file in the folder is baked into the binary; findEmbeddedFont(name) and findEmbeddedImage(name) look one up by file name without the extension. If you use either, call both: the generated files define the lookup tables, and an empty folder still generates an empty table.