diff --git a/system/ui/.gitignore b/system/ui/.gitignore index 1d32f7d8777bcf..3c2e611497e678 100644 --- a/system/ui/.gitignore +++ b/system/ui/.gitignore @@ -1 +1,2 @@ spinner +text diff --git a/system/ui/SConscript b/system/ui/SConscript index e97282b54e6ad8..175ad713cb45c0 100644 --- a/system/ui/SConscript +++ b/system/ui/SConscript @@ -21,3 +21,4 @@ if not UBUNTU_FOCAL: if arch != 'aarch64': renv.Program("spinner", ["raylib/spinner.cc"], LIBS=linked_libs, FRAMEWORKS=mac_frameworks) + renv.Program("text", ["raylib/text.cc"], LIBS=linked_libs, FRAMEWORKS=mac_frameworks) diff --git a/system/ui/raylib/text.cc b/system/ui/raylib/text.cc new file mode 100644 index 00000000000000..8130edc04bfdab --- /dev/null +++ b/system/ui/raylib/text.cc @@ -0,0 +1,108 @@ +#include +#include +#include + +#include "system/hardware/hw.h" +#include "system/ui/raylib/util.h" + +constexpr int kMargin = 50; +constexpr int kFontSize = 70; +constexpr int kButtonPadding = 50; +constexpr int kButtonSidePadding = 100; +constexpr int kButtonRadius = 20; +constexpr int kTextSpacing = 0.0f; + +struct Button { + Rectangle bounds{0, 0, 0, 0}; + const char* text{nullptr}; + bool hovered{false}; + + void draw() { + Color color = hovered ? RAYLIB_GRAY : RAYLIB_BLACK; + DrawRectangleRounded(bounds, 0.2f, kButtonRadius, color); + DrawRectangleRoundedLines(bounds, 0.2f, kButtonRadius, RAYLIB_WHITE); + + Vector2 textSize = MeasureTextEx(getFont(), text, kFontSize, kTextSpacing); + Vector2 textPos = { + bounds.x + (bounds.width - textSize.x) / 2, + bounds.y + (bounds.height - textSize.y) / 2 + }; + DrawTextEx(getFont(), text, textPos, kFontSize, kTextSpacing, RAYLIB_WHITE); + } + + bool checkHover(Vector2 mousePos) { + hovered = CheckCollisionPointRec(mousePos, bounds); + return hovered; + } +}; + +int main(int argc, char *argv[]) { + if (argc < 2) { + printf("Usage: ./text \n"); + return 1; + } + + SetConfigFlags(FLAG_MSAA_4X_HINT); + + initApp("text", 60); + SetExitKey(0); + + const char* displayText = argv[1]; + if (!displayText || strlen(displayText) == 0) { + printf("Error: Empty message\n"); + return 1; + } + + Button button; +#ifdef __aarch64__ + button.text = "Reboot"; +#else + button.text = "Exit"; +#endif + + while (!WindowShouldClose()) { + if (IsKeyPressed(KEY_ESCAPE)) break; + + BeginDrawing(); + ClearBackground(RAYLIB_BLACK); + + // Update button position and size + float buttonWidth = MeasureTextEx(getFont(), button.text, kFontSize, kTextSpacing).x + 2 * kButtonSidePadding; + button.bounds = { + static_cast(GetScreenWidth() - buttonWidth - kMargin * 2), + static_cast(GetScreenHeight() - kFontSize - 2 * kButtonPadding - kMargin), + buttonWidth, + static_cast(kFontSize + 2 * kButtonPadding) + }; + + // Handle mouse input + Vector2 mousePos = GetMousePosition(); + button.checkHover(mousePos); + + if (button.hovered && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { +#ifdef __aarch64__ + Hardware::reboot(); +#else + CloseWindow(); + return 0; +#endif + } + + // Draw text in container + Rectangle textBox = { + static_cast(kMargin), + static_cast(kMargin), + static_cast(GetScreenWidth() - 2 * kMargin), + static_cast(GetScreenHeight() - button.bounds.height - 3 * kMargin) + }; + + DrawTextBoxed(displayText, textBox, kFontSize, kTextSpacing, true, RAYLIB_WHITE); + + button.draw(); + + EndDrawing(); + } + + CloseWindow(); + return 0; +} diff --git a/system/ui/raylib/util.cc b/system/ui/raylib/util.cc index 46f8a4fd3fb454..721b7edb4fddf5 100644 --- a/system/ui/raylib/util.cc +++ b/system/ui/raylib/util.cc @@ -9,14 +9,14 @@ #include "system/hardware/hw.h" constexpr std::array(FontWeight::Count)> FONT_FILE_PATHS = { - "../../assets/fonts/Inter-Black.ttf", - "../../assets/fonts/Inter-Bold.ttf", - "../../assets/fonts/Inter-ExtraBold.ttf", - "../../assets/fonts/Inter-ExtraLight.ttf", - "../../assets/fonts/Inter-Medium.ttf", - "../../assets/fonts/Inter-Regular.ttf", - "../../assets/fonts/Inter-SemiBold.ttf", - "../../assets/fonts/Inter-Thin.ttf", + "../../selfdrive/assets/fonts/Inter-Black.ttf", + "../../selfdrive/assets/fonts/Inter-Bold.ttf", + "../../selfdrive/assets/fonts/Inter-ExtraBold.ttf", + "../../selfdrive/assets/fonts/Inter-ExtraLight.ttf", + "../../selfdrive/assets/fonts/Inter-Medium.ttf", + "../../selfdrive/assets/fonts/Inter-Regular.ttf", + "../../selfdrive/assets/fonts/Inter-SemiBold.ttf", + "../../selfdrive/assets/fonts/Inter-Thin.ttf", }; struct FontManager { @@ -50,5 +50,98 @@ void initApp(const char *title, int fps) { Hardware::set_brightness(65); // SetTraceLogLevel(LOG_NONE); InitWindow(2160, 1080, title); + + for (int i = 0; i < static_cast(FontWeight::Count); i++) { + SetTextureFilter(getFont(static_cast(i)).texture, TEXTURE_FILTER_BILINEAR); + } + SetTargetFPS(fps); } + +void DrawTextBoxed(const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint) { + int length = TextLength(text); + float textOffsetY = 0; + float textOffsetX = 0.0f; + float scaleFactor = fontSize/(float)getFont().baseSize; + + int state = wordWrap? 0 : 1; // 0-Measure, 1-Draw + int startLine = -1; + int endLine = -1; + int lastk = -1; + + for (int i = 0, k = 0; i < length; i++, k++) { + int codepointByteCount = 0; + int codepoint = GetCodepoint(&text[i], &codepointByteCount); + int index = GetGlyphIndex(getFont(), codepoint); + + if (codepoint == 0x3f) codepointByteCount = 1; + i += (codepointByteCount - 1); + + float glyphWidth = 0; + if (codepoint != '\n') { + glyphWidth = (getFont().glyphs[index].advanceX == 0) ? + getFont().recs[index].width*scaleFactor : + getFont().glyphs[index].advanceX*scaleFactor; + + if (i + 1 < length) glyphWidth = glyphWidth + spacing; + } + + if (state == 0) { + if ((codepoint == ' ') || (codepoint == '\t') || (codepoint == '\n')) endLine = i; + + if ((textOffsetX + glyphWidth) > rec.width) { + endLine = (endLine < 1)? i : endLine; + if (i == endLine) endLine -= codepointByteCount; + if ((startLine + codepointByteCount) == endLine) endLine = (i - codepointByteCount); + + state = !state; + } else if ((i + 1) == length) { + endLine = i; + state = !state; + } else if (codepoint == '\n') state = !state; + + if (state == 1) { + textOffsetX = 0; + i = startLine; + glyphWidth = 0; + + int tmp = lastk; + lastk = k - 1; + k = tmp; + } + } else { + if (codepoint == '\n') { + if (!wordWrap) { + textOffsetY += (getFont().baseSize + getFont().baseSize/2)*scaleFactor; + textOffsetX = 0; + } + } else { + if (!wordWrap && ((textOffsetX + glyphWidth) > rec.width)) { + textOffsetY += (getFont().baseSize + getFont().baseSize/2)*scaleFactor; + textOffsetX = 0; + } + + if ((textOffsetY + getFont().baseSize*scaleFactor) > rec.height) break; + + if ((codepoint != ' ') && (codepoint != '\t')) { + DrawTextCodepoint(getFont(), codepoint, + (Vector2){ rec.x + textOffsetX, rec.y + textOffsetY }, + fontSize, tint); + } + } + + if (wordWrap && (i == endLine)) { + textOffsetY += (getFont().baseSize + getFont().baseSize/2)*scaleFactor; + textOffsetX = 0; + startLine = endLine; + endLine = -1; + glyphWidth = 0; + k = lastk; + + state = !state; + } + } + + if ((textOffsetX != 0) || (codepoint != ' ')) textOffsetX += glyphWidth; + } +} diff --git a/system/ui/raylib/util.h b/system/ui/raylib/util.h index 1e2662fb90bb2d..eb29544afabfde 100644 --- a/system/ui/raylib/util.h +++ b/system/ui/raylib/util.h @@ -17,5 +17,6 @@ enum class FontWeight { }; void initApp(const char *title, int fps); -const Font& getFont(FontWeight weight = FontWeight::Normal); +const Font& getFont(FontWeight weight = FontWeight::Regular); Texture2D LoadTextureResized(const char *fileName, int size); +void DrawTextBoxed(const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint);